seo 0.12.1 → 0.13.0

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