@canonry/canonry 4.136.0 → 4.136.2

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.
Files changed (30) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +4 -0
  2. package/assets/agent-workspace/skills/canonry/references/indexing.md +26 -12
  3. package/assets/assets/{AuditHistoryPanel-DKaiIlcO.js → AuditHistoryPanel-DtUGiN21.js} +1 -1
  4. package/assets/assets/{BacklinksPage-Z2_ws8c2.js → BacklinksPage-BGwtmwfz.js} +1 -1
  5. package/assets/assets/{ChartPrimitives-CF_ocuvj.js → ChartPrimitives-Df2UC1g3.js} +1 -1
  6. package/assets/assets/{HistoryPage-DDKO8_D8.js → HistoryPage-AtaMVEUx.js} +1 -1
  7. package/assets/assets/ProjectPage-OlBjxXNx.js +8 -0
  8. package/assets/assets/{RunRow-BhtCgyqA.js → RunRow-Cmf-5xVo.js} +1 -1
  9. package/assets/assets/{RunsPage-B8Nw3wP-.js → RunsPage-BAPy9Yri.js} +1 -1
  10. package/assets/assets/{SettingsPage-DsmQhytl.js → SettingsPage-BXO7WpTo.js} +1 -1
  11. package/assets/assets/{TrafficPage-COJs6eRH.js → TrafficPage-IH-wiPu2.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-BI6IwzMQ.js → TrafficSourceDetailPage-CTTN-P52.js} +1 -1
  13. package/assets/assets/{arrow-left-C-lGPq-u.js → arrow-left-BwzAIeJ_.js} +1 -1
  14. package/assets/assets/{extract-error-message-8h0hF1I1.js → extract-error-message-BIcIoJLN.js} +1 -1
  15. package/assets/assets/index-BPpSrJY0.js +210 -0
  16. package/assets/assets/index-CglduHCN.css +1 -0
  17. package/assets/assets/{trash-2-CnOZwYKe.js → trash-2-C50OeBAV.js} +1 -1
  18. package/assets/index.html +2 -2
  19. package/dist/{chunk-PXEEWJRV.js → chunk-GGIRLRAY.js} +3093 -399
  20. package/dist/{chunk-3PFRVEWP.js → chunk-MXR3GI5L.js} +54 -122
  21. package/dist/{chunk-PAR6P5H6.js → chunk-SMFUCXHI.js} +132 -6
  22. package/dist/{chunk-TPVMHYHD.js → chunk-YVMQWVNY.js} +291 -206
  23. package/dist/cli.js +184 -15
  24. package/dist/index.js +4 -4
  25. package/dist/{intelligence-service-BMEJ5GFZ.js → intelligence-service-BD6BHTEJ.js} +2 -2
  26. package/dist/mcp.js +2 -2
  27. package/package.json +9 -9
  28. package/assets/assets/ProjectPage-hmNsqBov.js +0 -8
  29. package/assets/assets/index-B4ksyQXo.css +0 -1
  30. package/assets/assets/index-BoSfFv1t.js +0 -210
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-PAR6P5H6.js";
13
+ } from "./chunk-SMFUCXHI.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -119,7 +119,7 @@ import {
119
119
  siteAuditPages,
120
120
  siteAuditSnapshots,
121
121
  usageCounters
