seo 0.13.2 → 0.13.3

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.d.ts CHANGED
@@ -21539,6 +21539,12 @@ type AuditSummary = {
21539
21539
  capped_rules: string[];
21540
21540
  resolution: 'all' | 'crawled-pages-only';
21541
21541
  };
21542
+ rendering?: {
21543
+ mode: 'on' | 'off' | 'unavailable' | null;
21544
+ rendered: number;
21545
+ failed: number;
21546
+ browser_ms: number;
21547
+ };
21542
21548
  changes: {
21543
21549
  compared: boolean;
21544
21550
  new_urls: number;
@@ -21694,8 +21700,21 @@ type CommandDeps = {
21694
21700
  analytics?: AnalyticsAccess;
21695
21701
  audit?: AuditAccess;
21696
21702
  week?: () => Promise<WeekData | null>;
21703
+ startAudit?: (site: string) => Promise<StartAudit>;
21697
21704
  now?: () => Date;
21698
21705
  };
21706
+ type StartAudit = {
21707
+ status: 'added' | 'exists' | 'pending';
21708
+ } | {
21709
+ status: 'checkout';
21710
+ url: string;
21711
+ price: number;
21712
+ } | {
21713
+ status: 'full';
21714
+ limit: number;
21715
+ } | {
21716
+ status: 'payment_failed';
21717
+ };
21699
21718
  type PageMarkdown = {
21700
21719
  markdown: string;
21701
21720
  characters: number;
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-Dmtr8qrP.js";
2
+ import { n as version, t as load } from "./esm-hHlvm1W0.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";
@@ -1638,6 +1638,13 @@ async function failure(response, label, subject, options) {
1638
1638
  const detail = (await response.text().catch(() => "")).slice(0, 300);
1639
1639
  const status = response.status;
1640
1640
  const what = subject ? `${label} for ${subject}` : label;
1641
+ if (status === 400) {
1642
+ let reason = detail;
1643
+ try {
1644
+ reason = JSON.parse(detail).error?.message ?? detail;
1645
+ } catch {}
1646
+ return new GoogleApiError("INVALID_INPUT", `${label} rejected the request: ${reason}`.trim(), status);
1647
+ }
1641
1648
  if (status === 401) return new GoogleApiError("AUTH_EXPIRED", `Google rejected the sign-in. ${options.signIn}`, status);
1642
1649
  if (status === 403) return new GoogleApiError("ACCESS_DENIED", `Google denied access to ${what}. Check the Google account, its access to this property, and the granted scopes. ${detail}`.trim(), status);
1643
1650
  if (status === 404) return new GoogleApiError("PROPERTY_NOT_FOUND", `Google could not find ${what}. Check the property and account access.`, status);
@@ -30359,7 +30366,7 @@ async function analyticsBeside(access, site, range, before, pages) {
30359
30366
  //#endregion
30360
30367
  //#region packages/core/src/commands/audit.ts
30361
30368
  const AUDIT_SECTION = "weekly-audit";
30362
- 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.";
30369
+ 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, tags that JavaScript changes), with what is new, back or resolved since the crawl before. Only for sites added to the weekly site audit beta.";
30363
30370
  const AUDIT_RULES = {
30364
30371
  link_to_error: "Internal links to a page that returns an error",
30365
30372
  link_to_redirect: "Internal links to a redirect",
@@ -30372,7 +30379,8 @@ const AUDIT_RULES = {
30372
30379
  canonical_elsewhere: "Pages whose canonical is another URL",
30373
30380
  sitemap_url_not_ok: "Sitemap URLs that do not return 200",
30374
30381
  sitemap_page_unlinked: "Sitemap pages no crawled page links to (heuristic: the crawl may not reach every page)",
30375
- thin_page: "Pages under 100 words (heuristic)"
30382
+ thin_page: "Pages under 100 words (heuristic)",
30383
+ js_changed_tags: "Pages whose raw HTML has another title, canonical or robots tag than the page after JavaScript runs"
30376
30384
  };
30377
30385
  const PAGE_SIZE$1 = 50;
30378
30386
  const SUMMARY_ROWS = 20;
@@ -30387,6 +30395,17 @@ function evidence(finding, origin) {
30387
30395
  if (typeof details.location === "string") parts.push(`to ${relative$2(details.location, origin)}`);
30388
30396
  if (typeof details.canonical === "string") parts.push(`canonical ${relative$2(details.canonical, origin)}`);
30389
30397
  if (typeof details.words === "number") parts.push(`${details.words} words`);
30398
+ if (details.raw && typeof details.raw === "object") {
30399
+ const raw = details.raw;
30400
+ for (const key of [
30401
+ "title",
30402
+ "canonical",
30403
+ "robots"
30404
+ ]) if (key in raw) {
30405
+ const value = raw[key];
30406
+ parts.push(`raw ${key} ${typeof value === "string" ? key === "canonical" ? relative$2(value, origin) : value : "none"}`);
30407
+ }
30408
+ }
30390
30409
  const linking = typeof details.linking_pages === "number";
30391
30410
  const count = linking ? details.linking_pages : details.pages;
30392
30411
  if (typeof count === "number") {
@@ -30417,6 +30436,14 @@ function ordered(findings, since) {
30417
30436
  };
30418
30437
  return [...findings].sort((a, b) => rank(a) - rank(b) || a.rule.localeCompare(b.rule) || (a.subject < b.subject ? -1 : a.subject > b.subject ? 1 : 0));
30419
30438
  }
30439
+ function renderingText(rendering) {
30440
+ if (!rendering) return null;
30441
+ const pages = (n) => n === 1 ? "1 page" : `${n} pages`;
30442
+ if (rendering.mode === "on") return `JavaScript changed what some pages show, so the crawl rendered the parts of the site that need it (${pages(rendering.rendered)} rendered${rendering.failed ? `; ${pages(rendering.failed)} could not be rendered, so ${rendering.failed === 1 ? "it is" : "they are"} left out of the tag and text checks` : ""}).`;
30443
+ if (rendering.mode === "off") return "JavaScript did not change the pages checked, so the crawl read raw HTML.";
30444
+ if (rendering.mode === "unavailable") return "JavaScript rendering failed on the pages checked, so the crawl read raw HTML. If the site builds its pages with JavaScript, titles, canonicals and links may be missing here.";
30445
+ return rendering.failed && !rendering.rendered ? "JavaScript rendering failed, so the crawl read raw HTML." : null;
30446
+ }
30420
30447
  function runAuditReport(input, snapshot, call) {
30421
30448
  const site = input.site;
30422
30449
  const detail = input.detail ?? "summary";
@@ -30465,7 +30492,8 @@ function runAuditReport(input, snapshot, call) {
30465
30492
  const head = [
30466
30493
  `Weekly site audit of ${site}, crawled ${day$1(snapshot.finished_at)}: ${coverage}.${pages.blocked ? ` ${pages.blocked === 1 ? "1 URL" : `${pages.blocked} URLs`} blocked by robots.txt ${pages.blocked === 1 ? "was" : "were"} not fetched.` : ""}`,
30467
30494
  previous ? `Since the crawl of ${day$1(previous.finished_at)}: ${summary.findings.new} new, ${summary.findings.reopened} back, ${summary.findings.resolved} resolved. ${summary.findings.open} open now.` : `This is the first crawl, so every finding is new: ${summary.findings.open} open.`,
30468
- summary.findings.resolution === "crawled-pages-only" ? "Because the crawl is partial, a finding counts as resolved only when this crawl fetched its page again; a finding not seen here is not an all-clear." : null,
30495
+ renderingText(summary.rendering),
30496
+ summary.findings.resolution === "crawled-pages-only" ? summary.complete ? "Because some pages could not be rendered, a finding counts as resolved only when this crawl rendered its page; a finding not seen here is not an all-clear." : "Because the crawl is partial, a finding counts as resolved only when this crawl fetched its page again; a finding not seen here is not an all-clear." : null,
30469
30497
  summary.findings.capped_rules.length ? `Capped at 1,000 findings each, so these counts are lower bounds: ${summary.findings.capped_rules.join(", ")}.` : null,
30470
30498
  running
30471
30499
  ].filter(Boolean).join("\n");
@@ -32795,6 +32823,7 @@ const siteName = (property) => property.replace(/^sc-domain:|^https?:\/\//, "").
32795
32823
  const count = (n) => n.toLocaleString("en-US");
32796
32824
  const plural$1 = (n, one, many = `${one}s`) => `${count(n)} ${n === 1 ? one : many}`;
32797
32825
  const list = (items) => items.length <= 1 ? items[0] ?? "" : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
32826
+ const sentence = (text) => /[.!?]$/.test(text) ? text : `${text}.`;
32798
32827
  function path(url) {
32799
32828
  try {
32800
32829
  const parsed = new URL(url);
@@ -32817,60 +32846,58 @@ const percent = (site) => {
32817
32846
  return Math.round(Math.abs(clicks.clicks - clicks.previous) / clicks.previous * 100);
32818
32847
  };
32819
32848
  function issues(site, startMs, endMs) {
32820
- const name = siteName(site.property);
32821
32849
  const found = [];
32850
+ if (site.index?.dropped) found.push({
32851
+ rank: 0,
32852
+ heading: `${plural$1(site.index.dropped, "page")} dropped`
32853
+ });
32854
+ const crawl = site.crawl;
32855
+ const crawled = crawl !== null && crawl.finished_at >= startMs && crawl.finished_at < endMs;
32856
+ if (crawled && crawl.status === "failed") found.push({
32857
+ rank: 1,
32858
+ heading: "crawl stopped early"
32859
+ });
32860
+ else if (!crawled && site.added_at < startMs) found.push({
32861
+ rank: 1,
32862
+ heading: "no crawl this week"
32863
+ });
32864
+ if (clicksChange(site) === "fell") found.push({
32865
+ rank: 2,
32866
+ heading: `clicks down ${percent(site)}%`
32867
+ });
32868
+ if (crawled && crawl.new_issues > 0) found.push({
32869
+ rank: 3,
32870
+ heading: plural$1(crawl.new_issues, "new issue")
32871
+ });
32872
+ return found;
32873
+ }
32874
+ function siteLines(site, startMs, endMs) {
32875
+ const lines = [];
32822
32876
  const index = site.index;
32823
- if (index?.dropped) {
32877
+ if (!index) lines.push("Google index: no checks ran this week yet.");
32878
+ else if (index.dropped) {
32824
32879
  const paths = index.dropped_pages.map((page) => path(page.url));
32825
32880
  const more = index.dropped - paths.length;
32826
32881
  const shown = more > 0 ? `${paths.join(", ")} and ${plural$1(more, "more")}` : list(paths);
32827
32882
  const reasons = new Set(index.dropped_pages.map((page) => page.coverage));
32828
32883
  const reason = reasons.size === 1 ? [...reasons][0] : null;
32829
- found.push({
32830
- rank: 0,
32831
- heading: `${plural$1(index.dropped, "page")} dropped`,
32832
- line: `${shown} dropped out of Google’s index.${reason ? ` Google says: ${reason}.` : ""}`,
32833
- next: `check ${index.dropped === 1 ? "it" : "they"} still return${index.dropped === 1 ? "s" : ""} 200 and ${index.dropped === 1 ? "is" : "are"} not noindexed.`,
32834
- prompt: `Why did ${paths[0] ?? "a page"} on ${name} drop out of Google’s index this week, and how do I fix it?`
32835
- });
32836
- }
32884
+ lines.push(`Google index: ${plural$1(index.dropped, "page")} dropped out (${shown}).${reason ? ` Google says: ${reason}.` : ""}${index.returned ? ` ${plural$1(index.returned, "page")} came back.` : ""} ${plural$1(index.checked, "page")} checked.`);
32885
+ } else lines.push(`Google index: ${plural$1(index.checked, "page")} checked, none dropped${index.returned ? `, ${plural$1(index.returned, "page")} came back` : ""}.`);
32837
32886
  const crawl = site.crawl;
32838
32887
  const crawled = crawl !== null && crawl.finished_at >= startMs && crawl.finished_at < endMs;
32839
- if (crawled && crawl.status === "failed") found.push({
32840
- rank: 1,
32841
- heading: "crawl stopped early",
32842
- line: `The weekly crawl stopped after ${plural$1(crawl.pages, "page")}${crawl.error ? `: ${crawl.error}` : "."}`,
32843
- next: "check the site is up. The next weekly crawl tries again.",
32844
- prompt: `Why did the weekly crawl of ${name} stop early, and what should I check?`
32845
- });
32846
- else if (!crawled && site.added_at < startMs) found.push({
32847
- rank: 1,
32848
- heading: "no crawl this week",
32849
- line: `No weekly crawl finished this week${site.start_error ? `: ${site.start_error}` : "."}`,
32850
- next: "check the site is up. The crawl tries again each hour.",
32851
- prompt: `Why has the weekly crawl of ${name} not finished, and what should I check?`
32852
- });
32853
- if (clicksChange(site) === "fell") {
32854
- const clicks = site.clicks;
32855
- found.push({
32856
- rank: 2,
32857
- heading: `clicks down ${percent(site)}%`,
32858
- line: `Search clicks fell ${percent(site)}%, from ${count(clicks.previous)} to ${count(clicks.clicks)} (${weekLabel(clicks.start, clicks.end)}, against the 7 days before).`,
32859
- next: "ask your agent which pages and queries lost them.",
32860
- prompt: `Which pages and queries lost search clicks on ${name} in the last week, and why?`
32861
- });
32862
- }
32863
- if (crawled && crawl.new_issues > 0) {
32888
+ if (crawled && crawl.status === "failed") lines.push(`Weekly crawl: stopped after ${plural$1(crawl.pages, "page")}${crawl.error ? `: ${sentence(crawl.error)}` : "."} It tries again next week.`);
32889
+ else if (crawled) {
32864
32890
  const rule = crawl.top_rule ? AUDIT_RULES[crawl.top_rule] : void 0;
32865
- found.push({
32866
- rank: 3,
32867
- heading: plural$1(crawl.new_issues, "new issue"),
32868
- line: `The weekly crawl found ${plural$1(crawl.new_issues, "new issue")}${rule ? `, most of them ${rule.charAt(0).toLowerCase()}${rule.slice(1)}` : ""}.`,
32869
- next: "ask your agent for the weekly audit of this site.",
32870
- prompt: `What did the weekly crawl of ${name} find this week, and what should I fix first?`
32871
- });
32872
- }
32873
- return found;
32891
+ lines.push(crawl.new_issues ? `Weekly crawl: ${plural$1(crawl.pages, "page")}, ${plural$1(crawl.new_issues, "new issue")}${rule ? `, most of them ${rule.charAt(0).toLowerCase()}${rule.slice(1)}` : ""}.` : `Weekly crawl: ${plural$1(crawl.pages, "page")}, no new issues.`);
32892
+ } else if (site.added_at < startMs) lines.push(`Weekly crawl: none finished this week${site.start_error ? `: ${sentence(site.start_error)}` : "."} It tries again each hour.`);
32893
+ else lines.push("Weekly crawl: the first crawl is still running.");
32894
+ const clicks = site.clicks;
32895
+ if (clicks) {
32896
+ const change = clicks.previous > 0 ? Math.round((clicks.clicks - clicks.previous) / clicks.previous * 100) : null;
32897
+ const trend = change === null ? `from ${count(clicks.previous)}` : change === 0 ? "level with" : `${change > 0 ? "up" : "down"} ${Math.abs(change)}% on`;
32898
+ lines.push(`Search clicks: ${count(clicks.clicks)} (${weekLabel(clicks.start, clicks.end)}), ${trend}${change === null ? "" : ` ${count(clicks.previous)}`} the 7 days before.`);
32899
+ } else lines.push("Search clicks: not available this week.");
32900
+ return lines;
32874
32901
  }
32875
32902
  function weekMarkdown(data) {
32876
32903
  const startMs = Date.parse(`${data.start}T00:00:00Z`);
@@ -32878,43 +32905,21 @@ function weekMarkdown(data) {
32878
32905
  const title = `# ${weekTitle(data)}`;
32879
32906
  if (!data.sites.length) return `${title}\n\nNo site is in your weekly audit yet. Add one on the Sites tab: ${data.account}#sites`;
32880
32907
  const byName = (a, b) => siteName(a.property).localeCompare(siteName(b.property)) || a.property.localeCompare(b.property);
32881
- const sites = [...data.sites].sort(byName);
32882
- const attention = [];
32883
- const quiet = [];
32884
- const waiting = [];
32885
- for (const site of sites) {
32886
- const found = issues(site, startMs, endMs);
32887
- if (found.length) attention.push({
32888
- site,
32889
- found
32890
- });
32891
- else if (!site.crawl || !site.index) waiting.push(site);
32892
- else quiet.push(site);
32893
- }
32894
- attention.sort((a, b) => a.found[0].rank - b.found[0].rank || byName(a.site, b.site));
32895
- const total = sites.length;
32908
+ const rows = data.sites.map((site) => ({
32909
+ site,
32910
+ found: issues(site, startMs, endMs),
32911
+ waiting: !site.crawl || !site.index
32912
+ }));
32913
+ const attention = rows.filter((row) => row.found.length);
32914
+ const quiet = rows.filter((row) => !row.found.length && !row.waiting);
32915
+ rows.sort((a, b) => (a.found[0]?.rank ?? Number.POSITIVE_INFINITY) - (b.found[0]?.rank ?? Number.POSITIVE_INFINITY) || byName(a.site, b.site));
32916
+ const total = rows.length;
32896
32917
  const out = [
32897
32918
  title,
32898
32919
  "",
32899
- 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."
32920
+ attention.length ? total === 1 ? "Your site had changes worth a look this week." : `${attention.length} of your ${total} sites had changes worth 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."
32900
32921
  ];
32901
- if (attention.length) {
32902
- out.push("", "## Needs attention");
32903
- for (const { site, found } of attention) {
32904
- const [first, ...rest] = found;
32905
- const also = rest.length ? ` Also: ${list(rest.map((item) => item.heading))}.` : "";
32906
- const rose = clicksChange(site) === "rose" ? ` Search clicks rose ${percent(site)}%.` : "";
32907
- out.push("", `### ${siteName(site.property)}: ${first.heading}`, "", `- ${first.line}${also}${rose}`, `- Next step: ${first.next}`, ...site.link ? [`- [View site](${site.link})`] : []);
32908
- }
32909
- }
32910
- if (quiet.length) {
32911
- const risen = quiet.filter((site) => clicksChange(site) === "rose");
32912
- 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("")}`);
32913
- }
32914
- if (waiting.length) out.push("", "## Not checked yet", "", `${list(waiting.map((site) => siteName(site.property)))}: the first crawl or Google checks are still running.`);
32915
- const first = attention[0];
32916
- const prompt = first ? first.found[0].prompt : `What are the best quick wins on ${siteName((quiet[0] ?? sites[0]).property)} this month?`;
32917
- out.push("", "## Ask your agent", "", "Paste this into your agent:", "", "```", prompt, "```");
32922
+ for (const { site, found } of rows) out.push("", `## ${siteName(site.property)}${found.length ? `: ${list(found.map((item) => item.heading))}` : ""}`, "", ...siteLines(site, startMs, endMs).map((line) => `- ${line}`), ...site.link ? [`- [View site](${site.link})`] : []);
32918
32923
  return out.join("\n");
32919
32924
  }
32920
32925
  async function runAllSites(deps, sites) {
@@ -32974,15 +32979,38 @@ const SHARED_TOOLS = [
32974
32979
  name: "seo_sites",
32975
32980
  command: "data",
32976
32981
  title: "List sites",
32977
- 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.",
32978
- input: z.object({}),
32979
- run: listSites$1
32982
+ 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. With add: starts the weekly site audit of that site on the hosted server (a weekly crawl, daily Google index checks, a Monday email and webhook with what changed, and alerts when a page drops out of Google's index). A site that needs paying for returns a Stripe Checkout link: give it to the person to open, and the audit starts once they pay.",
32983
+ input: z.object({ add: site.optional().describe("Optional. A site to add to the weekly site audit. Only when the person asks for it: a site can cost money.") }),
32984
+ run: async (surface, input) => {
32985
+ if (!input.add) return listSites$1(surface);
32986
+ const start = surface.deps.startAudit;
32987
+ if (!start) throw new CommandError("INVALID_INPUT", "The weekly site audit runs on the hosted server only. Connect https://seoskill.dev/mcp and call seo_sites with add there.");
32988
+ const property = await surface.resolveSite(input.add);
32989
+ const result = await start(property);
32990
+ return {
32991
+ text: {
32992
+ added: `Added ${property} to the weekly site audit. Its first crawl and index checks have started; ask ${surface.deps.call("report", {
32993
+ site: property,
32994
+ section: AUDIT_SECTION
32995
+ })} for the findings once the crawl is done.`,
32996
+ exists: `${property} is already in the weekly site audit.`,
32997
+ pending: "The payment went through and the plan is starting. Call seo_sites with add again in a minute.",
32998
+ checkout: result.status === "checkout" ? `${property} needs a plan: $${result.price} a month per site, cancel at any time. Give the person this Stripe Checkout link to open and pay: ${result.url}\nThe audit of ${property} starts once they pay. Call seo_sites with add again afterwards to confirm.` : "",
32999
+ full: result.status === "full" ? `This account already audits ${result.limit} sites, the most it can. Remove one on the account page first.` : "",
33000
+ payment_failed: `Stripe could not take the payment for another site, so ${property} was not added. The person can check their card under Manage billing on the account page.`
33001
+ }[result.status],
33002
+ data: {
33003
+ property,
33004
+ ...result
33005
+ }
33006
+ };
33007
+ }
32980
33008
  }),
32981
33009
  tool({
32982
33010
  name: "seo_report",
32983
33011
  command: "report",
32984
33012
  title: "Search Console report",
32985
- 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")}`,
33013
+ description: `Start here. Without site: a short summary of every site in the person's weekly site audit (beta) for last week, with each site's Google index, weekly crawl and search clicks, sites with changes first, 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")}`,
32986
33014
  input: z.object({
32987
33015
  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."),
32988
33016
  section: z.enum(SECTION_IDS).optional().describe("Optional, needs site. Leave it out first; then ask for one section."),