seo 0.12.1 → 0.13.1

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