122
- } from "./chunk-TPVMHYHD.js";
122
+ } from "./chunk-YVMQWVNY.js";
123
123
  import {
124
124
  AGENT_MEMORY_VALUE_MAX_BYTES,
125
125
  AGENT_PROVIDER_IDS,
@@ -173,6 +173,8 @@ import {
173
173
  factorStatusFromScore,
174
174
  formatIsoDateInTimeZone,
175
175
  frameAncestorsHeaderValue,
176
+ hostMatchesAnyDomain,
177
+ hostMatchesDomain,
176
178
  hostOf,
177
179
  isAgentProviderId,
178
180
  isBrowserProvider,
@@ -185,16 +187,19 @@ import {
185
187
  notFound,
186
188
  parseOriginList,
187
189
  providerError,
190
+ registrableDomain,
188
191
  resolveSnapshotRequestQueries,
189
192
  serializeForInlineScript,
190
193
  serializeRunError,
191
194
  skillsClientSchema,
192
195
  splitList,
193
196
  startOfDayHourInTimeZone,
197
+ textContainsBrandAlias,
198
+ textContainsDomain,
194
199
  validationError,
195
200
  winnabilityClassLabel,
196
201
  withRetry
197
- } from "./chunk-PXEEWJRV.js";
202
+ } from "./chunk-GGIRLRAY.js";
198
203
 
199
204
  // src/telemetry.ts
200
205
  import crypto from "crypto";
@@ -728,17 +733,15 @@ function extractCitedDomainsFromSources(groundingSources) {
728
733
  return [...domains];
729
734
  }
730
735
  function extractDomainFromTitle(title) {
731
- const trimmed = title.trim().toLowerCase();
732
- if (/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z]{2,})+$/.test(trimmed)) {
733
- return trimmed.replace(/^www\./, "");
734
- }
735
- return null;
736
+ const trimmed = title.trim();
737
+ return registrableDomain(trimmed) ? hostOf(trimmed) : null;
736
738
  }
