seo 0.12.1 → 0.13.1
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-3A3Vzp7r.js} +1 -1
- package/dist/dist-rmavn5h6.js +420 -0
- package/dist/dist-rmavn5h6.js.map +1 -0
- package/dist/{esm-Cyusbw2J.js → esm-BIXGSxFJ.js} +140 -176
- package/dist/esm-BIXGSxFJ.js.map +1 -0
- package/dist/index.d.ts +916 -875
- package/dist/index.js +458 -212
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +11 -11
- package/dist/mcp.js +724 -395
- 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-BIXGSxFJ.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";
|
|
@@ -1400,6 +1400,44 @@ function agentActionsView(report, options = {}) {
|
|
|
1400
1400
|
};
|
|
1401
1401
|
}
|
|
1402
1402
|
//#endregion
|
|
1403
|
+
//#region packages/core/src/providers/errors.ts
|
|
1404
|
+
const PROVIDER_ERROR_CODES = {
|
|
1405
|
+
configuration: "AUTH_CONFIG_REQUIRED",
|
|
1406
|
+
authentication: "AUTH_CONFIG_REQUIRED",
|
|
1407
|
+
"rate-limit": "RATE_LIMITED",
|
|
1408
|
+
timeout: "PROVIDER_UNAVAILABLE",
|
|
1409
|
+
"response-too-large": "PROVIDER_UNAVAILABLE",
|
|
1410
|
+
"invalid-response": "PROVIDER_UNAVAILABLE",
|
|
1411
|
+
"remote-error": "PROVIDER_UNAVAILABLE"
|
|
1412
|
+
};
|
|
1413
|
+
var ProviderError = class extends Error {
|
|
1414
|
+
provider;
|
|
1415
|
+
operation;
|
|
1416
|
+
code;
|
|
1417
|
+
status;
|
|
1418
|
+
retryable;
|
|
1419
|
+
constructor(input) {
|
|
1420
|
+
super(input.message, { cause: input.cause });
|
|
1421
|
+
this.name = "ProviderError";
|
|
1422
|
+
this.provider = input.provider;
|
|
1423
|
+
this.operation = input.operation;
|
|
1424
|
+
this.code = input.code;
|
|
1425
|
+
this.status = input.status ?? null;
|
|
1426
|
+
this.retryable = input.retryable ?? false;
|
|
1427
|
+
}
|
|
1428
|
+
toJSON() {
|
|
1429
|
+
return {
|
|
1430
|
+
name: this.name,
|
|
1431
|
+
provider: this.provider,
|
|
1432
|
+
operation: this.operation,
|
|
1433
|
+
code: this.code,
|
|
1434
|
+
message: this.message,
|
|
1435
|
+
status: this.status,
|
|
1436
|
+
retryable: this.retryable
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
//#endregion
|
|
1403
1441
|
//#region packages/core/src/errors.ts
|
|
1404
1442
|
const errorMetadata = {
|
|
1405
1443
|
ACCESS_DENIED: {
|
|
@@ -1465,6 +1503,7 @@ function errorMessage$1(error) {
|
|
|
1465
1503
|
function toSeoError(error) {
|
|
1466
1504
|
if (error instanceof SeoError) return error;
|
|
1467
1505
|
const message = errorMessage$1(error);
|
|
1506
|
+
if (error instanceof ProviderError) return new SeoError(PROVIDER_ERROR_CODES[error.code], message);
|
|
1468
1507
|
if (/not logged in/i.test(message)) return new SeoError("AUTH_REQUIRED", message);
|
|
1469
1508
|
if (/oauth client config.*missing|shared seo google app/i.test(message)) return new SeoError("AUTH_CONFIG_REQUIRED", message);
|
|
1470
1509
|
if (/invalid_grant|refresh token.*no longer valid/i.test(message)) return new SeoError("AUTH_EXPIRED", message);
|
|
@@ -1678,7 +1717,7 @@ async function listAnalyticsAccounts(gfetch, options) {
|
|
|
1678
1717
|
return googleJson(gfetch, url.toString(), {
|
|
1679
1718
|
...options,
|
|
1680
1719
|
label: "Google Analytics accounts",
|
|
1681
|
-
maxBytes:
|
|
1720
|
+
maxBytes: 10485760
|
|
1682
1721
|
});
|
|
1683
1722
|
});
|
|
1684
1723
|
}
|
|
@@ -1687,7 +1726,7 @@ async function listAnalyticsDataStreams(gfetch, propertyId, options) {
|
|
|
1687
1726
|
...options,
|
|
1688
1727
|
label: "Google Analytics data streams",
|
|
1689
1728
|
subject: `property ${propertyId}`,
|
|
1690
|
-
maxBytes:
|
|
1729
|
+
maxBytes: 1048576
|
|
1691
1730
|
})).dataStreams ?? [];
|
|
1692
1731
|
}
|
|
1693
1732
|
async function runAnalyticsReport(gfetch, propertyId, body, options) {
|
|
@@ -1736,13 +1775,13 @@ function day$2(value) {
|
|
|
1736
1775
|
const date = legacy ? new Date(Number(legacy[1])) : new Date(value);
|
|
1737
1776
|
return Number.isNaN(date.getTime()) ? null : date.toISOString().slice(0, 10);
|
|
1738
1777
|
}
|
|
1739
|
-
const count$
|
|
1740
|
-
function list$
|
|
1778
|
+
const count$3 = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
1779
|
+
function list$2(data, label) {
|
|
1741
1780
|
if (!Array.isArray(data)) throw new SeoError("OPTIONAL_PROVIDER_UNAVAILABLE", `Bing Webmaster returned an invalid ${label}.`);
|
|
1742
1781
|
return data.filter((item) => Boolean(item) && typeof item === "object");
|
|
1743
1782
|
}
|
|
1744
1783
|
async function listBingSites(call) {
|
|
1745
|
-
return list$
|
|
1784
|
+
return list$2(await call("GetUserSites"), "site list").filter((row) => typeof row.Url === "string").map((row) => ({
|
|
1746
1785
|
url: row.Url,
|
|
1747
1786
|
verified: row.IsVerified === true
|
|
1748
1787
|
})).sort((a, b) => a.url.localeCompare(b.url, "en"));
|
|
@@ -1750,10 +1789,10 @@ async function listBingSites(call) {
|
|
|
1750
1789
|
async function bingTraffic(call, site) {
|
|
1751
1790
|
const rows = [];
|
|
1752
1791
|
let invalidRows = 0;
|
|
1753
|
-
for (const row of list$
|
|
1792
|
+
for (const row of list$2(await call("GetRankAndTrafficStats", { siteUrl: site }), "traffic list")) {
|
|
1754
1793
|
const date = day$2(row.Date);
|
|
1755
|
-
const clicks = count$
|
|
1756
|
-
const impressions = count$
|
|
1794
|
+
const clicks = count$3(row.Clicks);
|
|
1795
|
+
const impressions = count$3(row.Impressions);
|
|
1757
1796
|
if (!date || clicks === null || impressions === null) {
|
|
1758
1797
|
invalidRows += 1;
|
|
1759
1798
|
continue;
|
|
@@ -1773,12 +1812,12 @@ async function bingTraffic(call, site) {
|
|
|
1773
1812
|
async function keyed(call, method, params) {
|
|
1774
1813
|
const rows = [];
|
|
1775
1814
|
let invalidRows = 0;
|
|
1776
|
-
for (const row of list$
|
|
1815
|
+
for (const row of list$2(await call(method, params), "row list")) {
|
|
1777
1816
|
const date = day$2(row.Date);
|
|
1778
1817
|
const key = typeof row.Query === "string" ? row.Query.trim() : "";
|
|
1779
|
-
const clicks = count$
|
|
1780
|
-
const impressions = count$
|
|
1781
|
-
const position = count$
|
|
1818
|
+
const clicks = count$3(row.Clicks);
|
|
1819
|
+
const impressions = count$3(row.Impressions);
|
|
1820
|
+
const position = count$3(row.AvgImpressionPosition);
|
|
1782
1821
|
if (!date || !key || clicks === null || impressions === null) {
|
|
1783
1822
|
invalidRows += 1;
|
|
1784
1823
|
continue;
|
|
@@ -2249,15 +2288,18 @@ function factsOnly(value) {
|
|
|
2249
2288
|
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).filter(([key]) => !ADVICE_KEYS.test(key)).map(([key, child]) => [key, factsOnly(child)]));
|
|
2250
2289
|
return value;
|
|
2251
2290
|
}
|
|
2291
|
+
function realIsoDate(value) {
|
|
2292
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(value) && (/* @__PURE__ */ new Date(`${value}T00:00:00Z`)).toISOString().startsWith(value);
|
|
2293
|
+
}
|
|
2252
2294
|
function dateWindow(input, fallbackDays) {
|
|
2253
|
-
if (input.startDate
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2295
|
+
if (!input.startDate && !input.endDate) return finalGscDateRange(fallbackDays);
|
|
2296
|
+
const endDate = input.endDate ?? latestFinalGscDate();
|
|
2297
|
+
const startDate = input.startDate ?? shiftIsoDate(endDate, 1 - fallbackDays);
|
|
2298
|
+
if (!realIsoDate(startDate) || !realIsoDate(endDate) || startDate > endDate) throw new CommandError("INVALID_INPUT", `Send real YYYY-MM-DD dates with the start before the end (got ${startDate} to ${endDate}).`);
|
|
2299
|
+
return {
|
|
2300
|
+
startDate,
|
|
2301
|
+
endDate
|
|
2302
|
+
};
|
|
2261
2303
|
}
|
|
2262
2304
|
function earlierRange(range) {
|
|
2263
2305
|
return decayComparisonRange(range, "previous-period");
|
|
@@ -2313,7 +2355,7 @@ function sharedReader(read, redirects = /* @__PURE__ */ new Map()) {
|
|
|
2313
2355
|
return found;
|
|
2314
2356
|
};
|
|
2315
2357
|
}
|
|
2316
|
-
const MAX_STATUS_BYTES =
|
|
2358
|
+
const MAX_STATUS_BYTES = 10485760;
|
|
2317
2359
|
async function googleUpdates(window) {
|
|
2318
2360
|
if (!window) return [];
|
|
2319
2361
|
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);
|
|
@@ -2341,20 +2383,38 @@ function listOfUpdates(value, checked) {
|
|
|
2341
2383
|
if (value.length === 0) return `Google updates ${where}: none.`;
|
|
2342
2384
|
return `Google updates ${where} (timing only, not cause): ${value.map((update) => `${update.name} (${update.start.slice(0, 10)} to ${update.end?.slice(0, 10) ?? "ongoing"})`).join("; ")}.`;
|
|
2343
2385
|
}
|
|
2386
|
+
function readable(names, who = "it") {
|
|
2387
|
+
if (!names.length) return `Search Console properties ${who} can read: none.`;
|
|
2388
|
+
const more = names.length > 20 ? `, and ${names.length - 20} more` : "";
|
|
2389
|
+
return `Search Console properties ${who} can read: ${names.slice(0, 20).join(", ")}${more}.`;
|
|
2390
|
+
}
|
|
2344
2391
|
function matchProperty(names, site) {
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
let
|
|
2392
|
+
const value = site.trim();
|
|
2393
|
+
if (names.includes(value)) return value;
|
|
2394
|
+
let url;
|
|
2348
2395
|
try {
|
|
2349
|
-
|
|
2350
|
-
host = url.hostname.replace(/^www\./, "");
|
|
2351
|
-
origin = url.origin;
|
|
2396
|
+
url = new URL(value.includes("://") ? value : `https://${value.replace(/^sc-domain:/i, "")}`);
|
|
2352
2397
|
} catch {
|
|
2353
2398
|
return null;
|
|
2354
2399
|
}
|
|
2355
|
-
const
|
|
2356
|
-
|
|
2357
|
-
|
|
2400
|
+
const bare = (name) => name.replace(/^www\./, "");
|
|
2401
|
+
const host = bare(url.hostname);
|
|
2402
|
+
const domain = names.filter((name) => name.startsWith("sc-domain:")).filter((name) => {
|
|
2403
|
+
const own = name.slice(10);
|
|
2404
|
+
return own.startsWith("www.") ? bare(own) === host : host === own || host.endsWith(`.${own}`);
|
|
2405
|
+
}).sort((a, b) => b.length - a.length || a.localeCompare(b))[0];
|
|
2406
|
+
if (domain) return domain;
|
|
2407
|
+
const schemes = value.includes("://") ? [url.protocol] : ["https:", "http:"];
|
|
2408
|
+
return names.map((name) => {
|
|
2409
|
+
try {
|
|
2410
|
+
return {
|
|
2411
|
+
name,
|
|
2412
|
+
url: new URL(name)
|
|
2413
|
+
};
|
|
2414
|
+
} catch {
|
|
2415
|
+
return null;
|
|
2416
|
+
}
|
|
2417
|
+
}).filter((entry) => entry !== null && schemes.includes(entry.url.protocol) && bare(entry.url.hostname) === host && `${url.pathname}/`.startsWith(entry.url.pathname)).sort((a, b) => b.url.pathname.length - a.url.pathname.length || schemes.indexOf(a.url.protocol) - schemes.indexOf(b.url.protocol) || Number(b.url.hostname === url.hostname) - Number(a.url.hostname === url.hostname) || a.name.localeCompare(b.name))[0]?.name ?? null;
|
|
2358
2418
|
}
|
|
2359
2419
|
//#endregion
|
|
2360
2420
|
//#region packages/core/src/commands/present.ts
|
|
@@ -2776,9 +2836,10 @@ var LinkedAccounts = class {
|
|
|
2776
2836
|
}
|
|
2777
2837
|
async resolve(site) {
|
|
2778
2838
|
if (this.google.length === 0) throw new CommandError("NOT_CONNECTED", `No Google account is connected. ${this.hint.signIn}`);
|
|
2779
|
-
const
|
|
2839
|
+
const names = await this.properties();
|
|
2840
|
+
const found = matchProperty(names, site);
|
|
2780
2841
|
if (found) return found;
|
|
2781
|
-
throw new CommandError("NO_ACCESS", `None of the connected Google accounts (${this.google.map((linked) => linked.email).join(", ")}) has a Search Console property for ${site}. ${this.hint.addGoogle}`);
|
|
2842
|
+
throw new CommandError("NO_ACCESS", `None of the connected Google accounts (${this.google.map((linked) => linked.email).join(", ")}) has a Search Console property for ${site}. ${readable(names, "they")} ${this.hint.addGoogle}`);
|
|
2782
2843
|
}
|
|
2783
2844
|
async owner(site) {
|
|
2784
2845
|
const owner = (await this.ownerMap()).get(await this.resolve(site));
|
|
@@ -3179,6 +3240,10 @@ function classify(error) {
|
|
|
3179
3240
|
errorCategory: "crawl_timeout",
|
|
3180
3241
|
failureReason: "crawl_timeout"
|
|
3181
3242
|
};
|
|
3243
|
+
const provider = signals.find((signal) => signal.name === "ProviderError");
|
|
3244
|
+
const providerCode = PROVIDER_ERROR_CODES[provider?.code];
|
|
3245
|
+
const fromProvider = providerCode && PRODUCT_FAILURES[providerCode];
|
|
3246
|
+
if (fromProvider) return fromProvider;
|
|
3182
3247
|
for (const code of codes) {
|
|
3183
3248
|
const product = PRODUCT_FAILURES[code];
|
|
3184
3249
|
if (product) return product;
|
|
@@ -3397,6 +3462,19 @@ function detectTelemetryAgent(input = {}) {
|
|
|
3397
3462
|
if (env.CODEX_HOME || env.CODEX_THREAD_ID || env.CODEX_SANDBOX) return "codex";
|
|
3398
3463
|
return "cli";
|
|
3399
3464
|
}
|
|
3465
|
+
function detectAgentName(env = process.env) {
|
|
3466
|
+
const named = (env.AI_AGENT || env.AGENT)?.trim().toLowerCase();
|
|
3467
|
+
if (named) return named.slice(0, 40);
|
|
3468
|
+
const agent = detectTelemetryAgent({ env });
|
|
3469
|
+
if (agent !== "cli") return agent;
|
|
3470
|
+
if (env.GEMINI_CLI) return "gemini";
|
|
3471
|
+
if (env.OPENCODE || env.OPENCODE_CLIENT) return "opencode";
|
|
3472
|
+
if (env.ANTIGRAVITY_AGENT) return "antigravity";
|
|
3473
|
+
if (env.AUGMENT_AGENT) return "augment";
|
|
3474
|
+
if (env.COPILOT_MODEL || env.COPILOT_ALLOW_ALL) return "github-copilot";
|
|
3475
|
+
if (env.REPL_ID) return "replit";
|
|
3476
|
+
return null;
|
|
3477
|
+
}
|
|
3400
3478
|
function telemetryAgent(options) {
|
|
3401
3479
|
return options.agent ?? detectTelemetryAgent({
|
|
3402
3480
|
clientName: options.clientName,
|
|
@@ -3631,6 +3709,15 @@ async function cloudCredentials(options = {}) {
|
|
|
3631
3709
|
})();
|
|
3632
3710
|
return credentials;
|
|
3633
3711
|
}
|
|
3712
|
+
async function cloudWeek() {
|
|
3713
|
+
const token = cloudToken();
|
|
3714
|
+
if (!token) throw new SeoError("AUTH_REQUIRED", notSignedIn());
|
|
3715
|
+
const result = await call$1("/account/cli/week", { token });
|
|
3716
|
+
if (result.status === 401) throw new SeoError("AUTH_EXPIRED", `This machine is not linked to a seoskill.dev account any more. ${CLOUD_SIGN_IN}`);
|
|
3717
|
+
if (result.status === 403) return null;
|
|
3718
|
+
if (!result.ok) throw new SeoError("PROVIDER_UNAVAILABLE", `seoskill.dev returned ${result.status}. Try again in a minute.`);
|
|
3719
|
+
return result.body;
|
|
3720
|
+
}
|
|
3634
3721
|
function notSignedIn() {
|
|
3635
3722
|
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
3723
|
}
|
|
@@ -3680,7 +3767,7 @@ async function startCloudLogin() {
|
|
|
3680
3767
|
method: "POST",
|
|
3681
3768
|
body: JSON.stringify({
|
|
3682
3769
|
label: `seo on ${hostname().split(".")[0] || "this machine"}`.slice(0, 60),
|
|
3683
|
-
agent: getTelemetryStatus().enabled ?
|
|
3770
|
+
agent: getTelemetryStatus().enabled ? detectAgentName() : void 0
|
|
3684
3771
|
})
|
|
3685
3772
|
});
|
|
3686
3773
|
const { device, code, url, expiresIn } = result.body;
|
|
@@ -4768,9 +4855,9 @@ function googleFetchFor(client) {
|
|
|
4768
4855
|
const LOCAL_SIGN_IN = { signIn: "Run `seo auth login` again." };
|
|
4769
4856
|
//#endregion
|
|
4770
4857
|
//#region packages/core/src/storage/cache-maintenance.ts
|
|
4771
|
-
const MEBIBYTE$2 =
|
|
4858
|
+
const MEBIBYTE$2 = 1048576;
|
|
4772
4859
|
const CACHE_MAX_BYTES = 256 * MEBIBYTE$2;
|
|
4773
|
-
const CACHE_MAX_AGE_MS =
|
|
4860
|
+
const CACHE_MAX_AGE_MS = 2592e6;
|
|
4774
4861
|
const CACHE_LIMITS = {
|
|
4775
4862
|
gsc_cache: 48 * MEBIBYTE$2,
|
|
4776
4863
|
url_inspection_cache: 16 * MEBIBYTE$2,
|
|
@@ -6427,7 +6514,7 @@ async function extractMainMarkdown(fetchResult) {
|
|
|
6427
6514
|
const cut = markdown.slice(0, MARKDOWN_LIMIT);
|
|
6428
6515
|
const end = cut.lastIndexOf("\n");
|
|
6429
6516
|
return {
|
|
6430
|
-
markdown: (end >
|
|
6517
|
+
markdown: (end > 1e4 ? cut.slice(0, end) : cut).trimEnd(),
|
|
6431
6518
|
characters: markdown.length,
|
|
6432
6519
|
truncated: true
|
|
6433
6520
|
};
|
|
@@ -7691,7 +7778,7 @@ async function fetchWithRedirectChain(url, signal) {
|
|
|
7691
7778
|
}
|
|
7692
7779
|
//#endregion
|
|
7693
7780
|
//#region packages/core/src/fetch/page-fetcher/robots.ts
|
|
7694
|
-
const MAX_ROBOTS_RESPONSE_BYTES =
|
|
7781
|
+
const MAX_ROBOTS_RESPONSE_BYTES = 1048576;
|
|
7695
7782
|
var RobotsAccessError = class extends Error {
|
|
7696
7783
|
reason;
|
|
7697
7784
|
evidence;
|
|
@@ -7865,7 +7952,7 @@ function diagnosticRobotsEvidence(robots) {
|
|
|
7865
7952
|
}
|
|
7866
7953
|
//#endregion
|
|
7867
7954
|
//#region packages/core/src/fetch/page-fetcher/plain.ts
|
|
7868
|
-
const MAX_PAGE_RESPONSE_BYTES =
|
|
7955
|
+
const MAX_PAGE_RESPONSE_BYTES = 5242880;
|
|
7869
7956
|
function encodePageFetchCacheEvidence(result) {
|
|
7870
7957
|
return JSON.stringify({
|
|
7871
7958
|
finalUrl: result.finalUrl,
|
|
@@ -8630,7 +8717,7 @@ function assertUrlMatchesGscProperty(property, value) {
|
|
|
8630
8717
|
if (!url.startsWith(prefix)) return invalid(`${url} is outside the Search Console property ${property}.`);
|
|
8631
8718
|
return url;
|
|
8632
8719
|
}
|
|
8633
|
-
const RESERVATION_TTL_MS =
|
|
8720
|
+
const RESERVATION_TTL_MS = 3e5;
|
|
8634
8721
|
var UrlInspectionQuotaError = class extends SeoError {
|
|
8635
8722
|
property;
|
|
8636
8723
|
requestSent;
|
|
@@ -8675,8 +8762,8 @@ function reconcileExpiredReservations(input) {
|
|
|
8675
8762
|
WHERE credential_key = ? AND property_site_url = ? AND quota_date = ?`).run(expired.count, expired.count, input.now, input.credentialKey, input.property, input.quotaDate);
|
|
8676
8763
|
}
|
|
8677
8764
|
function pruneOldQuotaRows(database, now) {
|
|
8678
|
-
const reservationCutoff = now -
|
|
8679
|
-
const bucketCutoff = (/* @__PURE__ */ new Date(now -
|
|
8765
|
+
const reservationCutoff = now - 1728e5;
|
|
8766
|
+
const bucketCutoff = (/* @__PURE__ */ new Date(now - 6048e5)).toISOString().slice(0, 10);
|
|
8680
8767
|
database.prepare(`DELETE FROM url_inspection_quota_reservations
|
|
8681
8768
|
WHERE expires_at < ?`).run(reservationCutoff);
|
|
8682
8769
|
database.prepare(`DELETE FROM url_inspection_quota_buckets
|
|
@@ -10731,7 +10818,7 @@ const actionPriority = new Map([
|
|
|
10731
10818
|
"unverified",
|
|
10732
10819
|
"covered"
|
|
10733
10820
|
].map((type, index) => [type, index]));
|
|
10734
|
-
function plural$
|
|
10821
|
+
function plural$8(count, singular, pluralLabel) {
|
|
10735
10822
|
return count === 1 ? singular : pluralLabel;
|
|
10736
10823
|
}
|
|
10737
10824
|
function actionable(item) {
|
|
@@ -10765,7 +10852,7 @@ function pageOpportunityVerdict(input) {
|
|
|
10765
10852
|
if (focus === "unverified") return "GSC visibility exists, but the page was not verified and no specific change is justified yet.";
|
|
10766
10853
|
if (focus === "covered") return "The verified page covers the sampled queries and CTR is not materially below its directional benchmark.";
|
|
10767
10854
|
const count = analysis.summary.opportunities;
|
|
10768
|
-
const prefix = `${count} ${plural$
|
|
10855
|
+
const prefix = `${count} ${plural$8(count, "actionable observation", "actionable observations")} found.`;
|
|
10769
10856
|
if (focus === "content-gap") return `${prefix} Verified body coverage is the main issue; confirm intent before expanding the page.`;
|
|
10770
10857
|
if (focus === "serp-framing") return `${prefix} The body is relevant, but its title, meta description, or H1 framing is weak.`;
|
|
10771
10858
|
if (focus === "ranking") return `${prefix} Most upside requires stronger relevance or authority; no CTR-only lift is claimed for page-two queries.`;
|
|
@@ -10780,7 +10867,7 @@ function pageOpportunityRecommendations(analysis) {
|
|
|
10780
10867
|
}
|
|
10781
10868
|
const recommendations = [`Start with "${top.query}". ${top.recommendation}`];
|
|
10782
10869
|
const technical = actions.filter((item) => item.opportunityType === "technical-check").length;
|
|
10783
|
-
if (technical) recommendations.push(`Resolve the technical evidence affecting ${technical} ${plural$
|
|
10870
|
+
if (technical) recommendations.push(`Resolve the technical evidence affecting ${technical} ${plural$8(technical, "row", "rows")} before content work.`);
|
|
10784
10871
|
const gaps = actions.filter((item) => item.opportunityType === "content-gap").length;
|
|
10785
10872
|
if (gaps >= 3) recommendations.push(`Group the ${gaps} verified content gaps into useful sections rather than adding one thin paragraph per query.`);
|
|
10786
10873
|
return recommendations;
|
|
@@ -11207,8 +11294,8 @@ function urlKey(value) {
|
|
|
11207
11294
|
}
|
|
11208
11295
|
}
|
|
11209
11296
|
const SLOW_RESPONSE_MS = 2e3;
|
|
11210
|
-
const LARGE_HTML_BYTES =
|
|
11211
|
-
const COMPRESSION_MIN_BYTES =
|
|
11297
|
+
const LARGE_HTML_BYTES = 2097152;
|
|
11298
|
+
const COMPRESSION_MIN_BYTES = 4096;
|
|
11212
11299
|
const DEEP_PAGE_DEPTH = 4;
|
|
11213
11300
|
const WEAK_VALUABLE_INLINKS = 1;
|
|
11214
11301
|
const QUERY_COVERAGE_MIN_IMPRESSIONS = 50;
|
|
@@ -12969,8 +13056,10 @@ function summarizeCrawlReport(input) {
|
|
|
12969
13056
|
if (request.outcome === "response") {
|
|
12970
13057
|
responseRequests += 1;
|
|
12971
13058
|
if (request.extraction === "failed") extractionFailures += 1;
|
|
12972
|
-
} else if (request.outcome === "failure")
|
|
12973
|
-
|
|
13059
|
+
} else if (request.outcome === "failure") {
|
|
13060
|
+
if (request.failureKind === "aborted") abortedRequests += 1;
|
|
13061
|
+
else failedRequests += 1;
|
|
13062
|
+
}
|
|
12974
13063
|
}
|
|
12975
13064
|
let highIssues = 0;
|
|
12976
13065
|
let mediumIssues = 0;
|
|
@@ -14336,7 +14425,7 @@ function cancellationRace(signal) {
|
|
|
14336
14425
|
}
|
|
14337
14426
|
//#endregion
|
|
14338
14427
|
//#region packages/core/src/analyze/crawler/crawl-memory.ts
|
|
14339
|
-
const MEBIBYTE$1 =
|
|
14428
|
+
const MEBIBYTE$1 = 1048576;
|
|
14340
14429
|
const MIN_CRAWL_MEMORY_LIMIT = 384 * MEBIBYTE$1;
|
|
14341
14430
|
const MAX_CRAWL_MEMORY_LIMIT = 640 * MEBIBYTE$1;
|
|
14342
14431
|
const MIN_CRAWL_RSS_LIMIT = 512 * MEBIBYTE$1;
|
|
@@ -14739,7 +14828,7 @@ async function collectCrawlSiteChecks(input) {
|
|
|
14739
14828
|
}
|
|
14740
14829
|
//#endregion
|
|
14741
14830
|
//#region packages/core/src/analyze/crawler/site-crawl-ai-signals.ts
|
|
14742
|
-
const MAX_AUXILIARY_RESPONSE_BYTES =
|
|
14831
|
+
const MAX_AUXILIARY_RESPONSE_BYTES = 2097152;
|
|
14743
14832
|
const LLMS_LINK_LIMIT = 20;
|
|
14744
14833
|
const AI_BOTS = [
|
|
14745
14834
|
["OAI-SearchBot", "search"],
|
|
@@ -16110,35 +16199,6 @@ async function fetchSitemapUrls(input) {
|
|
|
16110
16199
|
};
|
|
16111
16200
|
}
|
|
16112
16201
|
//#endregion
|
|
16113
|
-
//#region packages/core/src/providers/errors.ts
|
|
16114
|
-
var ProviderError = class extends Error {
|
|
16115
|
-
provider;
|
|
16116
|
-
operation;
|
|
16117
|
-
code;
|
|
16118
|
-
status;
|
|
16119
|
-
retryable;
|
|
16120
|
-
constructor(input) {
|
|
16121
|
-
super(input.message, { cause: input.cause });
|
|
16122
|
-
this.name = "ProviderError";
|
|
16123
|
-
this.provider = input.provider;
|
|
16124
|
-
this.operation = input.operation;
|
|
16125
|
-
this.code = input.code;
|
|
16126
|
-
this.status = input.status ?? null;
|
|
16127
|
-
this.retryable = input.retryable ?? false;
|
|
16128
|
-
}
|
|
16129
|
-
toJSON() {
|
|
16130
|
-
return {
|
|
16131
|
-
name: this.name,
|
|
16132
|
-
provider: this.provider,
|
|
16133
|
-
operation: this.operation,
|
|
16134
|
-
code: this.code,
|
|
16135
|
-
message: this.message,
|
|
16136
|
-
status: this.status,
|
|
16137
|
-
retryable: this.retryable
|
|
16138
|
-
};
|
|
16139
|
-
}
|
|
16140
|
-
};
|
|
16141
|
-
//#endregion
|
|
16142
16202
|
//#region packages/core/src/providers/cache.ts
|
|
16143
16203
|
function stableValue(value) {
|
|
16144
16204
|
if (Array.isArray(value)) return value.map(stableValue);
|
|
@@ -16424,7 +16484,7 @@ const identifierPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
|
16424
16484
|
const packageNamePattern = /^(?:@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._-]*)$/u;
|
|
16425
16485
|
const exactVersionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
|
|
16426
16486
|
const integrityPattern = /^sha(?:256|384|512)-[A-Za-z0-9+/=]+$/u;
|
|
16427
|
-
const MAX_SCHEMA_BYTES =
|
|
16487
|
+
const MAX_SCHEMA_BYTES = 65536;
|
|
16428
16488
|
const MAX_SCHEMA_DEPTH = 16;
|
|
16429
16489
|
const MAX_SCHEMA_VALUES = 5e3;
|
|
16430
16490
|
function boundedJsonObject(value) {
|
|
@@ -16497,7 +16557,7 @@ const providerCapabilityAdapterSchema = z.union([landingPageVisitsCapabilitySche
|
|
|
16497
16557
|
const providerActionAdapterSchema = z.custom((value) => {
|
|
16498
16558
|
if (typeof value !== "object" || value === null) return false;
|
|
16499
16559
|
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 <=
|
|
16560
|
+
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
16561
|
}, "Invalid provider action.");
|
|
16502
16562
|
const providerPackageManifestSchema = z.object({
|
|
16503
16563
|
apiVersion: z.literal(1),
|
|
@@ -16928,8 +16988,8 @@ async function loadInstalledProviderExtensions(options = {}) {
|
|
|
16928
16988
|
//#endregion
|
|
16929
16989
|
//#region packages/core/src/provider-extensions/runtime.ts
|
|
16930
16990
|
const DEFAULT_MAX_REQUESTS = 8;
|
|
16931
|
-
const MAX_RESPONSE_BYTES$1 =
|
|
16932
|
-
const MAX_REQUEST_BODY_BYTES =
|
|
16991
|
+
const MAX_RESPONSE_BYTES$1 = 10485760;
|
|
16992
|
+
const MAX_REQUEST_BODY_BYTES = 65536;
|
|
16933
16993
|
const TIMEOUT_MS$3 = 15e3;
|
|
16934
16994
|
function normalizeProviderExtensionAccount(provider, account) {
|
|
16935
16995
|
const fields = provider.connection.fields.filter((field) => field.kind === "account");
|
|
@@ -16999,7 +17059,7 @@ async function verifyProviderExtension(input) {
|
|
|
16999
17059
|
}
|
|
17000
17060
|
//#endregion
|
|
17001
17061
|
//#region packages/core/src/provider-extensions/analytics.ts
|
|
17002
|
-
const CACHE_TTL_MS$1 =
|
|
17062
|
+
const CACHE_TTL_MS$1 = 864e5;
|
|
17003
17063
|
const landingPageResultSchema = z.object({
|
|
17004
17064
|
metric: z.literal("landing-page-visits"),
|
|
17005
17065
|
rows: z.array(z.object({
|
|
@@ -17601,16 +17661,18 @@ async function crawlSite(input, dependencies) {
|
|
|
17601
17661
|
return;
|
|
17602
17662
|
}
|
|
17603
17663
|
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
|
-
|
|
17664
|
+
if (!queueSafetyDeferredUrls.has(normalized)) {
|
|
17665
|
+
if (queueSafetyDeferredUrls.size < maxTrackedQueueSafetyUrls) {
|
|
17666
|
+
queueSafetyDeferredUrls.add(normalized);
|
|
17667
|
+
queueSafetySkippedUrls = queueSafetyDeferredUrls.size;
|
|
17668
|
+
recordSkip("queue-safety-limit");
|
|
17669
|
+
emitStatus("url_skipped", {
|
|
17670
|
+
url: normalized,
|
|
17671
|
+
depth,
|
|
17672
|
+
reason: "queue_safety_limit"
|
|
17673
|
+
});
|
|
17674
|
+
} else queueSafetyInventoryCapped = true;
|
|
17675
|
+
}
|
|
17614
17676
|
return;
|
|
17615
17677
|
}
|
|
17616
17678
|
discovered.add(normalized);
|
|
@@ -17716,12 +17778,14 @@ async function crawlSite(input, dependencies) {
|
|
|
17716
17778
|
extraction: "not-applicable"
|
|
17717
17779
|
});
|
|
17718
17780
|
requests.push(request);
|
|
17719
|
-
if (healthStrategy)
|
|
17720
|
-
|
|
17721
|
-
|
|
17722
|
-
|
|
17723
|
-
|
|
17724
|
-
|
|
17781
|
+
if (healthStrategy) {
|
|
17782
|
+
if (request.outcome === "response" && request.status < 400 && !request.accessBlock) {
|
|
17783
|
+
healthyProbeStreak += 1;
|
|
17784
|
+
activeConcurrency = Math.min(config.concurrency, 1 + Math.floor(healthyProbeStreak / 20));
|
|
17785
|
+
} else {
|
|
17786
|
+
healthyProbeStreak = 0;
|
|
17787
|
+
activeConcurrency = 1;
|
|
17788
|
+
}
|
|
17725
17789
|
}
|
|
17726
17790
|
checkMemoryPressure();
|
|
17727
17791
|
if (request.outcome === "skipped") {
|
|
@@ -17854,14 +17918,16 @@ async function crawlSite(input, dependencies) {
|
|
|
17854
17918
|
depth: task.depth,
|
|
17855
17919
|
statusCode: page.status
|
|
17856
17920
|
});
|
|
17857
|
-
if (followLinks && task.depth < config.maxDepth)
|
|
17858
|
-
|
|
17859
|
-
|
|
17860
|
-
|
|
17861
|
-
|
|
17921
|
+
if (followLinks && task.depth < config.maxDepth) {
|
|
17922
|
+
if (memoryLimited) for (const value of result.urls) {
|
|
17923
|
+
const normalized = normalizeUrl$1(value, finalUrl);
|
|
17924
|
+
if (normalized && sameOrigin(normalized, origin) && !isLikelyAsset(normalized) && passesFilters(normalized, config.include, config.exclude) && !visited.has(normalized) && !queue.has(normalized)) {
|
|
17925
|
+
discovered.add(normalized);
|
|
17926
|
+
memoryDeferredUrls.add(normalized);
|
|
17927
|
+
}
|
|
17862
17928
|
}
|
|
17929
|
+
else for (const url of result.urls) enqueue(url, task.depth + 1);
|
|
17863
17930
|
}
|
|
17864
|
-
else for (const url of result.urls) enqueue(url, task.depth + 1);
|
|
17865
17931
|
}
|
|
17866
17932
|
const memoryPressureSkippedUrls = memoryLimited ? queue.size + memoryDeferredUrls.size : 0;
|
|
17867
17933
|
const stoppedForMemory = memoryPressureSkippedUrls > 0;
|
|
@@ -18558,7 +18624,7 @@ function analyzeCtrUnderperformersFromRows(input) {
|
|
|
18558
18624
|
const MAX_GSC_ROWS$3 = 1e5;
|
|
18559
18625
|
const DEFAULT_DAYS$3 = 28;
|
|
18560
18626
|
const MAX_DAYS$2 = 548;
|
|
18561
|
-
function plural$
|
|
18627
|
+
function plural$7(count, singular, pluralLabel = `${singular}s`) {
|
|
18562
18628
|
return count === 1 ? singular : pluralLabel;
|
|
18563
18629
|
}
|
|
18564
18630
|
async function ctrUnderperformersReportWith(input, dependencies) {
|
|
@@ -18661,11 +18727,11 @@ async function ctrUnderperformersReportWith(input, dependencies) {
|
|
|
18661
18727
|
minImpressions,
|
|
18662
18728
|
limit,
|
|
18663
18729
|
brandFiltering: input.includeBrand ? "included" : "excluded",
|
|
18664
|
-
verdict: top ? `${selection.eligibleUnderperformers} CTR ${plural$
|
|
18730
|
+
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
18731
|
},
|
|
18666
18732
|
items,
|
|
18667
18733
|
caveats: [
|
|
18668
|
-
`Date window: ${range.startDate} to ${range.endDate} (${days} ${plural$
|
|
18734
|
+
`Date window: ${range.startDate} to ${range.endDate} (${days} ${plural$7(days, "day")}), using final GSC data where available.`,
|
|
18669
18735
|
`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
18736
|
`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
18737
|
minImpressionsCaveat({
|
|
@@ -18673,13 +18739,13 @@ async function ctrUnderperformersReportWith(input, dependencies) {
|
|
|
18673
18739
|
belowMinimumRows: selection.belowMinimumRows
|
|
18674
18740
|
}),
|
|
18675
18741
|
"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$
|
|
18742
|
+
...selection.fragmentRows ? [`${selection.fragmentRows} source ${plural$7(selection.fragmentRows, "row")} with a #fragment URL ${selection.fragmentRows === 1 ? "was" : "were"} merged into the base URL.`] : [],
|
|
18743
|
+
...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
18744
|
"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$
|
|
18745
|
+
...selection.invalidRows ? [`${selection.invalidRows} invalid provider ${plural$7(selection.invalidRows, "row")} ${selection.invalidRows === 1 ? "was" : "were"} excluded, so this report is partial.`] : [],
|
|
18746
|
+
...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
18747
|
...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$
|
|
18748
|
+
...selection.limitedUnderperformers ? [`${selection.limitedUnderperformers} eligible ${plural$7(selection.limitedUnderperformers, "underperformer")} ${selection.limitedUnderperformers === 1 ? "was" : "were"} omitted by the output limit.`] : []
|
|
18683
18749
|
],
|
|
18684
18750
|
recommendations: top ? [
|
|
18685
18751
|
`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 +19091,7 @@ function analyzeSegmentRows(input) {
|
|
|
19025
19091
|
}
|
|
19026
19092
|
//#endregion
|
|
19027
19093
|
//#region packages/core/src/analyze/segment-impact-input.ts
|
|
19028
|
-
const DAY_MS = 864e5;
|
|
19094
|
+
const DAY_MS$1 = 864e5;
|
|
19029
19095
|
const MAX_WINDOW_DAYS = 240;
|
|
19030
19096
|
function subtractDays(value, days) {
|
|
19031
19097
|
const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
@@ -19033,7 +19099,7 @@ function subtractDays(value, days) {
|
|
|
19033
19099
|
return date.toISOString().slice(0, 10);
|
|
19034
19100
|
}
|
|
19035
19101
|
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;
|
|
19102
|
+
return Math.floor((Date.parse(`${range.endDate}T00:00:00.000Z`) - Date.parse(`${range.startDate}T00:00:00.000Z`)) / DAY_MS$1) + 1;
|
|
19037
19103
|
}
|
|
19038
19104
|
function segmentComparisonRange(after, compareDays) {
|
|
19039
19105
|
const endDate = subtractDays(after.startDate, 1);
|
|
@@ -19301,9 +19367,10 @@ function delimiterCount(line, delimiter) {
|
|
|
19301
19367
|
let quoted = false;
|
|
19302
19368
|
for (let index = 0; index < line.length; index += 1) {
|
|
19303
19369
|
const character = line[index];
|
|
19304
|
-
if (character === "\"")
|
|
19305
|
-
|
|
19306
|
-
|
|
19370
|
+
if (character === "\"") {
|
|
19371
|
+
if (quoted && line[index + 1] === "\"") index += 1;
|
|
19372
|
+
else quoted = !quoted;
|
|
19373
|
+
} else if (!quoted && character === delimiter) count += 1;
|
|
19307
19374
|
}
|
|
19308
19375
|
return count;
|
|
19309
19376
|
}
|
|
@@ -19330,9 +19397,10 @@ function decodeText(decoder, chunk, stream, label) {
|
|
|
19330
19397
|
async function detectCsvDelimiter(path) {
|
|
19331
19398
|
const handle = await open$1(path, "r");
|
|
19332
19399
|
try {
|
|
19333
|
-
const buffer = Buffer.alloc(
|
|
19400
|
+
const buffer = Buffer.alloc(65536);
|
|
19334
19401
|
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
19335
|
-
const
|
|
19402
|
+
const encoding = await detectTextEncoding(path);
|
|
19403
|
+
const firstRecord = new TextDecoder$1(encoding).decode(buffer.subarray(0, bytesRead)).split(/\r?\n/u, 1)[0]?.replace(/^\uFEFF/u, "");
|
|
19336
19404
|
if (!firstRecord) return ",";
|
|
19337
19405
|
const candidates = [
|
|
19338
19406
|
",",
|
|
@@ -19371,9 +19439,10 @@ async function* csvRecords(input) {
|
|
|
19371
19439
|
quoted = false;
|
|
19372
19440
|
pendingQuote = false;
|
|
19373
19441
|
}
|
|
19374
|
-
if (quoted)
|
|
19375
|
-
|
|
19376
|
-
|
|
19442
|
+
if (quoted) {
|
|
19443
|
+
if (character === "\"") pendingQuote = true;
|
|
19444
|
+
else field += character;
|
|
19445
|
+
} else if (character === "\"" && field.length === 0) quoted = true;
|
|
19377
19446
|
else if (character === delimiter) {
|
|
19378
19447
|
record.push(field);
|
|
19379
19448
|
field = "";
|
|
@@ -22452,7 +22521,7 @@ const INCIDENTS_URL = "https://status.search.google.com/incidents.json";
|
|
|
22452
22521
|
async function fetchSearchStatusIncidents() {
|
|
22453
22522
|
const response = await fetch$1(INCIDENTS_URL, { signal: AbortSignal.timeout(8e3) });
|
|
22454
22523
|
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:
|
|
22524
|
+
return readJson(response, "Google Search Status", { maxBytes: 10485760 });
|
|
22456
22525
|
}
|
|
22457
22526
|
async function listSearchUpdates(input = {}) {
|
|
22458
22527
|
return searchUpdatesFromIncidents(withRankingHistory(await fetchSearchStatusIncidents()), input);
|
|
@@ -22514,7 +22583,7 @@ function anomalyEvidence(anomaly) {
|
|
|
22514
22583
|
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
22584
|
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
22585
|
}
|
|
22517
|
-
function plural$
|
|
22586
|
+
function plural$6(count, singular, pluralLabel = `${singular}s`) {
|
|
22518
22587
|
return count === 1 ? singular : pluralLabel;
|
|
22519
22588
|
}
|
|
22520
22589
|
function strongestSignal(anomalies) {
|
|
@@ -22570,13 +22639,13 @@ function interpretUpdateCorrelation(input) {
|
|
|
22570
22639
|
summary,
|
|
22571
22640
|
evidence: [
|
|
22572
22641
|
...input.anomalies.map(anomalyEvidence),
|
|
22573
|
-
hasUpdate ? `${input.overlappingUpdates.length} official Google Ranking update ${plural$
|
|
22642
|
+
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
22643
|
largeMovement ? "Both clicks and impressions moved sharply in the same direction." : "Clicks and impressions did not both meet the sharp-movement evidence threshold.",
|
|
22575
22644
|
hasConfounders ? `Known overlapping site changes: ${confounders.map((confounder) => confounder.title).join("; ")}.` : "No overlapping site changes were supplied."
|
|
22576
22645
|
],
|
|
22577
22646
|
caveats: [
|
|
22578
22647
|
`GSC window: ${input.days} days; recent comparison window: ${input.recentDays} days.`,
|
|
22579
|
-
`Update overlap padding: ${input.paddingDays} ${plural$
|
|
22648
|
+
`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
22649
|
input.refresh ? "Data freshness: cache bypassed for GSC data." : "Data freshness: local GSC cache may have been used.",
|
|
22581
22650
|
"Source: official Google Search Status Dashboard Ranking incidents only; third-party volatility and unconfirmed chatter are not included.",
|
|
22582
22651
|
"An update-window overlap cannot establish causation, even when movement is large and no known site change was recorded.",
|
|
@@ -22726,7 +22795,7 @@ function analyzeTrafficAnomalyRows(input) {
|
|
|
22726
22795
|
const comparisonStart = shiftIsoDate(input.endDate, -(input.recentDays - 1));
|
|
22727
22796
|
const baselineEnd = shiftIsoDate(comparisonStart, -1);
|
|
22728
22797
|
const baselineExpectedDays = calendarDays(input.startDate, baselineEnd);
|
|
22729
|
-
if (comparisonStart < input.startDate || baselineExpectedDays < 14) throw new SeoError("INSUFFICIENT_DATA",
|
|
22798
|
+
if (comparisonStart < input.startDate || baselineExpectedDays < 14) throw new SeoError("INSUFFICIENT_DATA", `The date range needs at least ${input.recentDays + 14} days: 14 baseline days before the last ${input.recentDays}. Send a longer range, or leave the dates out for the default.`);
|
|
22730
22799
|
const aggregated = aggregateDailyRows({
|
|
22731
22800
|
rows: input.rows,
|
|
22732
22801
|
startDate: input.startDate,
|
|
@@ -22734,7 +22803,7 @@ function analyzeTrafficAnomalyRows(input) {
|
|
|
22734
22803
|
});
|
|
22735
22804
|
const baselineRows = aggregated.rows.filter((row) => (row.keys[0] ?? "") <= baselineEnd);
|
|
22736
22805
|
const comparisonRows = aggregated.rows.filter((row) => (row.keys[0] ?? "") >= comparisonStart);
|
|
22737
|
-
if (baselineRows.length < 14 || comparisonRows.length < 3) throw new SeoError("INSUFFICIENT_DATA", `Not enough
|
|
22806
|
+
if (baselineRows.length < 14 || comparisonRows.length < 3) throw new SeoError("INSUFFICIENT_DATA", `Not enough daily Search Console data to look for a drop: ${baselineRows.length} baseline days and ${comparisonRows.length} comparison days, and it needs 14 and 3. The site may be new or get too few clicks for daily rows.`);
|
|
22738
22807
|
const metrics = ["clicks", "impressions"];
|
|
22739
22808
|
const stepCaveats = [];
|
|
22740
22809
|
const anomalies = metrics.map((metric) => {
|
|
@@ -22744,7 +22813,7 @@ function analyzeTrafficAnomalyRows(input) {
|
|
|
22744
22813
|
if (step && changeDate) {
|
|
22745
22814
|
const previousDate = aggregated.rows[step.index - 1]?.keys[0];
|
|
22746
22815
|
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$
|
|
22816
|
+
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
22817
|
return compareSegments({
|
|
22749
22818
|
site: input.site,
|
|
22750
22819
|
metric,
|
|
@@ -22775,9 +22844,9 @@ function analyzeTrafficAnomalyRows(input) {
|
|
|
22775
22844
|
const comparisonMissingDays = input.recentDays - comparisonRows.length;
|
|
22776
22845
|
const missingDays = expectedDays - aggregated.rows.length;
|
|
22777
22846
|
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$
|
|
22847
|
+
...missingDays > 0 ? [`${missingDays} requested calendar ${plural$6(missingDays, "day")} had no returned date aggregate and ${missingDays === 1 ? "was" : "were"} not filled with zeros.`] : [],
|
|
22848
|
+
...aggregated.invalidRows > 0 ? [`${aggregated.invalidRows} invalid provider ${plural$6(aggregated.invalidRows, "row")} ${aggregated.invalidRows === 1 ? "was" : "were"} excluded.`] : [],
|
|
22849
|
+
...aggregated.duplicateRows > 0 ? [`${aggregated.duplicateRows} duplicate date ${plural$6(aggregated.duplicateRows, "row")} ${aggregated.duplicateRows === 1 ? "was" : "were"} aggregated before analysis.`] : [],
|
|
22781
22850
|
...stepCaveats
|
|
22782
22851
|
];
|
|
22783
22852
|
return {
|
|
@@ -23461,7 +23530,7 @@ var DecisionModelError = class extends Error {
|
|
|
23461
23530
|
const TYPESAFE_URL = "https://api.typesafe.ai/v1/systemone";
|
|
23462
23531
|
const JEV_PRICE_PER_MILLION = .042;
|
|
23463
23532
|
const REQUEST_TIMEOUT_MS = 6e4;
|
|
23464
|
-
const MAX_RESPONSE_BYTES =
|
|
23533
|
+
const MAX_RESPONSE_BYTES = 1048576;
|
|
23465
23534
|
function retryAfter(response) {
|
|
23466
23535
|
const value = Number(response.headers.get("retry-after"));
|
|
23467
23536
|
return Number.isFinite(value) && value > 0 ? Math.min(value, 30) * 1e3 : void 0;
|
|
@@ -23799,7 +23868,7 @@ function keyTerms(page) {
|
|
|
23799
23868
|
const title = page.title.replace(/\s+[|\u2013\u2014-]\s+[^|\u2013\u2014]*$/u, "");
|
|
23800
23869
|
for (const term of terms(page.description)) key.set(term, 1);
|
|
23801
23870
|
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);
|
|
23871
|
+
for (const term of terms(siteName$1(page.url))) key.delete(term);
|
|
23803
23872
|
return key;
|
|
23804
23873
|
}
|
|
23805
23874
|
function lastSegmentWords(url) {
|
|
@@ -23809,7 +23878,7 @@ function lastSegmentWords(url) {
|
|
|
23809
23878
|
return "";
|
|
23810
23879
|
}
|
|
23811
23880
|
}
|
|
23812
|
-
function siteName(url) {
|
|
23881
|
+
function siteName$1(url) {
|
|
23813
23882
|
try {
|
|
23814
23883
|
return new URL(url).hostname.replace(/^www\./u, "").split(".")[0] ?? "";
|
|
23815
23884
|
} catch {
|
|
@@ -24687,15 +24756,15 @@ function internalLinksReportRange(input, fallbackDays, now) {
|
|
|
24687
24756
|
days: Math.round((Date.parse(`${endDate}T00:00:00Z`) - Date.parse(`${startDate}T00:00:00Z`)) / DAY$1) + 1
|
|
24688
24757
|
};
|
|
24689
24758
|
}
|
|
24690
|
-
const plural$
|
|
24759
|
+
const plural$5 = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
24691
24760
|
function internalLinksVerdict(report) {
|
|
24692
24761
|
const { counts } = report;
|
|
24693
24762
|
if (report.dataStatus === "blocked") return "robots.txt does not allow this crawler to read the site, so nothing was checked.";
|
|
24694
24763
|
if (report.dataStatus === "empty") return "No HTML page could be crawled, so there is nothing to link.";
|
|
24695
24764
|
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
24765
|
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$
|
|
24766
|
+
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)`;
|
|
24767
|
+
const scope = report.mode === "url" ? `to ${report.target?.url}` : `across ${plural$5(counts.pagesCrawled, "page")}`;
|
|
24699
24768
|
const capped = report.crawl.capReached ? ` The crawl stopped at its limit of ${report.crawl.pageLimit} pages, so pages beyond it were not checked.` : "";
|
|
24700
24769
|
return `${judged} ${scope}${counts.heldBack ? `; ${counts.heldBack} held back by the evidence floors` : ""}.${capped}`;
|
|
24701
24770
|
}
|
|
@@ -26500,7 +26569,7 @@ async function runLighthouse(input) {
|
|
|
26500
26569
|
], {
|
|
26501
26570
|
env: lighthouseEnvironment(),
|
|
26502
26571
|
timeout: input.timeoutMs ?? 12e4,
|
|
26503
|
-
maxBuffer:
|
|
26572
|
+
maxBuffer: 31457280
|
|
26504
26573
|
});
|
|
26505
26574
|
const jsonStart = stdout.indexOf("{");
|
|
26506
26575
|
if (jsonStart < 0) throw new LighthouseRunError("invalid_result");
|
|
@@ -27136,14 +27205,14 @@ function* retainedQueryClusterRows(input) {
|
|
|
27136
27205
|
};
|
|
27137
27206
|
}
|
|
27138
27207
|
}
|
|
27139
|
-
function plural$
|
|
27208
|
+
function plural$4(count, singular, pluralLabel = `${singular}s`) {
|
|
27140
27209
|
return count === 1 ? singular : pluralLabel;
|
|
27141
27210
|
}
|
|
27142
27211
|
function reportVerdict(input) {
|
|
27143
27212
|
if (!input.clusters.length) return "No query clusters were generated for this scope.";
|
|
27144
27213
|
if (input.highOpportunityClusters > 0) {
|
|
27145
27214
|
const verb = input.highOpportunityClusters === 1 ? "has" : "have";
|
|
27146
|
-
return `${input.highOpportunityClusters} ${plural$
|
|
27215
|
+
return `${input.highOpportunityClusters} ${plural$4(input.highOpportunityClusters, "cluster")} ${verb} enough demand and weak enough performance to review first.`;
|
|
27147
27216
|
}
|
|
27148
27217
|
return "No obvious high-opportunity clusters were found. Use this report for content structure and intent mapping rather than urgent fixes.";
|
|
27149
27218
|
}
|
|
@@ -27284,7 +27353,7 @@ function movementLine(report) {
|
|
|
27284
27353
|
}
|
|
27285
27354
|
function countPhrase(count, singular, pluralLabel = `${singular}s`) {
|
|
27286
27355
|
if (count === 0) return `no ${pluralLabel}`;
|
|
27287
|
-
return `${count} ${plural$
|
|
27356
|
+
return `${count} ${plural$3(count, singular, pluralLabel)}`;
|
|
27288
27357
|
}
|
|
27289
27358
|
function headlineLine(report) {
|
|
27290
27359
|
const parts = [
|
|
@@ -27339,7 +27408,7 @@ function decayDiagnosisLabel(diagnosis) {
|
|
|
27339
27408
|
if (diagnosis === "lost_clicks") return "clicks dropped without one dominant signal";
|
|
27340
27409
|
return diagnosis.replaceAll("_", " ");
|
|
27341
27410
|
}
|
|
27342
|
-
function plural$
|
|
27411
|
+
function plural$3(count, singular, pluralLabel = `${singular}s`) {
|
|
27343
27412
|
return count === 1 ? singular : pluralLabel;
|
|
27344
27413
|
}
|
|
27345
27414
|
function decayClusterLine(report) {
|
|
@@ -27347,7 +27416,7 @@ function decayClusterLine(report) {
|
|
|
27347
27416
|
if (!group) return "No material decay cluster stood out.";
|
|
27348
27417
|
const diagnosis = decayDiagnosisLabel(group.diagnosis);
|
|
27349
27418
|
const samples = group.sampleQueries.slice(0, 2).join("; ");
|
|
27350
|
-
return `Top decay cluster: ${group.count} ${plural$
|
|
27419
|
+
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
27420
|
}
|
|
27352
27421
|
function cannibalSuppressionLine(report) {
|
|
27353
27422
|
const suppressed = report.cannibalization.selection.suppressedQueries;
|
|
@@ -27365,21 +27434,21 @@ function contentOpportunityBullets(report) {
|
|
|
27365
27434
|
if (unavailableSections.length) bullets.push(`Could not assess ${unavailableSections.join(", ")} opportunities in this run; see report caveats.`);
|
|
27366
27435
|
if (report.summary.decayItems) {
|
|
27367
27436
|
const top = report.decay.items[0];
|
|
27368
|
-
bullets.push(top ? `${report.summary.decayItems} observed retained query/page ${plural$
|
|
27437
|
+
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
27438
|
bullets.push(decayClusterLine(report));
|
|
27370
27439
|
}
|
|
27371
27440
|
if (report.summary.cannibalItems) {
|
|
27372
27441
|
const top = report.cannibalization.items[0];
|
|
27373
|
-
bullets.push(top ? `${report.summary.cannibalItems} multi-URL query ${plural$
|
|
27442
|
+
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
27443
|
}
|
|
27375
27444
|
if (report.cannibalization.selection.suppressedQueries) bullets.push(cannibalSuppressionLine(report));
|
|
27376
27445
|
if (report.summary.strikingDistanceItems) {
|
|
27377
27446
|
const top = report.strikingDistance.items[0];
|
|
27378
|
-
bullets.push(top ? `${countLabel(report.summary.strikingDistanceItems, "query/page candidate")} ${plural$
|
|
27447
|
+
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
27448
|
}
|
|
27380
27449
|
if (!skippedReason(report, "quick-win opportunities") && report.summary.quickWinItems) {
|
|
27381
27450
|
const top = report.quickWins.items[0];
|
|
27382
|
-
bullets.push(top ? `${report.summary.quickWinItems} retained query/page ${plural$
|
|
27451
|
+
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
27452
|
}
|
|
27384
27453
|
const templateLine = templateOpportunityLine(report);
|
|
27385
27454
|
if (!templateLine.startsWith("No dominant")) bullets.push(templateLine);
|
|
@@ -29062,7 +29131,7 @@ async function clickyDomain(site) {
|
|
|
29062
29131
|
}
|
|
29063
29132
|
//#endregion
|
|
29064
29133
|
//#region packages/core/src/analyze/ai-queries.ts
|
|
29065
|
-
const RULES
|
|
29134
|
+
const RULES = [
|
|
29066
29135
|
["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
29136
|
["tracker", "\\. ?my location is |\\?\\. ?my( |$)|^evaluate the .+ on "],
|
|
29068
29137
|
["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 +29149,8 @@ const AI_QUERY_KINDS = {
|
|
|
29080
29149
|
chat: "chat phrasing, such as \"can you\" or \"should i\"",
|
|
29081
29150
|
long: "ten or more words with no other sign"
|
|
29082
29151
|
};
|
|
29083
|
-
const MATCHERS = RULES
|
|
29084
|
-
const AI_QUERY_PATTERN = RULES
|
|
29152
|
+
const MATCHERS = RULES.map(([kind, pattern]) => [kind, new RegExp(pattern)]);
|
|
29153
|
+
const AI_QUERY_PATTERN = RULES.map(([, pattern]) => `(?:${pattern})`).join("|");
|
|
29085
29154
|
const NOISE = [
|
|
29086
29155
|
["spaced_letters", /(^|\s)(\S\s){3,}\S(\s|$)/u],
|
|
29087
29156
|
["broken_encoding", /^(яю|ÿþ|þÿ)|&#[0-9]+;?|&[a-z]+;|[âÃÂ][\u0080-\u00bf€™œ“”]|\ufffd/u],
|
|
@@ -29655,15 +29724,15 @@ async function runRows(input, deps) {
|
|
|
29655
29724
|
};
|
|
29656
29725
|
}
|
|
29657
29726
|
const QUESTION_DAYS = 90;
|
|
29658
|
-
const list = (value) => Array.isArray(value) ? value : [];
|
|
29727
|
+
const list$1 = (value) => Array.isArray(value) ? value : [];
|
|
29659
29728
|
const urls = (items) => items.map((item) => item.url).filter((url) => typeof url === "string");
|
|
29660
29729
|
const overview = (result) => {
|
|
29661
|
-
const block = list(result.items).find((item) => item.type === "ai_overview");
|
|
29730
|
+
const block = list$1(result.items).find((item) => item.type === "ai_overview");
|
|
29662
29731
|
if (!block) return null;
|
|
29663
|
-
const parts = list(block.items);
|
|
29732
|
+
const parts = list$1(block.items);
|
|
29664
29733
|
return {
|
|
29665
29734
|
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))]),
|
|
29735
|
+
sources: urls([...list$1(block.references), ...parts.flatMap((part) => list$1(part.references))]),
|
|
29667
29736
|
model: null
|
|
29668
29737
|
};
|
|
29669
29738
|
};
|
|
@@ -29679,7 +29748,7 @@ const ENGINES = [
|
|
|
29679
29748
|
}),
|
|
29680
29749
|
shown: (result) => {
|
|
29681
29750
|
const markdown = typeof result.markdown === "string" ? result.markdown : "";
|
|
29682
|
-
const sources = urls(list(result.sources));
|
|
29751
|
+
const sources = urls(list$1(result.sources));
|
|
29683
29752
|
if (!markdown && !sources.length) return null;
|
|
29684
29753
|
return {
|
|
29685
29754
|
markdown,
|
|
@@ -30263,7 +30332,7 @@ async function analyticsBeside(access, site, range, before, pages) {
|
|
|
30263
30332
|
//#region packages/core/src/commands/audit.ts
|
|
30264
30333
|
const AUDIT_SECTION = "weekly-audit";
|
|
30265
30334
|
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
|
|
30335
|
+
const AUDIT_RULES = {
|
|
30267
30336
|
link_to_error: "Internal links to a page that returns an error",
|
|
30268
30337
|
link_to_redirect: "Internal links to a redirect",
|
|
30269
30338
|
link_failed: "Internal links to a page that could not be fetched",
|
|
@@ -30420,7 +30489,7 @@ function runAuditReport(input, snapshot, call) {
|
|
|
30420
30489
|
text: [
|
|
30421
30490
|
head,
|
|
30422
30491
|
indexText,
|
|
30423
|
-
byRule.length ? ["finding | open | what it is", ...byRule.map(([rule, count]) => `${rule} | ${count} | ${
|
|
30492
|
+
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
30493
|
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
30494
|
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
30495
|
`More: ${call("report", {
|
|
@@ -30445,9 +30514,9 @@ function auditPreview(snapshot) {
|
|
|
30445
30514
|
const index = snapshot.index_changes;
|
|
30446
30515
|
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
30516
|
}
|
|
30448
|
-
const WEEK_MS =
|
|
30517
|
+
const WEEK_MS = 6048e5;
|
|
30449
30518
|
const day = (ms) => ms === null ? "" : new Date(ms).toISOString().slice(0, 10);
|
|
30450
|
-
const count$
|
|
30519
|
+
const count$2 = (value) => value.toLocaleString("en-US");
|
|
30451
30520
|
function relative$1(value, origin) {
|
|
30452
30521
|
if (!value) return "";
|
|
30453
30522
|
return origin && value.startsWith(origin) ? value.slice(origin.length) || "/" : value;
|
|
@@ -30468,7 +30537,7 @@ function rowsTable(rows, checks) {
|
|
|
30468
30537
|
changed(row) ? row.prev_coverage ?? "" : "",
|
|
30469
30538
|
changed(row) ? day(row.changed_at) : "",
|
|
30470
30539
|
day(row.checked_at),
|
|
30471
|
-
`${count$
|
|
30540
|
+
`${count$2(row.clicks)} (${count$2(row.previous_clicks)})${row.lost ? " lost" : ""}`,
|
|
30472
30541
|
row.in_sitemap ? "yes" : "no",
|
|
30473
30542
|
row.google_canonical && row.google_canonical !== row.url ? relative$1(row.google_canonical, checks.origin) : "",
|
|
30474
30543
|
row.error ?? ""
|
|
@@ -30476,12 +30545,12 @@ function rowsTable(rows, checks) {
|
|
|
30476
30545
|
}
|
|
30477
30546
|
function head(site, checks) {
|
|
30478
30547
|
const { queued } = checks;
|
|
30479
|
-
const quota = checks.properties.map((item) => `${item.property} (${count$
|
|
30548
|
+
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
30549
|
return [
|
|
30481
30550
|
`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$
|
|
30551
|
+
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,
|
|
30552
|
+
`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.`,
|
|
30553
|
+
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
30554
|
quota ? `Checked through ${quota}.` : null,
|
|
30486
30555
|
checks.stopped ? `Checks stopped early: ${checks.stopped}` : null,
|
|
30487
30556
|
"A URL without a result has none here, so this is not an all-clear for it."
|
|
@@ -30490,9 +30559,9 @@ function head(site, checks) {
|
|
|
30490
30559
|
function suggestionsText(checks) {
|
|
30491
30560
|
if (!checks.suggestions.length) return null;
|
|
30492
30561
|
return [
|
|
30493
|
-
`The sitemap has more URLs than these properties can check every 7 days. Each Search Console property gets its own ${count$
|
|
30562
|
+
`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
30563
|
"property to add | sitemap URLs in it | more URLs checked a week",
|
|
30495
|
-
...checks.suggestions.map((item) => `${item.property} | ${count$
|
|
30564
|
+
...checks.suggestions.map((item) => `${item.property} | ${count$2(item.urls)} | ${count$2(item.gain)}`),
|
|
30496
30565
|
"The next day the checks find a new property in Search Console and use its quota for the URLs in that folder."
|
|
30497
30566
|
].join("\n");
|
|
30498
30567
|
}
|
|
@@ -30507,7 +30576,7 @@ function runAuditChecks(input, checks, call) {
|
|
|
30507
30576
|
data: checks
|
|
30508
30577
|
};
|
|
30509
30578
|
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$
|
|
30579
|
+
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
30580
|
data: checks
|
|
30512
30581
|
};
|
|
30513
30582
|
const more = (offset) => input.paged ? `More: ${call("index", {
|
|
@@ -30531,7 +30600,7 @@ function runAuditChecks(input, checks, call) {
|
|
|
30531
30600
|
const reasons = checks.reasons.length ? [
|
|
30532
30601
|
"Google's reason at each URL's latest check:",
|
|
30533
30602
|
"reason | verdict | urls | changed to it this week",
|
|
30534
|
-
...checks.reasons.map((item) => `${item.coverage ?? ""} | ${item.verdict ?? ""} | ${count$
|
|
30603
|
+
...checks.reasons.map((item) => `${item.coverage ?? ""} | ${item.verdict ?? ""} | ${count$2(item.urls)} | ${count$2(item.changed_this_week)}`)
|
|
30535
30604
|
].join("\n") : "No URL has a result yet.";
|
|
30536
30605
|
const shown = checks.rows.slice(0, 20);
|
|
30537
30606
|
return {
|
|
@@ -30941,13 +31010,13 @@ function clickyRange(input) {
|
|
|
30941
31010
|
endDate: input.endDate
|
|
30942
31011
|
};
|
|
30943
31012
|
}
|
|
30944
|
-
const count = (item) => {
|
|
31013
|
+
const count$1 = (item) => {
|
|
30945
31014
|
const value = Number(item.value);
|
|
30946
31015
|
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
30947
31016
|
};
|
|
30948
31017
|
const totals = (items) => items.map((item) => ({
|
|
30949
31018
|
name: item.title ?? "",
|
|
30950
|
-
visits: count(item)
|
|
31019
|
+
visits: count$1(item)
|
|
30951
31020
|
})).filter((item) => Boolean(item.name) && item.visits !== null);
|
|
30952
31021
|
const line = (items) => {
|
|
30953
31022
|
const top = items.slice(0, 10).map((item) => `${item.name} ${item.visits}`);
|
|
@@ -30978,7 +31047,7 @@ async function runClickyRows(input, keys, options) {
|
|
|
30978
31047
|
const entrances = stats["pages-entrance"] ?? [];
|
|
30979
31048
|
const visitsByPage = /* @__PURE__ */ new Map();
|
|
30980
31049
|
for (const item of entrances) {
|
|
30981
|
-
const visits = count(item);
|
|
31050
|
+
const visits = count$1(item);
|
|
30982
31051
|
if (visits === null || !item.url) continue;
|
|
30983
31052
|
let page;
|
|
30984
31053
|
try {
|
|
@@ -31029,7 +31098,7 @@ async function runClickyRows(input, keys, options) {
|
|
|
31029
31098
|
const DR_ATTRIBUTION = "domain_rating: Domain Rating by Ahrefs (https://ahrefs.com/).";
|
|
31030
31099
|
const ENDPOINT = "https://api.ahrefs.com/v3/public/domain-rating-free";
|
|
31031
31100
|
const MAX_DOMAINS = 25;
|
|
31032
|
-
const TTL_SECONDS =
|
|
31101
|
+
const TTL_SECONDS = 86400;
|
|
31033
31102
|
const DOMAIN_FIELDS = [
|
|
31034
31103
|
"domain",
|
|
31035
31104
|
"competitor_domain",
|
|
@@ -31136,14 +31205,14 @@ const samePath = (a, b) => {
|
|
|
31136
31205
|
return from.pathname === to.pathname && from.search === to.search;
|
|
31137
31206
|
};
|
|
31138
31207
|
const authPath = (url) => AUTH_SEGMENTS.has(new URL(url).pathname.split("/")[1]?.toLowerCase() ?? "");
|
|
31139
|
-
const plural$
|
|
31208
|
+
const plural$2 = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
31140
31209
|
function labelOf(e) {
|
|
31141
31210
|
const { url, status, live } = e;
|
|
31142
31211
|
const { first, hops, last } = live;
|
|
31143
31212
|
const indexed = status.verdict === "PASS";
|
|
31144
31213
|
const coverage = status.coverage ?? "";
|
|
31145
31214
|
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$
|
|
31215
|
+
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
31216
|
if (first.error) return indexed ? {
|
|
31148
31217
|
label: "indexed",
|
|
31149
31218
|
why: `Google indexes it. The live check failed (${first.error}).`
|
|
@@ -31425,20 +31494,20 @@ function indexingText(site, run, page, origin, askMore, now) {
|
|
|
31425
31494
|
const sources = [
|
|
31426
31495
|
`lost-click pages ${n(known.lostClicks)}`,
|
|
31427
31496
|
`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$
|
|
31497
|
+
`sitemap ${n(known.sitemap.urls.length)} from ${plural$2(known.sitemap.files, "file")}${known.sitemap.partial ? ", partial" : ""}`
|
|
31429
31498
|
];
|
|
31430
31499
|
const lowerBound = known.sitemap.partial || known.searchConsoleCapped ? " at least" : "";
|
|
31431
31500
|
const asked = run.inspected - run.fromSaved;
|
|
31432
31501
|
const most = Math.min(total, MAX_INSPECT_LIMIT, run.quota ? run.inspected + run.quota.limit - run.quota.used - asked : MAX_INSPECT_LIMIT);
|
|
31433
31502
|
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$
|
|
31503
|
+
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
31504
|
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
31505
|
const lines = [
|
|
31437
31506
|
`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
31507
|
`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
31508
|
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
31509
|
];
|
|
31441
|
-
if (run.failed.length) lines.push(`URL Inspection failed for ${plural$
|
|
31510
|
+
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
31511
|
const groups = byReason(run.rows);
|
|
31443
31512
|
if (groups.length) lines.push(`By Google's reason, with labels from the rules below (not Google's):\n${table$1(groups, [
|
|
31444
31513
|
"reason",
|
|
@@ -32473,10 +32542,10 @@ function markdownText(markdown, url) {
|
|
|
32473
32542
|
return `Main content as Markdown (live page${markdown.truncated ? `, cut to ${markdown.markdown.length.toLocaleString("en-US")} of ${markdown.characters.toLocaleString("en-US")} characters` : ""}):\n\n${markdown.markdown}`;
|
|
32474
32543
|
}
|
|
32475
32544
|
async function runCompare(input, deps) {
|
|
32476
|
-
if (
|
|
32545
|
+
if (!realIsoDate(input.date)) throw new CommandError("INVALID_INPUT", "The date must be a real day as YYYY-MM-DD.");
|
|
32477
32546
|
const last = latestFinalGscDate(deps.now?.() ?? /* @__PURE__ */ new Date());
|
|
32478
32547
|
const daysAfter = Math.round((Date.parse(last) - Date.parse(input.date)) / 864e5) + 1;
|
|
32479
|
-
if (daysAfter < 3) throw new CommandError("INVALID_INPUT", `Search Console has final data to ${last}
|
|
32548
|
+
if (daysAfter < 3) throw new CommandError("INVALID_INPUT", `Search Console has final data to ${last}, and a comparison needs 3 days after the change. The latest date that works is ${shiftIsoDate(last, -2)}.`);
|
|
32480
32549
|
const days = Math.min(28, daysAfter);
|
|
32481
32550
|
const after = {
|
|
32482
32551
|
startDate: input.date,
|
|
@@ -32674,11 +32743,176 @@ async function runLostPages(input, deps) {
|
|
|
32674
32743
|
};
|
|
32675
32744
|
}
|
|
32676
32745
|
//#endregion
|
|
32746
|
+
//#region packages/core/src/commands/week.ts
|
|
32747
|
+
const DAY_MS = 864e5;
|
|
32748
|
+
function weekLabel(start, end) {
|
|
32749
|
+
const parts = (date) => {
|
|
32750
|
+
const value = /* @__PURE__ */ new Date(`${date}T00:00:00Z`);
|
|
32751
|
+
return {
|
|
32752
|
+
day: value.getUTCDate(),
|
|
32753
|
+
month: value.toLocaleString("en-GB", {
|
|
32754
|
+
month: "short",
|
|
32755
|
+
timeZone: "UTC"
|
|
32756
|
+
})
|
|
32757
|
+
};
|
|
32758
|
+
};
|
|
32759
|
+
const from = parts(start);
|
|
32760
|
+
const to = parts(end);
|
|
32761
|
+
return from.month === to.month ? `${from.day} to ${to.day} ${to.month}` : `${from.day} ${from.month} to ${to.day} ${to.month}`;
|
|
32762
|
+
}
|
|
32763
|
+
const weekTitle = (data) => `Your SEO Skill week, ${weekLabel(data.start, data.end)}`;
|
|
32764
|
+
const siteName = (property) => property.replace(/^sc-domain:|^https?:\/\//, "").replace(/\/$/, "");
|
|
32765
|
+
const count = (n) => n.toLocaleString("en-US");
|
|
32766
|
+
const plural$1 = (n, one, many = `${one}s`) => `${count(n)} ${n === 1 ? one : many}`;
|
|
32767
|
+
const list = (items) => items.length <= 1 ? items[0] ?? "" : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
32768
|
+
function path(url) {
|
|
32769
|
+
try {
|
|
32770
|
+
const parsed = new URL(url);
|
|
32771
|
+
return `${parsed.pathname}${parsed.search}`;
|
|
32772
|
+
} catch {
|
|
32773
|
+
return url;
|
|
32774
|
+
}
|
|
32775
|
+
}
|
|
32776
|
+
function clicksChange(site) {
|
|
32777
|
+
const clicks = site.clicks;
|
|
32778
|
+
if (!clicks) return null;
|
|
32779
|
+
const change = clicks.clicks - clicks.previous;
|
|
32780
|
+
if (Math.abs(change) < 50) return null;
|
|
32781
|
+
if (change < 0 && clicks.previous >= 100 && -change >= .3 * clicks.previous) return "fell";
|
|
32782
|
+
if (change > 0 && clicks.previous >= 50 && change >= .3 * clicks.previous) return "rose";
|
|
32783
|
+
return null;
|
|
32784
|
+
}
|
|
32785
|
+
const percent = (site) => {
|
|
32786
|
+
const clicks = site.clicks;
|
|
32787
|
+
return Math.round(Math.abs(clicks.clicks - clicks.previous) / clicks.previous * 100);
|
|
32788
|
+
};
|
|
32789
|
+
function issues(site, startMs, endMs) {
|
|
32790
|
+
const name = siteName(site.property);
|
|
32791
|
+
const found = [];
|
|
32792
|
+
const index = site.index;
|
|
32793
|
+
if (index?.dropped) {
|
|
32794
|
+
const paths = index.dropped_pages.map((page) => path(page.url));
|
|
32795
|
+
const more = index.dropped - paths.length;
|
|
32796
|
+
const shown = more > 0 ? `${paths.join(", ")} and ${plural$1(more, "more")}` : list(paths);
|
|
32797
|
+
const reasons = new Set(index.dropped_pages.map((page) => page.coverage));
|
|
32798
|
+
const reason = reasons.size === 1 ? [...reasons][0] : null;
|
|
32799
|
+
found.push({
|
|
32800
|
+
rank: 0,
|
|
32801
|
+
heading: `${plural$1(index.dropped, "page")} dropped`,
|
|
32802
|
+
line: `${shown} dropped out of Google’s index.${reason ? ` Google says: ${reason}.` : ""}`,
|
|
32803
|
+
next: `check ${index.dropped === 1 ? "it" : "they"} still return${index.dropped === 1 ? "s" : ""} 200 and ${index.dropped === 1 ? "is" : "are"} not noindexed.`,
|
|
32804
|
+
prompt: `Why did ${paths[0] ?? "a page"} on ${name} drop out of Google’s index this week, and how do I fix it?`
|
|
32805
|
+
});
|
|
32806
|
+
}
|
|
32807
|
+
const crawl = site.crawl;
|
|
32808
|
+
const crawled = crawl !== null && crawl.finished_at >= startMs && crawl.finished_at < endMs;
|
|
32809
|
+
if (crawled && crawl.status === "failed") found.push({
|
|
32810
|
+
rank: 1,
|
|
32811
|
+
heading: "crawl stopped early",
|
|
32812
|
+
line: `The weekly crawl stopped after ${plural$1(crawl.pages, "page")}${crawl.error ? `: ${crawl.error}` : "."}`,
|
|
32813
|
+
next: "check the site is up. The next weekly crawl tries again.",
|
|
32814
|
+
prompt: `Why did the weekly crawl of ${name} stop early, and what should I check?`
|
|
32815
|
+
});
|
|
32816
|
+
else if (!crawled && site.added_at < startMs) found.push({
|
|
32817
|
+
rank: 1,
|
|
32818
|
+
heading: "no crawl this week",
|
|
32819
|
+
line: `No weekly crawl finished this week${site.start_error ? `: ${site.start_error}` : "."}`,
|
|
32820
|
+
next: "check the site is up. The crawl tries again each hour.",
|
|
32821
|
+
prompt: `Why has the weekly crawl of ${name} not finished, and what should I check?`
|
|
32822
|
+
});
|
|
32823
|
+
if (clicksChange(site) === "fell") {
|
|
32824
|
+
const clicks = site.clicks;
|
|
32825
|
+
found.push({
|
|
32826
|
+
rank: 2,
|
|
32827
|
+
heading: `clicks down ${percent(site)}%`,
|
|
32828
|
+
line: `Search clicks fell ${percent(site)}%, from ${count(clicks.previous)} to ${count(clicks.clicks)} (${weekLabel(clicks.start, clicks.end)}, against the 7 days before).`,
|
|
32829
|
+
next: "ask your agent which pages and queries lost them.",
|
|
32830
|
+
prompt: `Which pages and queries lost search clicks on ${name} in the last week, and why?`
|
|
32831
|
+
});
|
|
32832
|
+
}
|
|
32833
|
+
if (crawled && crawl.new_issues > 0) {
|
|
32834
|
+
const rule = crawl.top_rule ? AUDIT_RULES[crawl.top_rule] : void 0;
|
|
32835
|
+
found.push({
|
|
32836
|
+
rank: 3,
|
|
32837
|
+
heading: plural$1(crawl.new_issues, "new issue"),
|
|
32838
|
+
line: `The weekly crawl found ${plural$1(crawl.new_issues, "new issue")}${rule ? `, most of them ${rule.charAt(0).toLowerCase()}${rule.slice(1)}` : ""}.`,
|
|
32839
|
+
next: "ask your agent for the weekly audit of this site.",
|
|
32840
|
+
prompt: `What did the weekly crawl of ${name} find this week, and what should I fix first?`
|
|
32841
|
+
});
|
|
32842
|
+
}
|
|
32843
|
+
return found;
|
|
32844
|
+
}
|
|
32845
|
+
function weekMarkdown(data) {
|
|
32846
|
+
const startMs = Date.parse(`${data.start}T00:00:00Z`);
|
|
32847
|
+
const endMs = Date.parse(`${data.end}T00:00:00Z`) + DAY_MS;
|
|
32848
|
+
const title = `# ${weekTitle(data)}`;
|
|
32849
|
+
if (!data.sites.length) return `${title}\n\nNo site is in your weekly audit yet. Add one on the Sites tab: ${data.account}#sites`;
|
|
32850
|
+
const byName = (a, b) => siteName(a.property).localeCompare(siteName(b.property)) || a.property.localeCompare(b.property);
|
|
32851
|
+
const sites = [...data.sites].sort(byName);
|
|
32852
|
+
const attention = [];
|
|
32853
|
+
const quiet = [];
|
|
32854
|
+
const waiting = [];
|
|
32855
|
+
for (const site of sites) {
|
|
32856
|
+
const found = issues(site, startMs, endMs);
|
|
32857
|
+
if (found.length) attention.push({
|
|
32858
|
+
site,
|
|
32859
|
+
found
|
|
32860
|
+
});
|
|
32861
|
+
else if (!site.crawl || !site.index) waiting.push(site);
|
|
32862
|
+
else quiet.push(site);
|
|
32863
|
+
}
|
|
32864
|
+
attention.sort((a, b) => a.found[0].rank - b.found[0].rank || byName(a.site, b.site));
|
|
32865
|
+
const total = sites.length;
|
|
32866
|
+
const out = [
|
|
32867
|
+
title,
|
|
32868
|
+
"",
|
|
32869
|
+
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."
|
|
32870
|
+
];
|
|
32871
|
+
if (attention.length) {
|
|
32872
|
+
out.push("", "## Needs attention");
|
|
32873
|
+
for (const { site, found } of attention) {
|
|
32874
|
+
const [first, ...rest] = found;
|
|
32875
|
+
const also = rest.length ? ` Also: ${list(rest.map((item) => item.heading))}.` : "";
|
|
32876
|
+
const rose = clicksChange(site) === "rose" ? ` Search clicks rose ${percent(site)}%.` : "";
|
|
32877
|
+
out.push("", `### ${siteName(site.property)}: ${first.heading}`, "", `- ${first.line}${also}${rose}`, `- Next step: ${first.next}`, ...site.link ? [`- [View site](${site.link})`] : []);
|
|
32878
|
+
}
|
|
32879
|
+
}
|
|
32880
|
+
if (quiet.length) {
|
|
32881
|
+
const risen = quiet.filter((site) => clicksChange(site) === "rose");
|
|
32882
|
+
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("")}`);
|
|
32883
|
+
}
|
|
32884
|
+
if (waiting.length) out.push("", "## Not checked yet", "", `${list(waiting.map((site) => siteName(site.property)))}: the first crawl or Google checks are still running.`);
|
|
32885
|
+
const first = attention[0];
|
|
32886
|
+
const prompt = first ? first.found[0].prompt : `What are the best quick wins on ${siteName((quiet[0] ?? sites[0]).property)} this month?`;
|
|
32887
|
+
out.push("", "## Ask your agent", "", "Paste this into your agent:", "", "```", prompt, "```");
|
|
32888
|
+
return out.join("\n");
|
|
32889
|
+
}
|
|
32890
|
+
async function runAllSites(deps, sites) {
|
|
32891
|
+
const data = deps.week ? await deps.week() : null;
|
|
32892
|
+
if (!data && sites) {
|
|
32893
|
+
const list = await sites();
|
|
32894
|
+
return {
|
|
32895
|
+
text: `No site given. Pick one below and send it: ${deps.call("report", { site: "<site>" })}. The summary of every site is for accounts in the weekly site audit beta.\n\n${list.text}`,
|
|
32896
|
+
data: list.data
|
|
32897
|
+
};
|
|
32898
|
+
}
|
|
32899
|
+
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.`);
|
|
32900
|
+
const startMs = Date.parse(`${data.start}T00:00:00Z`);
|
|
32901
|
+
const endMs = Date.parse(`${data.end}T00:00:00Z`) + DAY_MS;
|
|
32902
|
+
const urgency = (site) => issues(site, startMs, endMs)[0]?.rank ?? Number.POSITIVE_INFINITY;
|
|
32903
|
+
const first = [...data.sites].sort((a, b) => urgency(a) - urgency(b) || siteName(a.property).localeCompare(siteName(b.property)))[0];
|
|
32904
|
+
const example = first ? siteName(first.property) : "<site>";
|
|
32905
|
+
return {
|
|
32906
|
+
text: `${weekMarkdown(data)}\n\nFull report on one site: ${deps.call("report", { site: example })}`,
|
|
32907
|
+
data
|
|
32908
|
+
};
|
|
32909
|
+
}
|
|
32910
|
+
//#endregion
|
|
32677
32911
|
//#region packages/core/src/commands/tools.ts
|
|
32678
32912
|
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
32913
|
const dates = {
|
|
32680
|
-
start_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD. Optional;
|
|
32681
|
-
end_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD. Optional
|
|
32914
|
+
start_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD. Optional; without end_date it runs to the latest final day."),
|
|
32915
|
+
end_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD. Optional.")
|
|
32682
32916
|
};
|
|
32683
32917
|
const detail = {
|
|
32684
32918
|
detail: z.enum([
|
|
@@ -32697,6 +32931,14 @@ const DATA_SOURCES = [
|
|
|
32697
32931
|
function tool(spec) {
|
|
32698
32932
|
return spec;
|
|
32699
32933
|
}
|
|
32934
|
+
const listSites$1 = async (surface) => runSites({
|
|
32935
|
+
accounts: await surface.accounts(),
|
|
32936
|
+
keys: await surface.keys(),
|
|
32937
|
+
clicky: clickySites(await surface.keys()).map(clickyLabel),
|
|
32938
|
+
signIn: surface.signIn,
|
|
32939
|
+
bing: surface.deps.bing ? await surface.deps.bing() : void 0,
|
|
32940
|
+
connect: surface.connect
|
|
32941
|
+
});
|
|
32700
32942
|
const SHARED_TOOLS = [
|
|
32701
32943
|
tool({
|
|
32702
32944
|
name: "seo_sites",
|
|
@@ -32704,30 +32946,31 @@ const SHARED_TOOLS = [
|
|
|
32704
32946
|
title: "List sites",
|
|
32705
32947
|
description: "Lists the Search Console and Google Analytics properties the connected Google accounts can read, the Bing Webmaster site for each, and which research keys are set. Call it first.",
|
|
32706
32948
|
input: z.object({}),
|
|
32707
|
-
run:
|
|
32708
|
-
accounts: await surface.accounts(),
|
|
32709
|
-
keys: await surface.keys(),
|
|
32710
|
-
clicky: clickySites(await surface.keys()).map(clickyLabel),
|
|
32711
|
-
signIn: surface.signIn,
|
|
32712
|
-
bing: surface.deps.bing ? await surface.deps.bing() : void 0,
|
|
32713
|
-
connect: surface.connect
|
|
32714
|
-
})
|
|
32949
|
+
run: listSites$1
|
|
32715
32950
|
}),
|
|
32716
32951
|
tool({
|
|
32717
32952
|
name: "seo_report",
|
|
32718
32953
|
command: "report",
|
|
32719
32954
|
title: "Search Console report",
|
|
32720
|
-
description: `
|
|
32955
|
+
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 gets its site list to pick from. 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
32956
|
input: z.object({
|
|
32722
|
-
site,
|
|
32723
|
-
section: z.enum(SECTION_IDS).optional().describe("Optional. Leave it out first; then ask for one section."),
|
|
32957
|
+
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."),
|
|
32958
|
+
section: z.enum(SECTION_IDS).optional().describe("Optional, needs site. Leave it out first; then ask for one section."),
|
|
32724
32959
|
...dates,
|
|
32725
32960
|
...detail
|
|
32726
32961
|
}),
|
|
32727
32962
|
run: async (surface, input) => {
|
|
32728
|
-
|
|
32963
|
+
if (!input.site) {
|
|
32964
|
+
if (input.section) throw new CommandError("INVALID_INPUT", `section needs a site: ${surface.deps.call("report", {
|
|
32965
|
+
site: "<site>",
|
|
32966
|
+
section: input.section
|
|
32967
|
+
})}.`);
|
|
32968
|
+
return runAllSites(surface.deps, () => listSites$1(surface));
|
|
32969
|
+
}
|
|
32970
|
+
const target = input.site;
|
|
32971
|
+
const resolved = await surface.resolveSite(target).catch((error) => error instanceof Error ? error : new Error(String(error)));
|
|
32729
32972
|
if (resolved instanceof Error && !input.section && surface.deps.bing) return runBingReport({
|
|
32730
|
-
site:
|
|
32973
|
+
site: target,
|
|
32731
32974
|
startDate: input.start_date,
|
|
32732
32975
|
endDate: input.end_date
|
|
32733
32976
|
}, surface.deps).catch(() => {
|
|
@@ -32735,7 +32978,7 @@ const SHARED_TOOLS = [
|
|
|
32735
32978
|
});
|
|
32736
32979
|
if (resolved instanceof Error && input.section !== "bing") throw resolved;
|
|
32737
32980
|
return runReport({
|
|
32738
|
-
site: resolved instanceof Error ?
|
|
32981
|
+
site: resolved instanceof Error ? target : resolved,
|
|
32739
32982
|
section: input.section,
|
|
32740
32983
|
startDate: input.start_date,
|
|
32741
32984
|
endDate: input.end_date,
|
|
@@ -32939,7 +33182,7 @@ SHARED_TOOLS.map((shared) => shared.name === "seo_index" ? indexFromStore(shared
|
|
|
32939
33182
|
} : shared);
|
|
32940
33183
|
//#endregion
|
|
32941
33184
|
//#region packages/core/src/gsc/client/inspection-cache.ts
|
|
32942
|
-
const INSPECTION_FRESH_MS =
|
|
33185
|
+
const INSPECTION_FRESH_MS = 6048e5;
|
|
32943
33186
|
function readSavedInspections(site, urls, now = Date.now()) {
|
|
32944
33187
|
const found = /* @__PURE__ */ new Map();
|
|
32945
33188
|
try {
|
|
@@ -33184,7 +33427,8 @@ function nodeCommandDeps(options) {
|
|
|
33184
33427
|
analytics: {
|
|
33185
33428
|
accounts: localAccounts,
|
|
33186
33429
|
signIn: LOCAL_SIGN_IN.signIn
|
|
33187
|
-
}
|
|
33430
|
+
},
|
|
33431
|
+
...cloudLinked() ? { week: cloudWeek } : {}
|
|
33188
33432
|
};
|
|
33189
33433
|
}
|
|
33190
33434
|
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).";
|
|
@@ -33247,8 +33491,9 @@ async function localRefreshClicky() {
|
|
|
33247
33491
|
return localResearchKeys();
|
|
33248
33492
|
}
|
|
33249
33493
|
async function resolveLocalSite(site, options = {}) {
|
|
33250
|
-
const
|
|
33251
|
-
|
|
33494
|
+
const names = (await listSites(options.refresh, options.account)).map((entry) => entry.siteUrl);
|
|
33495
|
+
const found = matchProperty(names, site);
|
|
33496
|
+
if (!found) throw new CommandError("NO_ACCESS", `This Google login has no Search Console property for ${site}. ${readable(names)}`);
|
|
33252
33497
|
return found;
|
|
33253
33498
|
}
|
|
33254
33499
|
const ERROR_CODES = {
|
|
@@ -33256,6 +33501,7 @@ const ERROR_CODES = {
|
|
|
33256
33501
|
NOT_CONNECTED: "AUTH_REQUIRED",
|
|
33257
33502
|
NO_ACCESS: "PROPERTY_NOT_FOUND",
|
|
33258
33503
|
NO_KEY: "AUTH_CONFIG_REQUIRED",
|
|
33504
|
+
NO_DATA: "INSUFFICIENT_DATA",
|
|
33259
33505
|
RATE_LIMITED: "RATE_LIMITED",
|
|
33260
33506
|
GOOGLE_ERROR: "PROVIDER_UNAVAILABLE",
|
|
33261
33507
|
PROVIDER_ERROR: "PROVIDER_UNAVAILABLE"
|
|
@@ -33998,10 +34244,10 @@ function reconcileExportPagesWithCrawl(input) {
|
|
|
33998
34244
|
}
|
|
33999
34245
|
//#endregion
|
|
34000
34246
|
//#region packages/core/src/storage/log-retention.ts
|
|
34001
|
-
const MEBIBYTE =
|
|
34247
|
+
const MEBIBYTE = 1048576;
|
|
34002
34248
|
const LOG_MAX_FILE_BYTES = 8 * MEBIBYTE;
|
|
34003
34249
|
const LOG_MAX_TOTAL_BYTES = 64 * MEBIBYTE;
|
|
34004
|
-
const LOG_MAX_AGE_MS =
|
|
34250
|
+
const LOG_MAX_AGE_MS = 12096e5;
|
|
34005
34251
|
function logFiles(directory) {
|
|
34006
34252
|
if (!existsSync(directory)) return [];
|
|
34007
34253
|
return readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".log")).map((entry) => {
|
|
@@ -34081,10 +34327,10 @@ function makeRunId() {
|
|
|
34081
34327
|
}
|
|
34082
34328
|
//#endregion
|
|
34083
34329
|
//#region packages/core/src/provider-extensions/actions.ts
|
|
34084
|
-
const MAX_ACTION_PARAM_BYTES =
|
|
34330
|
+
const MAX_ACTION_PARAM_BYTES = 65536;
|
|
34085
34331
|
const MAX_ACTION_PARAM_DEPTH = 16;
|
|
34086
34332
|
const MAX_ACTION_PARAM_VALUES = 5e3;
|
|
34087
|
-
const MAX_ACTION_RESULT_BYTES =
|
|
34333
|
+
const MAX_ACTION_RESULT_BYTES = 10485760;
|
|
34088
34334
|
const MAX_ACTION_RESULT_DEPTH = 20;
|
|
34089
34335
|
const MAX_ACTION_RESULT_VALUES = 1e5;
|
|
34090
34336
|
function normalizedParams(params) {
|
|
@@ -34299,8 +34545,8 @@ async function describeInstalledProviderExtension(id) {
|
|
|
34299
34545
|
//#endregion
|
|
34300
34546
|
//#region packages/core/src/provider-extensions/installer.ts
|
|
34301
34547
|
const execFileAsync = promisify(execFile);
|
|
34302
|
-
const MAX_PROVIDER_UNPACKED_BYTES =
|
|
34303
|
-
const MAX_NPM_OUTPUT_BYTES =
|
|
34548
|
+
const MAX_PROVIDER_UNPACKED_BYTES = 5242880;
|
|
34549
|
+
const MAX_NPM_OUTPUT_BYTES = 1048576;
|
|
34304
34550
|
const PACKAGE_REFERENCE_EXAMPLE = "@scope/provider or @scope/provider@1.2.3";
|
|
34305
34551
|
const npmPackageMetadataSchema = z.object({
|
|
34306
34552
|
name: providerPackageNameSchema,
|
|
@@ -34525,7 +34771,7 @@ const ahrefsLimitsAndUsageResponseSchema = z.object({ limits_and_usage: z.object
|
|
|
34525
34771
|
const DEFAULT_BASE_URL$1 = "https://api.ahrefs.com/v3/";
|
|
34526
34772
|
const LIMITS_AND_USAGE_PATH = "subscription-info/limits-and-usage";
|
|
34527
34773
|
const DEFAULT_TIMEOUT_MS$1 = 1e4;
|
|
34528
|
-
const MAX_ACCOUNT_RESPONSE_BYTES =
|
|
34774
|
+
const MAX_ACCOUNT_RESPONSE_BYTES = 65536;
|
|
34529
34775
|
function remaining(limit, used) {
|
|
34530
34776
|
if (limit === null || used === null) return null;
|
|
34531
34777
|
return Math.max(0, limit - used);
|
|
@@ -34652,7 +34898,7 @@ function dataForSeoTaskErrorCode(statusCode) {
|
|
|
34652
34898
|
const DEFAULT_BASE_URL = "https://api.dataforseo.com/";
|
|
34653
34899
|
const USER_DATA_PATH = "v3/appendix/user_data";
|
|
34654
34900
|
const DEFAULT_TIMEOUT_MS = 15e3;
|
|
34655
|
-
const MAX_USER_DATA_RESPONSE_BYTES =
|
|
34901
|
+
const MAX_USER_DATA_RESPONSE_BYTES = 5242880;
|
|
34656
34902
|
var DataForSeoClient = class {
|
|
34657
34903
|
fetch;
|
|
34658
34904
|
credentials;
|
|
@@ -34952,6 +35198,6 @@ function isoDate(daysAgo = 0) {
|
|
|
34952
35198
|
return date.toISOString().slice(0, 10);
|
|
34953
35199
|
}
|
|
34954
35200
|
//#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 };
|
|
35201
|
+
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_ERROR_CODES, 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, detectAgentName, 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
35202
|
|
|
34957
35203
|
//# sourceMappingURL=index.js.map
|