737
739
  function extractDomainFromUri(uri) {
738
740
  try {
739
741
  const url = new URL(uri);
740
- const hostname = url.hostname.replace(/^www\./, "").toLowerCase();
741
- if (AI_ENGINE_SELF_DOMAINS.chatgpt.some((self) => hostname.includes(self))) {
742
+ const hostname = hostOf(uri);
743
+ if (!hostname || !registrableDomain(hostname)) return null;
744
+ if (hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.chatgpt)) {
742
745
  return null;
743
746
  }
744
747
  if (hostname === VERTEX_AI_SEARCH_PROXY_DOMAIN) {
@@ -747,8 +750,7 @@ function extractDomainFromUri(uri) {
747
750
  try {
748
751
  const decoded = decodeURIComponent(redirectPath);
749
752
  if (decoded.startsWith("http")) {
750
- const realUrl = new URL(decoded);
751
- return realUrl.hostname.replace(/^www\./, "");
753
+ return hostOf(decoded);
752
754
  }
753
755
  } catch {
754
756
  }
@@ -1169,16 +1171,9 @@ function extractCitedDomainsFromSources2(groundingSources) {
1169
1171
  return [...domains];
1170
1172
  }
1171
1173
  function extractDomainFromUri2(uri) {
1172
- try {
1173
- const url = new URL(uri);
1174
- const hostname = url.hostname.replace(/^www\./, "").toLowerCase();
1175
- if (AI_ENGINE_SELF_DOMAINS.chatgpt.some((self) => hostname.includes(self))) {
1176
- return null;
1177
- }
1178
- return hostname;
1179
- } catch {
1180
- return null;
1181
- }
1174
+ const hostname = hostOf(uri);
1175
+ if (!hostname || !registrableDomain(hostname) || hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.chatgpt)) return null;
1176
+ return hostname;
1182
1177
  }
1183
1178
  async function generateText2(prompt, config) {
1184
1179
  const model = config.model ?? DEFAULT_MODEL2;
@@ -1538,16 +1533,11 @@ function extractCitedDomainsFromSources3(groundingSources) {
1538
1533
  return [...domains];
1539
1534
  }
1540
1535
  function extractDomainFromUri3(uri) {
1541
- try {
1542
- const url = new URL(uri);
1543
- const hostname = url.hostname.replace(/^www\./, "").toLowerCase();
1544
- if (AI_ENGINE_SELF_DOMAINS.chatgpt.some((self) => hostname.includes(self))) {
1545
- return null;
1546
- }
1547
- return hostname;
1548
- } catch {
1536
+ const hostname = hostOf(uri);
1537
+ if (!hostname || !registrableDomain(hostname) || hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.chatgpt)) {
1549
1538
  return null;
1550
1539
  }
1540
+ return hostname;
1551
1541
  }
1552
1542
  async function generateText3(prompt, config) {
1553
1543
  const model = resolveModel2(config);
@@ -1755,11 +1745,10 @@ async function executeTrackedQuery4(input) {
1755
1745
  }
1756
1746
  function normalizeResult4(raw) {
1757
1747
  const answerText = extractAnswerText2(raw.rawResponse);
1758
- const citedDomains = extractDomainMentions(answerText);
1759
- const groundingSources = citedDomains.map((domain) => ({
1760
- uri: `http://${domain}`,
1761
- title: domain
1762
- }));
1748
+ const groundingSources = raw.groundingSources;
1749
+ const citedDomains = [...new Set(
1750
+ groundingSources.map((source) => hostOf(source.uri)).filter((domain) => domain !== null && registrableDomain(domain).length > 0)
1751
+ )];
1763
1752
  return {
1764
1753
  provider: "local",
1765
1754
  answerText,
@@ -1795,19 +1784,6 @@ async function generateText4(prompt, config) {
1795
1784
  );
1796
1785
  return response.choices[0]?.message?.content ?? "";
1797
1786
  }
1798
- function extractDomainMentions(text) {
1799
- const domains = /* @__PURE__ */ new Set();
1800
- const urlPattern = /https?:\/\/([a-zA-Z0-9][-a-zA-Z0-9]*(?:\.[a-zA-Z0-9][-a-zA-Z0-9]*)+)/g;
1801
- let match;
1802
- while ((match = urlPattern.exec(text)) !== null) {
1803
- domains.add(match[1].replace(/^www\./, "").toLowerCase());
1804
- }
1805
- const domainPattern = /(?:^|[\s(["'])((?:[a-zA-Z0-9][-a-zA-Z0-9]*\.)+(?:com|org|net|io|co|dev|ai|app|edu|gov|biz|info|tech|health|dental|legal|law|med|uk|us|ca|au|de|fr|es|it|nl|se|no|dk|fi|jp|cn|kr|br|mx|ru|in|sg|nz|za)(?:\.[a-zA-Z]{2})?)(?:[\s).,;/"']|$)/g;
1806
- while ((match = domainPattern.exec(text)) !== null) {
1807
- domains.add(match[1].replace(/^www\./, "").toLowerCase());
1808
- }
1809
- return [...domains];
1810
- }
1811
1787
  function extractServedModel4(rawResponse) {
1812
1788
  return normalizeServedModel(rawResponse.model);
1813
1789
  }
@@ -2186,6 +2162,9 @@ var chatgptTarget = {
2186
2162
  const { result } = await client.Runtime.evaluate({
2187
2163
  expression: `(() => {
2188
2164
  const SELF_DOMAINS = ${selfDomainsLiteral};
2165
+ const isSelfDomain = hostname => SELF_DOMAINS.some(
2166
+ self => hostname === self || hostname.endsWith('.' + self)
2167
+ );
2189
2168
  const sources = [];
2190
2169
  const seen = new Set();
2191
2170
  const turns = document.querySelectorAll('[data-message-author-role="assistant"]');
@@ -2209,7 +2188,7 @@ var chatgptTarget = {
2209
2188
  }
2210
2189
  }
2211
2190
 
2212
- if (!seen.has(href) && !SELF_DOMAINS.includes(hostname)) {
2191
+ if (!seen.has(href) && !isSelfDomain(hostname)) {
2213
2192
  seen.add(href);
2214
2193
  sources.push({
2215
2194
  uri: href,
@@ -2226,7 +2205,7 @@ var chatgptTarget = {
2226
2205
  if (href && !seen.has(href)) {
2227
2206
  let hostname = '';
2228
2207
  try { hostname = new URL(href).hostname.replace(/^www\\./, ''); } catch {}
2229
- if (!SELF_DOMAINS.includes(hostname)) {
2208
+ if (!isSelfDomain(hostname)) {
2230
2209
  seen.add(href);
2231
2210
  sources.push({ uri: href, title: title || hostname || href });
2232
2211
  }
@@ -2313,13 +2292,10 @@ async function captureElementScreenshot(client, selector, outputPath) {
2313
2292
  function extractCitedDomains(groundingSources) {
2314
2293
  const domains = /* @__PURE__ */ new Set();
2315
2294
  for (const source of groundingSources) {
2316
- try {
2317
- const url = new URL(source.uri);
2318
- const domain = url.hostname.replace(/^www\./, "").toLowerCase();
2319
- if (!AI_ENGINE_SELF_DOMAINS.chatgpt.some((self) => domain.includes(self))) {
2320
- domains.add(domain);
2321
- }
2322
- } catch {
2295
+ const domain = hostOf(source.uri);
2296
+ if (domain && registrableDomain(domain)) {
2297
+ if (!hostMatchesAnyDomain(domain, AI_ENGINE_SELF_DOMAINS.chatgpt)) domains.add(domain);
2298
+ } else {
2323
2299
  const titleDomain = extractDomainFromTitle2(source.title);
2324
2300
  if (titleDomain) domains.add(titleDomain);
2325
2301
  }
@@ -2327,12 +2303,9 @@ function extractCitedDomains(groundingSources) {
2327
2303
  return [...domains];
2328
2304
  }
2329
2305
  function extractDomainFromTitle2(title) {
2330
- const domainPattern = /^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i;
2331
2306
  const firstWord = title.split(/[\s\-–—|]/)[0]?.trim();
2332
- if (firstWord && domainPattern.test(firstWord)) {
2333
- return firstWord.replace(/^www\./, "").toLowerCase();
2334
- }
2335
- return void 0;
2307
+ if (!firstWord || !registrableDomain(firstWord)) return void 0;
2308
+ return hostOf(firstWord) ?? void 0;
2336
2309
  }
2337
2310
  function normalizeResult5(raw) {
2338
2311
  const answerText = typeof raw.rawResponse.answerText === "string" ? raw.rawResponse.answerText : "";
@@ -2685,16 +2658,9 @@ function extractCitedDomains2(groundingSources) {
2685
2658
  return [...domains];
2686
2659
  }
2687
2660
  function extractDomainFromUri4(uri) {
2688
- try {
2689
- const url = new URL(uri);
2690
- const hostname = url.hostname.replace(/^www\./, "").toLowerCase();
2691
- if (AI_ENGINE_SELF_DOMAINS.chatgpt.some((self) => hostname.includes(self))) {
2692
- return null;
2693
- }
2694
- return hostname;
2695
- } catch {
2696
- return null;
2697
- }
2661
+ const hostname = hostOf(uri);
2662
+ if (!hostname || !registrableDomain(hostname) || hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.chatgpt)) return null;
2663
+ return hostname;
2698
2664
  }
2699
2665
  async function generateText5(prompt, config) {
2700
2666
  const model = config.model ?? DEFAULT_MODEL5;
@@ -5930,7 +5896,7 @@ function aggregateInboundLinksByDomain(links, targetDomain) {
5930
5896
  for (const link of links) {
5931
5897
  const host = hostOf(link.Url);
5932
5898
  if (!host) continue;
5933
- if (target && (host === target || host.endsWith(`.${target}`))) continue;
5899
+ if (target && hostMatchesDomain(host, target)) continue;
5934
5900
  let urls = byHost.get(host);
5935
5901
  if (!urls) {
5936
5902
  urls = /* @__PURE__ */ new Set();
@@ -7066,7 +7032,7 @@ function readStoredGroundingSources(rawResponse) {
7066
7032
  return result;
7067
7033
  }
7068
7034
  async function backfillInsightsCommand(project, opts) {
7069
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-BMEJ5GFZ.js");
7035
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-BD6BHTEJ.js");
7070
7036
  const config = loadConfig();
7071
7037
  const db = createClient(config.database);
7072
7038
  migrate(db);
@@ -10899,16 +10865,6 @@ var FETCH_TIMEOUT_MS2 = 1e4;
10899
10865
  var MAX_TEXT_LENGTH = 4e3;
10900
10866
  var MAX_BODY_BYTES = 512e3;
10901
10867
  var USER_AGENT = "Canonry/1.0 (site-analysis)";
10902
- function extractHostname(domain) {
10903
- let hostname = domain;
10904
- try {
10905
- if (hostname.includes("://")) {
10906
- hostname = new URL(hostname).hostname;
10907
- }
10908
- } catch {
10909
- }
10910
- return hostname.replace(/^www\./, "");
10911
- }
10912
10868
  function fetchWithPinnedAddress(target) {
10913
10869
  return new Promise((resolve) => {
10914
10870
  const port = target.url.port ? Number(target.url.port) : 443;
@@ -10965,7 +10921,8 @@ function fetchWithPinnedAddress(target) {
10965
10921
  });
10966
10922
  }
10967
10923
  async function fetchSiteText(domain) {
10968
- const hostname = extractHostname(domain);
10924
+ const hostname = hostOf(domain);
10925
+ if (!hostname) return "";
10969
10926
  const url = `https://${hostname}`;
10970
10927
  const targetCheck = await resolveWebhookTarget(url);
10971
10928
  if (!targetCheck.ok) return "";
@@ -11084,7 +11041,7 @@ var SnapshotService = class {
11084
11041
  registry;
11085
11042
  async createReport(input) {
11086
11043
  const companyName = input.companyName.trim();
11087
- const domain = normalizeDomain(input.domain);
11044
+ const domain = hostOf(input.domain) ?? input.domain.trim();
11088
11045
  const manualQueries = normalizeStringList(resolveSnapshotRequestQueries(input));
11089
11046
  const manualCompetitors = normalizeStringList(input.competitors ?? []);
11090
11047
  const providers = this.registry.getAll();
@@ -11092,14 +11049,14 @@ var SnapshotService = class {
11092
11049
  throw new Error("No providers configured. Add at least one provider API key before running canonry snapshot.");
11093
11050
  }
11094
11051
  const analysisProvider = pickAnalysisProvider(this.registry.getApiProviders());
11095
- const homepageUrl = `https://${extractHostname2(domain)}`;
11052
+ const homepageUrl = `https://${domain}`;
11096
11053
  const [siteText, audit] = await Promise.all([
11097
11054
  fetchSiteText(domain),
11098
11055
  this.runAudit(homepageUrl)
11099
11056
  ]);
11100
11057
  if (manualQueries.length === 0 && !siteText) {
11101
11058
  throw new Error(
11102
- `Could not analyze https://${extractHostname2(domain)}. Try again with a reachable homepage or pass manual category queries via --queries.`
11059
+ `Could not analyze https://${domain}. Try again with a reachable homepage or pass manual category queries via --queries.`
11103
11060
  );
11104
11061
  }
11105
11062
  const profile = await this.buildProfile({
@@ -11501,36 +11458,30 @@ function buildFallbackRecommendedActions(audit) {
11501
11458
  return uniqueStrings([...weakestFactors, ...defaults]).slice(0, 4);
11502
11459
  }
11503
11460
  function citesTargetDomain(citedDomains, groundingSources, targetDomain) {
11504
- const normalizedTarget = extractHostname2(targetDomain);
11505
11461
  for (const domain of citedDomains) {
11506
- if (domainMatches(domain, normalizedTarget)) {
11462
+ if (hostMatchesDomain(domain, targetDomain)) {
11507
11463
  return true;
11508
11464
  }
11509
11465
  }
11510
11466
  for (const source of groundingSources) {
11511
- if (source.uri && source.uri.toLowerCase().includes(normalizedTarget.toLowerCase())) {
11512
- return true;
11513
- }
11514
- if (source.title && source.title.toLowerCase().includes(normalizedTarget.toLowerCase())) {
11515
- return true;
11516
- }
11467
+ if (hostMatchesDomain(source.uri, targetDomain)) return true;
11468
+ if (source.title && hostMatchesDomain(source.title, targetDomain)) return true;
11517
11469
  }
11518
11470
  return false;
11519
11471
  }
11520
11472
  function extractCompetitorsFromResponse(ctx) {
11521
11473
  const competitors2 = /* @__PURE__ */ new Set();
11522
- const lowerAnswer = ctx.answerText.toLowerCase();
11523
- const targetDomain = extractHostname2(ctx.targetDomain);
11474
+ const targetDomain = hostOf(ctx.targetDomain) ?? ctx.targetDomain;
11524
11475
  for (const hint of ctx.manualCompetitors) {
11525
11476
  if (isDomainLike(hint)) {
11526
- const normalizedHint = normalizeDomain(hint);
11527
- if (domainMatches(normalizedHint, targetDomain)) continue;
11528
- if (ctx.citedDomains.some((domain) => domainMatches(domain, normalizedHint)) || lowerAnswer.includes(normalizedHint.toLowerCase())) {
11477
+ const normalizedHint = hostOf(hint) ?? hint;
11478
+ if (hostMatchesDomain(normalizedHint, targetDomain)) continue;
11479
+ if (ctx.citedDomains.some((domain) => hostMatchesDomain(domain, normalizedHint)) || textContainsDomain(ctx.answerText, normalizedHint)) {
11529
11480
  competitors2.add(normalizedHint);
11530
11481
  }
11531
11482
  continue;
11532
11483
  }
11533
- if (hint.length >= 3 && lowerAnswer.includes(hint.toLowerCase())) {
11484
+ if (hint.length >= 3 && textContainsBrandAlias(ctx.answerText, hint)) {
11534
11485
  competitors2.add(hint);
11535
11486
  }
11536
11487
  }
@@ -11579,27 +11530,8 @@ function uniqueStrings(values) {
11579
11530
  values.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean)
11580
11531
  )];
11581
11532
  }
11582
- function normalizeDomain(value) {
11583
- const trimmed = value.trim();
11584
- if (!trimmed) return trimmed;
11585
- try {
11586
- const url = trimmed.includes("://") ? new URL(trimmed) : new URL(`https://${trimmed}`);
11587
- return url.hostname.replace(/^www\./, "").toLowerCase();
11588
- } catch {
11589
- return trimmed.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^www\./, "").toLowerCase();
11590
- }
11591
- }
11592
- function extractHostname2(value) {
11593
- return normalizeDomain(value);
11594
- }
11595
- function domainMatches(candidate, target) {
11596
- const normalizedCandidate = normalizeDomain(candidate);
11597
- const normalizedTarget = normalizeDomain(target);
11598
- return normalizedCandidate === normalizedTarget || normalizedCandidate.endsWith(`.${normalizedTarget}`);
11599
- }
11600
11533
  function isDomainLike(value) {
11601
- const normalized = normalizeDomain(value);
11602
- return normalized.includes(".") && !normalized.includes(" ");
11534
+ return registrableDomain(value).length > 0;
11603
11535
  }
11604
11536
  function clipText(value, length) {
11605
11537
  if (value.length <= length) return value;
@@ -42,7 +42,7 @@ import {
42
42
  trafficConnectWordpressRequestSchema,
43
43
  trafficEventKindSchema,
44
44
  trafficSeriesGranularitySchema
45
- } from "./chunk-PXEEWJRV.js";
45
+ } from "./chunk-GGIRLRAY.js";
46
46
 
47
47
  // src/config.ts
48
48
  import fs from "fs";
@@ -1087,6 +1087,22 @@ var createClient = (config = {}) => {
1087
1087
  var client = createClient(createConfig());
1088
1088
 
1089
1089
  // ../api-client-generated/src/generated/sdk.gen.ts
1090
+ var postApiV1ProjectsByNameGoogleGscSitemapsSubmit = (options) => {
1091
+ return (options.client ?? client).post({
1092
+ security: [
1093
+ {
1094
+ scheme: "bearer",
1095
+ type: "http"
1096
+ }
1097
+ ],
1098
+ url: "/api/v1/projects/{name}/google/gsc/sitemaps/submit",
1099
+ ...options,
1100
+ headers: {
1101
+ "Content-Type": "application/json",
1102
+ ...options.headers
1103
+ }
1104
+ });
1105
+ };
1090
1106
  var deleteApiV1ProjectsByName = (options) => {
1091
1107
  return (options.client ?? client).delete({
1092
1108
  security: [
@@ -5181,9 +5197,22 @@ var ApiClient = class {
5181
5197
  })
5182
5198
  );
5183
5199
  }
5184
- async gscSitemaps(project) {
5200
+ async gscSitemaps(project, params) {
5201
+ return this.invoke(
5202
+ () => getApiV1ProjectsByNameGoogleGscSitemaps({
5203
+ client: this.heyClient,
5204
+ path: { name: project },
5205
+ query: { sitemapIndex: params?.sitemapIndex }
5206
+ })
5207
+ );
5208
+ }
5209
+ async gscSubmitSitemaps(project, body) {
5185
5210
  return this.invoke(
5186
- () => getApiV1ProjectsByNameGoogleGscSitemaps({ client: this.heyClient, path: { name: project } })
5211
+ () => postApiV1ProjectsByNameGoogleGscSitemapsSubmit({
5212
+ client: this.heyClient,
5213
+ path: { name: project },
5214
+ body
5215
+ })
5187
5216
  );
5188
5217
  }
5189
5218
  async gscDiscoverSitemaps(project) {
@@ -6149,6 +6178,92 @@ var gscCoverageHistoryInputSchema = z2.object({
6149
6178
  project: projectNameSchema,
6150
6179
  limit: z2.number().int().positive().max(500).optional()
6151
6180
  });
6181
+ var gscSitemapsInputSchema = z2.object({
6182
+ project: projectNameSchema,
6183
+ sitemapIndex: z2.string().url().optional()
6184
+ });
6185
+ var gscSitemapsSubmitInputSchema = z2.union([
6186
+ z2.object({
6187
+ project: projectNameSchema,
6188
+ sitemapUrls: z2.array(z2.string().url()).min(1).max(50)
6189
+ }).strict(),
6190
+ z2.object({
6191
+ project: projectNameSchema,
6192
+ mode: z2.enum(["indexes", "all-files"])
6193
+ }).strict()
6194
+ ]);
6195
+ async function submitGscSitemapsFromMcp(client2, input) {
6196
+ let sitemapUrls;
6197
+ if ("sitemapUrls" in input) {
6198
+ sitemapUrls = uniqueStrings(input.sitemapUrls);
6199
+ } else {
6200
+ const topLevel = await client2.gscSitemaps(input.project);
6201
+ if (input.mode === "indexes") {
6202
+ sitemapUrls = uniqueStrings(
6203
+ topLevel.preferredSubmissionUrls.length > 0 ? topLevel.preferredSubmissionUrls : topLevel.sitemaps.map((sitemap) => sitemap.path)
6204
+ );
6205
+ } else {
6206
+ const indexes = topLevel.sitemaps.filter((sitemap) => sitemap.isSitemapsIndex).map((sitemap) => sitemap.path);
6207
+ const expandedIndexUrls = [];
6208
+ for (let offset = 0; offset < indexes.length; offset += 4) {
6209
+ const children = await Promise.all(
6210
+ indexes.slice(offset, offset + 4).map(
6211
+ (sitemapIndex) => client2.gscSitemaps(input.project, { sitemapIndex })
6212
+ )
6213
+ );
6214
+ children.forEach((result, index) => {
6215
+ const sitemapIndex = indexes[offset + index];
6216
+ expandedIndexUrls.push(...result.sitemaps.length > 0 ? result.sitemaps.map((sitemap) => sitemap.path) : [sitemapIndex]);
6217
+ });
6218
+ }
6219
+ sitemapUrls = uniqueStrings([
6220
+ ...topLevel.sitemaps.filter((sitemap) => !sitemap.isSitemapsIndex).map((sitemap) => sitemap.path),
6221
+ ...expandedIndexUrls
6222
+ ]);
6223
+ }
6224
+ }
6225
+ if (sitemapUrls.length === 0) {
6226
+ throw new Error("No GSC sitemaps found. Submit an explicit sitemap URL first.");
6227
+ }
6228
+ const aggregate = {
6229
+ summary: { total: 0, accepted: 0, failed: 0 },
6230
+ results: []
6231
+ };
6232
+ for (let offset = 0; offset < sitemapUrls.length; offset += 50) {
6233
+ const batchSitemapUrls = sitemapUrls.slice(offset, offset + 50);
6234
+ try {
6235
+ const result = await client2.gscSubmitSitemaps(input.project, {
6236
+ sitemapUrls: batchSitemapUrls
6237
+ });
6238
+ aggregate.summary.total += result.summary.total;
6239
+ aggregate.summary.accepted += result.summary.accepted;
6240
+ aggregate.summary.failed += result.summary.failed;
6241
+ aggregate.results.push(...result.results);
6242
+ } catch (cause) {
6243
+ if (offset === 0) throw cause;
6244
+ const attempted = aggregate.summary.total + batchSitemapUrls.length;
6245
+ const remaining = sitemapUrls.length - attempted;
6246
+ throw new CliError({
6247
+ code: "GOOGLE_SITEMAP_SUBMISSION_PARTIAL",
6248
+ message: `Sitemap submission stopped after ${aggregate.summary.total} completed results; ${batchSitemapUrls.length} are unconfirmed and ${remaining} were not attempted.`,
6249
+ exitCode: cause instanceof CliError && cause.exitCode === 1 ? 1 : EXIT_SYSTEM_ERROR,
6250
+ details: {
6251
+ project: input.project,
6252
+ accepted: aggregate.summary.accepted,
6253
+ failed: aggregate.summary.failed,
6254
+ completed: aggregate.summary.total,
6255
+ attempted,
6256
+ unconfirmed: batchSitemapUrls.length,
6257
+ remaining,
6258
+ unconfirmedBatch: { index: Math.floor(offset / 50) + 1, sitemapUrls: batchSitemapUrls },
6259
+ partialResult: aggregate,
6260
+ cause: cause instanceof CliError ? { code: cause.code, message: cause.message, details: cause.details } : { message: cause instanceof Error ? cause.message : String(cause) }
6261
+ }
6262
+ });
6263
+ }
6264
+ }
6265
+ return aggregate;
6266
+ }
6152
6267
  var gaWindowInputSchema = z2.object({
6153
6268
  project: projectNameSchema,
6154
6269
  window: analyticsWindowSchema.optional()
@@ -6486,7 +6601,7 @@ var canonryMcpTools = [
6486
6601
  description: "Get a Canonry project by name.",
6487
6602
  access: "read",
6488
6603
  tier: "core",
6489
- inputSchema: projectInputSchema,
6604
+ inputSchema: gscSitemapsInputSchema,
6490
6605
  annotations: readAnnotations(),
6491
6606
  openApiOperations: ["GET /api/v1/projects/{name}"],
6492
6607
  handler: (client2, input) => client2.getProject(input.project)
@@ -7048,10 +7163,21 @@ var canonryMcpTools = [
7048
7163
  description: "Get sitemap data from Google Search Console for a Canonry project.",
7049
7164
  access: "read",
7050
7165
  tier: "gsc",
7051
- inputSchema: projectInputSchema,
7166
+ inputSchema: gscSitemapsInputSchema,
7052
7167
  annotations: readAnnotations(true),
7053
7168
  openApiOperations: ["GET /api/v1/projects/{name}/google/gsc/sitemaps"],
7054
- handler: (client2, input) => client2.gscSitemaps(input.project)
7169
+ handler: (client2, input) => client2.gscSitemaps(input.project, { sitemapIndex: input.sitemapIndex })
7170
+ }),
7171
+ defineTool({
7172
+ name: "canonry_gsc_sitemaps_submit",
7173
+ title: "Submit GSC sitemaps",
7174
+ description: "Submit up to 50 explicit sitemap URLs, preferred sitemap indexes, or every standalone top-level file plus index child to Google Search Console for refetching. Parent indexes are not redundantly submitted in all-files mode. Google acceptance does not guarantee indexing.",
7175
+ access: "write",
7176
+ tier: "gsc",
7177
+ inputSchema: gscSitemapsSubmitInputSchema,
7178
+ annotations: writeAnnotations({ idempotentHint: false, openWorldHint: true }),
7179
+ openApiOperations: ["POST /api/v1/projects/{name}/google/gsc/sitemaps/submit"],
7180
+ handler: submitGscSitemapsFromMcp
7055
7181
  }),
7056
7182
  defineTool({
7057
7183
  name: "canonry_ga_status",