@canonry/canonry 4.158.0 → 4.159.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +2 -2
  2. package/THIRD_PARTY_NOTICES.md +102 -0
  3. package/assets/THIRD_PARTY_NOTICES.md +102 -0
  4. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -0
  5. package/assets/assets/{AuditHistoryPanel-McQfjoU9.js → AuditHistoryPanel-BiloGcQA.js} +1 -1
  6. package/assets/assets/{BacklinksPage-Da8y1Wv8.js → BacklinksPage-CPM1O2bC.js} +1 -1
  7. package/assets/assets/{HistoryPage-9MJfhRyT.js → HistoryPage-1YOjtdJy.js} +1 -1
  8. package/assets/assets/{MeasurementPropertyPage-BiNAHEJW.js → MeasurementPropertyPage-CVDTY_gC.js} +1 -1
  9. package/assets/assets/ProjectPage-DnGXMx1n.js +12 -0
  10. package/assets/assets/{RunRow-DCG2LBlS.js → RunRow-fIaadH4h.js} +1 -1
  11. package/assets/assets/{RunsPage-ByWA_FSk.js → RunsPage-DfifdM5z.js} +1 -1
  12. package/assets/assets/{SettingsPage-DdSeKAKF.js → SettingsPage-efA2EEmS.js} +1 -1
  13. package/assets/assets/SiteHealthSection-BROtQGfu.js +4 -0
  14. package/assets/assets/{TrafficPage-5u8rPcBt.js → TrafficPage-Wk43Bm_q.js} +1 -1
  15. package/assets/assets/{TrafficSourceDetailPage-Dm-fK87u.js → TrafficSourceDetailPage-BhBvoLWu.js} +1 -1
  16. package/assets/assets/extract-error-message-YGzZFvWs.js +1 -0
  17. package/assets/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  18. package/assets/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  19. package/assets/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  20. package/assets/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  21. package/assets/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  22. package/assets/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  23. package/assets/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  24. package/assets/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  25. package/assets/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  26. package/assets/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  27. package/assets/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  28. package/assets/assets/index-CACRaOmZ.css +1 -0
  29. package/assets/assets/index-DmsIZQAm.js +83 -0
  30. package/assets/assets/{react-sigma_core.esm.min-ComIyDKi.js → react-sigma_core.esm.min-DM9XptbM.js} +2 -2
  31. package/assets/assets/{vendor-lucide-BtYdosYk.js → vendor-lucide-nYKv-BB7.js} +1 -1
  32. package/assets/assets/{vendor-markdown-D274_wsI.js → vendor-markdown-Bn7fRwrx.js} +1 -1
  33. package/assets/assets/{vendor-radix-BxMXSwbC.js → vendor-radix-4UOaVWza.js} +1 -1
  34. package/assets/assets/{vendor-recharts-C4ktw_aE.js → vendor-recharts-D8rt5lKn.js} +1 -1
  35. package/assets/assets/{vendor-tanstack-pFxw0eGP.js → vendor-tanstack-Dock0eH1.js} +1 -1
  36. package/assets/index.html +7 -7
  37. package/dist/{chunk-YP4K2KXH.js → chunk-5AFWHKKI.js} +776 -32
  38. package/dist/{chunk-4VZP64GZ.js → chunk-BZPBVOAM.js} +625 -262
  39. package/dist/{chunk-LK6YFHG4.js → chunk-ZEG43TY2.js} +45 -15
  40. package/dist/{chunk-DEVAEC2S.js → chunk-ZL3VY435.js} +213 -8
  41. package/dist/cli.js +42 -4
  42. package/dist/index.d.ts +9 -0
  43. package/dist/index.js +4 -4
  44. package/dist/{intelligence-service-LQ6ZRWMF.js → intelligence-service-DHY5CYWX.js} +2 -2
  45. package/dist/mcp.js +2 -2
  46. package/package.json +11 -10
  47. package/assets/assets/ProjectPage-mWSMjXrR.js +0 -15
  48. package/assets/assets/extract-error-message-CtxkAd6E.js +0 -1
  49. package/assets/assets/index-DUfg50Pl.js +0 -83
  50. package/assets/assets/index-cXb3mfeQ.css +0 -1
@@ -81,7 +81,7 @@ import {
81
81
  trafficConnectWordpressRequestSchema,
82
82
  trafficEventKindSchema,
83
83
  trafficSeriesGranularitySchema
84
- } from "./chunk-DEVAEC2S.js";
84
+ } from "./chunk-ZL3VY435.js";
85
85
 
86
86
  // src/config.ts
87
87
  import fs from "fs";
@@ -1160,6 +1160,18 @@ var postApiV1ProjectsByNameGoogleGscSitemapsSubmit = (options) => {
1160
1160
  }
1161
1161
  });
1162
1162
  };
1163
+ var getApiV1Projects = (options) => {
1164
+ return (options?.client ?? client).get({
1165
+ security: [
1166
+ {
1167
+ scheme: "bearer",
1168
+ type: "http"
1169
+ }
1170
+ ],
1171
+ url: "/api/v1/projects",
1172
+ ...options
1173
+ });
1174
+ };
1163
1175
  var deleteApiV1ProjectsByName = (options) => {
1164
1176
  return (options.client ?? client).delete({
1165
1177
  security: [
@@ -1992,18 +2004,6 @@ var postApiV1ProjectsByNameMeasurementQueryTemplatesByTemplateIdApply = (options
1992
2004
  }
1993
2005
  });
1994
2006
  };
1995
- var getApiV1Projects = (options) => {
1996
- return (options?.client ?? client).get({
1997
- security: [
1998
- {
1999
- scheme: "bearer",
2000
- type: "http"
2001
- }
2002
- ],
2003
- url: "/api/v1/projects",
2004
- ...options
2005
- });
2006
- };
2007
2007
  var getApiV1ProjectsByNameDeletePreview = (options) => {
2008
2008
  return (options.client ?? client).get({
2009
2009
  security: [
@@ -5068,6 +5068,18 @@ var postApiV1ProjectsByNameTechnicalAeoRuns = (options) => {
5068
5068
  }
5069
5069
  });
5070
5070
  };
5071
+ var getApiV1ProjectsByNameTechnicalAeoRunsByRunIdProgress = (options) => {
5072
+ return (options.client ?? client).get({
5073
+ security: [
5074
+ {
5075
+ scheme: "bearer",
5076
+ type: "http"
5077
+ }
5078
+ ],
5079
+ url: "/api/v1/projects/{name}/technical-aeo/runs/{runId}/progress",
5080
+ ...options
5081
+ });
5082
+ };
5071
5083
  var deleteApiV1ProjectsByNameAgentTranscript = (options) => {
5072
5084
  return (options.client ?? client).delete({
5073
5085
  security: [
@@ -7195,6 +7207,15 @@ var ApiClient = class {
7195
7207
  })
7196
7208
  );
7197
7209
  }
7210
+ /** Exact durable counters and lifecycle state for one non-probe site-audit run. */
7211
+ async getTechnicalAeoProgress(project, runId) {
7212
+ return this.invoke(
7213
+ () => getApiV1ProjectsByNameTechnicalAeoRunsByRunIdProgress({
7214
+ client: this.heyClient,
7215
+ path: { name: project, runId }
7216
+ })
7217
+ );
7218
+ }
7198
7219
  /** Persisted full-crawl metadata. This never synthesizes a graph from legacy audit rows. */
7199
7220
  async getTechnicalAeoCrawl(project, opts) {
7200
7221
  return this.invoke(
@@ -7315,6 +7336,7 @@ var ApiClient = class {
7315
7336
  sourceUrl: opts?.sourceUrl,
7316
7337
  targetUrl: opts?.targetUrl,
7317
7338
  followable: opts?.followable,
7339
+ linkKind: opts?.linkKind,
7318
7340
  cursor: opts?.cursor,
7319
7341
  limit: opts?.limit !== void 0 ? String(opts.limit) : void 0
7320
7342
  }
@@ -7331,6 +7353,7 @@ var ApiClient = class {
7331
7353
  runId: opts.runId,
7332
7354
  nodeKey: opts.nodeKey,
7333
7355
  url: opts.url,
7356
+ linkKind: opts.linkKind,
7334
7357
  limit: opts.limit !== void 0 ? String(opts.limit) : void 0
7335
7358
  }
7336
7359
  })
@@ -8593,12 +8616,16 @@ var technicalAeoStructureInputSchema = z3.object({
8593
8616
  cursor: z3.string().min(1).optional().describe("Opaque cursor from the previous structure result."),
8594
8617
  limit: z3.number().int().positive().max(100).optional()
8595
8618
  });
8619
+ var linkKindSchema = z3.enum(["all", "content", "template"]).optional().describe(
8620
+ "Restrict to content links (excludes nav, header, and footer links) or to template links only. Defaults to all. Check templateDetection before reading an empty content list as a real zero."
8621
+ );
8596
8622
  var technicalAeoInternalLinksInputSchema = z3.object({
8597
8623
  project: projectNameSchema,
8598
8624
  runId: runIdSchema.optional(),
8599
8625
  sourceUrl: z3.string().url().optional(),
8600
8626
  targetUrl: z3.string().url().optional(),
8601
8627
  followable: z3.boolean().optional(),
8628
+ linkKind: linkKindSchema,
8602
8629
  cursor: z3.string().min(1).optional().describe("Opaque cursor from the previous internal-links result."),
8603
8630
  limit: z3.number().int().positive().max(200).optional()
8604
8631
  });
@@ -8607,6 +8634,7 @@ var technicalAeoLinkNeighborsInputSchema = z3.object({
8607
8634
  runId: runIdSchema.optional(),
8608
8635
  nodeKey: z3.string().min(1).optional(),
8609
8636
  url: z3.string().url().optional(),
8637
+ linkKind: linkKindSchema,
8610
8638
  limit: z3.number().int().positive().max(100).optional()
8611
8639
  }).refine((value) => Boolean(value.nodeKey || value.url), {
8612
8640
  message: "Provide nodeKey or url.",
@@ -10603,7 +10631,7 @@ var canonryMcpTools = [
10603
10631
  defineTool({
10604
10632
  name: "canonry_technical_aeo_internal_links",
10605
10633
  title: "List Technical AEO internal links",
10606
- description: "Read a bounded, cursor-paged list of persisted internal crawl edges. Filter by source URL, target URL, or followability. Use the neighbors tool for one page rather than loading a graph.",
10634
+ description: "Read a bounded, cursor-paged list of persisted internal crawl edges. Filter by source URL, target URL, followability, or link kind. Nav, header, and footer links are marked isTemplate; templateDetection reports whether that classification ran, so an empty content-only list is not evidence of no content links. Use the neighbors tool for one page rather than loading a graph.",
10607
10635
  access: "read",
10608
10636
  tier: "monitoring",
10609
10637
  inputSchema: technicalAeoInternalLinksInputSchema,
@@ -10614,6 +10642,7 @@ var canonryMcpTools = [
10614
10642
  sourceUrl: input.sourceUrl,
10615
10643
  targetUrl: input.targetUrl,
10616
10644
  followable: input.followable,
10645
+ linkKind: input.linkKind,
10617
10646
  cursor: input.cursor,
10618
10647
  limit: input.limit
10619
10648
  })
@@ -10621,7 +10650,7 @@ var canonryMcpTools = [
10621
10650
  defineTool({
10622
10651
  name: "canonry_technical_aeo_link_neighbors",
10623
10652
  title: "Get Technical AEO page link neighbors",
10624
- description: "Read bounded inbound and outbound internal links for exactly one crawl node, selected by nodeKey or URL. It returns independent truncation flags for inbound and outbound edges, not a transitive traversal.",
10653
+ description: "Read bounded inbound and outbound internal links for exactly one crawl node, selected by nodeKey or URL. Filter by link kind to separate editorial links from nav, header, and footer links. It returns independent truncation flags for inbound and outbound edges, not a transitive traversal.",
10625
10654
  access: "read",
10626
10655
  tier: "monitoring",
10627
10656
  inputSchema: technicalAeoLinkNeighborsInputSchema,
@@ -10631,6 +10660,7 @@ var canonryMcpTools = [
10631
10660
  runId: input.runId,
10632
10661
  nodeKey: input.nodeKey,
10633
10662
  url: input.url,
10663
+ linkKind: input.linkKind,
10634
10664
  limit: input.limit
10635
10665
  })
10636
10666
  }),
@@ -3518,6 +3518,12 @@ var projectUpsertRequestSchema = z3.object({
3518
3518
  autoExtractBacklinks: z3.boolean().optional(),
3519
3519
  configSource: configSourceSchema.optional()
3520
3520
  });
3521
+ var projectCreateRequestSchema = projectUpsertRequestSchema.extend({
3522
+ name: z3.string().trim().min(1).max(120)
3523
+ });
3524
+ function normalizeProjectName(input) {
3525
+ return input.trim().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3526
+ }
3521
3527
  var projectDtoSchema = z3.object({
3522
3528
  id: z3.string(),
3523
3529
  name: z3.string(),
@@ -5428,6 +5434,7 @@ var auditLogEntrySchema = z8.object({
5428
5434
  // ../contracts/src/scopes.ts
5429
5435
  var READ_ONLY_SCOPE = "read";
5430
5436
  var WILDCARD_SCOPE = "*";
5437
+ var PROJECTS_WRITE_SCOPE = "projects.write";
5431
5438
  var ADS_WRITE_SCOPE = "ads.write";
5432
5439
  var ADS_APPROVE_SCOPE = "ads.approve";
5433
5440
  var ADS_ACTIVATE_SCOPE = "ads.activate";
@@ -8486,7 +8493,7 @@ var siteAuditScoreSchema = z24.object({
8486
8493
  pagesErrored: z24.number().int().nonnegative(),
8487
8494
  /** `aggregateScore - previousScore`, or `null` when there is no prior run. */
8488
8495
  deltaScore: z24.number().nullable(),
8489
- trend: siteAuditTrendDirectionSchema.nullable(),
8496
+ trend: z24.union([siteAuditTrendDirectionSchema, z24.null()]),
8490
8497
  previousScore: z24.number().nullable(),
8491
8498
  previousAuditedAt: z24.string().nullable(),
8492
8499
  factors: z24.array(siteAuditFactorSummarySchema).default([]),
@@ -8577,8 +8584,21 @@ var siteCrawlTerminationSchema = z24.enum([
8577
8584
  "max-sitemap-urls",
8578
8585
  "root-host-redirect"
8579
8586
  ]);
8580
- var siteHealthStateSchema = z24.enum(["eligible", "hidden", "failed", "unchecked"]);
8587
+ function isSelfLink(sourceUrl, targetUrl) {
8588
+ return sourceUrl === targetUrl;
8589
+ }
8590
+ var siteHealthStateSchema = z24.enum([
8591
+ "eligible",
8592
+ "hidden",
8593
+ /** Fetched, but not an HTML page: a text file, a PDF, an image. Not a fault. */
8594
+ "resource",
8595
+ /** Fetched and redirected elsewhere. Moved, not hidden. */
8596
+ "redirect",
8597
+ "failed",
8598
+ "unchecked"
8599
+ ]);
8581
8600
  var SiteHealthStates = siteHealthStateSchema.enum;
8601
+ var SITE_GRAPH_LEGEND_STATES = siteHealthStateSchema.options;
8582
8602
  var SiteCrawlFetchStates = {
8583
8603
  discovered: "discovered",
8584
8604
  robotsBlocked: "robots-blocked",
@@ -8616,9 +8636,16 @@ function deriveSiteHealthState(input) {
8616
8636
  return "failed";
8617
8637
  case SiteCrawlFetchStates.discovered:
8618
8638
  return "unchecked";
8639
+ // A non-HTML resource was fetched successfully. It is simply not a page,
8640
+ // which is a fact about its type, not a problem with the site.
8641
+ case SiteCrawlFetchStates.nonHtml:
8642
+ return "resource";
8643
+ // A redirect moved; nothing about it says "do not index me".
8619
8644
  case SiteCrawlFetchStates.redirect:
8645
+ return "redirect";
8646
+ // robots.txt IS the site telling engines not to fetch this, so it is the
8647
+ // one fetch state that genuinely belongs with the hidden pages.
8620
8648
  case SiteCrawlFetchStates.robotsBlocked:
8621
- case SiteCrawlFetchStates.nonHtml:
8622
8649
  return "hidden";
8623
8650
  case SiteCrawlFetchStates.html:
8624
8651
  break;
@@ -8637,6 +8664,56 @@ function deriveSiteHealthState(input) {
8637
8664
  return "unchecked";
8638
8665
  }
8639
8666
  }
8667
+ var TEMPLATE_LINK_RATIO_THRESHOLD = 0.7;
8668
+ var TEMPLATE_LINK_MIN_FETCHED_PAGES = 15;
8669
+ var siteHealthTemplateDetectionSchema = z24.enum([
8670
+ "applied",
8671
+ "unavailable-too-few-pages",
8672
+ "unavailable-legacy-scan"
8673
+ ]);
8674
+ var SiteHealthTemplateDetections = siteHealthTemplateDetectionSchema.enum;
8675
+ var siteHealthLinkKindSchema = z24.enum(["all", "content", "template"]);
8676
+ var SiteHealthLinkKinds = siteHealthLinkKindSchema.enum;
8677
+ function normalizeTemplateAnchorText(anchor) {
8678
+ return anchor.trim().replace(/\s+/gu, " ").toLowerCase();
8679
+ }
8680
+ function createTemplateLinkPairIndex() {
8681
+ return { sourcePagesByPair: /* @__PURE__ */ new Map() };
8682
+ }
8683
+ function templateLinkPairKey(targetNodeKey, normalizedAnchor) {
8684
+ return `${targetNodeKey}${normalizedAnchor}`;
8685
+ }
8686
+ function observeTemplateLinkEdges(index, edges) {
8687
+ for (const edge of edges) {
8688
+ if (edge.targetNodeKey == null) continue;
8689
+ for (const anchor of edge.anchors) {
8690
+ const key = templateLinkPairKey(edge.targetNodeKey, normalizeTemplateAnchorText(anchor));
8691
+ const sources = index.sourcePagesByPair.get(key);
8692
+ if (sources) sources.add(edge.sourceNodeKey);
8693
+ else index.sourcePagesByPair.set(key, /* @__PURE__ */ new Set([edge.sourceNodeKey]));
8694
+ }
8695
+ }
8696
+ }
8697
+ function templateLinkRatio(index, pagesFetched, edge) {
8698
+ if (edge.targetNodeKey == null || edge.anchors.length === 0) return null;
8699
+ if (!Number.isFinite(pagesFetched) || pagesFetched <= 0) return null;
8700
+ let least = null;
8701
+ for (const anchor of edge.anchors) {
8702
+ const sources = index.sourcePagesByPair.get(
8703
+ templateLinkPairKey(edge.targetNodeKey, normalizeTemplateAnchorText(anchor))
8704
+ );
8705
+ if (!sources) continue;
8706
+ const ratio = Math.min(1, sources.size / pagesFetched);
8707
+ if (least == null || ratio < least) least = ratio;
8708
+ }
8709
+ return least == null ? null : Math.round(least * 1e6) / 1e6;
8710
+ }
8711
+ function isTemplateLinkRatio(ratio) {
8712
+ return ratio != null && ratio >= TEMPLATE_LINK_RATIO_THRESHOLD;
8713
+ }
8714
+ function templateLinkDetection(pagesFetched) {
8715
+ return Number.isFinite(pagesFetched) && pagesFetched >= TEMPLATE_LINK_MIN_FETCHED_PAGES ? SiteHealthTemplateDetections.applied : SiteHealthTemplateDetections["unavailable-too-few-pages"];
8716
+ }
8640
8717
  var siteCrawlPageSchema = z24.object({
8641
8718
  nodeKey: z24.string(),
8642
8719
  url: z24.string(),
@@ -8714,12 +8791,15 @@ var siteCrawlPageAuditSchema = z24.discriminatedUnion("state", [
8714
8791
  criticalDefects: z24.array(siteCrawlCriticalDefectSchema).default([])
8715
8792
  })
8716
8793
  ]);
8794
+ var siteCrawlPagesFilterStateSchema = z24.enum(["applied", "unavailable-legacy-scan"]);
8717
8795
  var siteCrawlPagesResponseSchema = z24.object({
8718
8796
  project: z24.string(),
8719
8797
  hasCrawlData: z24.boolean(),
8720
8798
  runId: z24.string().nullable(),
8721
8799
  total: z24.number().int().nonnegative(),
8722
8800
  nextCursor: z24.string().nullable(),
8801
+ /** Null when no `healthState` filter was requested. */
8802
+ healthStateFilter: z24.union([siteCrawlPagesFilterStateSchema, z24.null()]).default(null),
8723
8803
  pages: z24.array(siteCrawlPageSchema).default([])
8724
8804
  });
8725
8805
  var siteCrawlStructureChildSchema = z24.object({
@@ -8751,7 +8831,19 @@ var siteCrawlEdgeSchema = z24.object({
8751
8831
  occurrences: z24.number().int().positive(),
8752
8832
  followableOccurrences: z24.number().int().nonnegative(),
8753
8833
  nofollowOccurrences: z24.number().int().nonnegative(),
8754
- anchors: z24.array(z24.string()).default([])
8834
+ anchors: z24.array(z24.string()).default([]),
8835
+ /**
8836
+ * True when this link repeats across the scan as nav, header, or footer
8837
+ * chrome. Null means the scan never classified its links, which the
8838
+ * response's `templateDetection` explains; it never means "not a nav link".
8839
+ */
8840
+ isTemplate: z24.union([z24.boolean(), z24.null()]),
8841
+ /**
8842
+ * Share of fetched pages carrying this link's LEAST ubiquitous anchor: the
8843
+ * most editorial thing anyone says when linking these two pages. At or above
8844
+ * the threshold means every anchor on the link is chrome.
8845
+ */
8846
+ templateRatio: z24.union([z24.number(), z24.null()])
8755
8847
  });
8756
8848
  var siteCrawlGraphNodeSchema = z24.object({
8757
8849
  nodeKey: z24.string(),
@@ -8777,7 +8869,13 @@ var siteCrawlGraphEdgeSchema = z24.object({
8777
8869
  sourceNodeKey: z24.string(),
8778
8870
  targetNodeKey: z24.string(),
8779
8871
  followable: z24.boolean(),
8780
- occurrences: z24.number().int().positive()
8872
+ occurrences: z24.number().int().positive(),
8873
+ /**
8874
+ * True for a nav, header, or footer link. Template links are published in
8875
+ * the sample so a viewer can switch them on without a refetch, but they were
8876
+ * excluded from the layout, so switching them on must never move a node.
8877
+ */
8878
+ isTemplate: z24.boolean()
8781
8879
  });
8782
8880
  var siteCrawlGraphLayoutUnavailableReasonSchema = z24.enum([
8783
8881
  "no-crawl",
@@ -8790,7 +8888,15 @@ var siteCrawlGraphLayoutSchema = z24.discriminatedUnion("state", [
8790
8888
  z24.object({
8791
8889
  state: z24.literal("ready"),
8792
8890
  version: z24.string().min(1),
8793
- computedAt: z24.string().min(1)
8891
+ computedAt: z24.string().min(1),
8892
+ /**
8893
+ * True when nav, header, and footer links were kept out of the physics, so
8894
+ * positions describe content structure. False for a scan published before
8895
+ * template detection: its links are classified by the migration backfill
8896
+ * and can still be filtered, but its node positions were computed with the
8897
+ * nav mesh included. Re-running the scan is what updates them.
8898
+ */
8899
+ templateLinksExcluded: z24.boolean()
8794
8900
  }),
8795
8901
  z24.object({
8796
8902
  state: z24.literal("unavailable"),
@@ -8810,10 +8916,22 @@ var siteCrawlGraphResponseSchema = z24.object({
8810
8916
  */
8811
8917
  rootNodeKey: z24.string().nullable(),
8812
8918
  layout: siteCrawlGraphLayoutSchema,
8919
+ /** Whether nav and footer links could be told apart for this scan. */
8920
+ templateDetection: siteHealthTemplateDetectionSchema,
8921
+ /** Echo of the requested `linkKind`, so a caller knows what `edges` holds. */
8922
+ linkKind: siteHealthLinkKindSchema,
8813
8923
  /** Total canonical pages in the persisted crawl before graph sampling. */
8814
8924
  totalNodes: z24.number().int().nonnegative(),
8815
- /** Total graph-compatible internal anchor edges before graph sampling. */
8925
+ /**
8926
+ * Total graph-compatible internal anchor edges before graph sampling. This
8927
+ * keeps counting EVERY link, whatever `linkKind` was requested, so the
8928
+ * content and template counts below split a number that never moved.
8929
+ */
8816
8930
  totalEdges: z24.number().int().nonnegative(),
8931
+ /** Share of `totalEdges` classified as nav, header, or footer links. */
8932
+ totalTemplateEdges: z24.number().int().nonnegative(),
8933
+ /** `totalEdges - totalTemplateEdges`. Equals `totalEdges` when detection is unavailable. */
8934
+ totalContentEdges: z24.number().int().nonnegative(),
8817
8935
  nodes: z24.array(siteCrawlGraphNodeSchema).default([]),
8818
8936
  edges: z24.array(siteCrawlGraphEdgeSchema).default([]),
8819
8937
  omittedNodes: z24.number().int().nonnegative(),
@@ -8959,8 +9077,13 @@ var siteCrawlInternalLinksResponseSchema = z24.object({
8959
9077
  project: z24.string(),
8960
9078
  hasCrawlData: z24.boolean(),
8961
9079
  runId: z24.string().nullable(),
9080
+ /** Total links matching every requested filter, `linkKind` included. */
8962
9081
  total: z24.number().int().nonnegative(),
8963
9082
  nextCursor: z24.string().nullable(),
9083
+ /** Whether nav and footer links could be told apart for this scan. */
9084
+ templateDetection: siteHealthTemplateDetectionSchema,
9085
+ /** Echo of the requested `linkKind`, so a caller knows what `total` counts. */
9086
+ linkKind: siteHealthLinkKindSchema,
8964
9087
  edges: z24.array(siteCrawlEdgeSchema).default([])
8965
9088
  });
8966
9089
  var siteCrawlNeighborsResponseSchema = z24.object({
@@ -8969,6 +9092,10 @@ var siteCrawlNeighborsResponseSchema = z24.object({
8969
9092
  runId: z24.string().nullable(),
8970
9093
  nodeKey: z24.string().nullable(),
8971
9094
  url: z24.string().nullable(),
9095
+ /** Whether nav and footer links could be told apart for this scan. */
9096
+ templateDetection: siteHealthTemplateDetectionSchema,
9097
+ /** Echo of the requested `linkKind`, so a caller knows what the lists hold. */
9098
+ linkKind: siteHealthLinkKindSchema,
8972
9099
  inbound: z24.array(siteCrawlEdgeSchema).default([]),
8973
9100
  outbound: z24.array(siteCrawlEdgeSchema).default([]),
8974
9101
  inboundTruncated: z24.boolean(),
@@ -9065,6 +9192,61 @@ var siteHealthScansResponseSchema = z24.object({
9065
9192
  });
9066
9193
  var SITE_HEALTH_SCANS_DEFAULT_LIMIT = 20;
9067
9194
  var SITE_HEALTH_SCANS_MAX_LIMIT = 100;
9195
+ var siteAuditRunPhaseSchema = z24.enum([
9196
+ "queued",
9197
+ "discovering",
9198
+ "checking",
9199
+ "arranging-map",
9200
+ "completed",
9201
+ "partial",
9202
+ "failed",
9203
+ "cancelled"
9204
+ ]);
9205
+ var siteAuditRunLayoutSchema = z24.object({
9206
+ state: z24.enum(["pending", "ready", "unavailable"]),
9207
+ layoutVersion: z24.string().nullable(),
9208
+ failureCode: z24.string().nullable(),
9209
+ updatedAt: z24.string().nullable()
9210
+ });
9211
+ var siteAuditRunAttemptProgressSchema = z24.object({
9212
+ id: z24.string(),
9213
+ state: z24.string(),
9214
+ pagesDiscovered: z24.number().int().nonnegative(),
9215
+ pagesFetched: z24.number().int().nonnegative(),
9216
+ pagesEligible: z24.number().int().nonnegative(),
9217
+ pagesErrored: z24.number().int().nonnegative(),
9218
+ edgesDiscovered: z24.number().int().nonnegative(),
9219
+ lastUpdatedAt: z24.string(),
9220
+ startedAt: z24.string().nullable(),
9221
+ finishedAt: z24.string().nullable(),
9222
+ error: z24.string().nullable()
9223
+ });
9224
+ var siteAuditRunProgressSchema = z24.object({
9225
+ project: z24.string(),
9226
+ runId: z24.string(),
9227
+ status: runStatusSchema,
9228
+ phase: siteAuditRunPhaseSchema,
9229
+ attempt: siteAuditRunAttemptProgressSchema.nullable(),
9230
+ layout: siteAuditRunLayoutSchema,
9231
+ error: z24.string().nullable()
9232
+ });
9233
+ var siteAuditLivePageHealthStateSchema = z24.enum(["waiting", "collecting", "terminal"]);
9234
+ var siteAuditLivePageHealthExampleSchema = z24.object({
9235
+ nodeKey: z24.string(),
9236
+ url: z24.string(),
9237
+ auditScore: z24.number(),
9238
+ checksNeedingAttention: z24.number().int().nonnegative()
9239
+ });
9240
+ var siteAuditLivePageHealthSchema = z24.object({
9241
+ project: z24.string(),
9242
+ runId: z24.string(),
9243
+ status: runStatusSchema,
9244
+ state: siteAuditLivePageHealthStateSchema,
9245
+ attemptId: z24.string().nullable(),
9246
+ pagesAudited: z24.number().int().nonnegative(),
9247
+ updatedAt: z24.string().nullable(),
9248
+ examples: z24.array(siteAuditLivePageHealthExampleSchema).max(12).default([])
9249
+ });
9068
9250
 
9069
9251
  // ../contracts/src/schedule.ts
9070
9252
  import { z as z25 } from "zod";
@@ -14280,9 +14462,15 @@ function buildEmbedClientConfig(resolved) {
14280
14462
  const client = { enabled: true };
14281
14463
  if (resolved.views && resolved.views.length > 0) client.views = resolved.views;
14282
14464
  if (resolved.projectTabs && resolved.projectTabs.length > 0) client.projectTabs = resolved.projectTabs;
14283
- if (resolved.theme && Object.keys(resolved.theme).length > 0) client.theme = resolved.theme;
14465
+ const theme = withoutFontOverride(resolved.theme);
14466
+ if (theme) client.theme = theme;
14284
14467
  return client;
14285
14468
  }
14469
+ function withoutFontOverride(theme) {
14470
+ if (!theme) return void 0;
14471
+ const entries = Object.entries(theme).filter(([key]) => key.toLowerCase() !== "font");
14472
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
14473
+ }
14286
14474
  function normalizeIdTokens(raw) {
14287
14475
  if (raw.length === 0) return void 0;
14288
14476
  const seen = /* @__PURE__ */ new Set();
@@ -14309,6 +14497,7 @@ function parseThemeOverride(raw) {
14309
14497
  for (const [k, v] of Object.entries(parsed)) {
14310
14498
  if (Object.keys(out).length >= 12) break;
14311
14499
  if (typeof k !== "string" || k.length === 0 || k.length > 32) continue;
14500
+ if (k.toLowerCase() === "font") continue;
14312
14501
  if (typeof v !== "string" || v.length > 256) continue;
14313
14502
  out[k] = v;
14314
14503
  }
@@ -14497,6 +14686,8 @@ export {
14497
14686
  orderLocationsDefaultFirst,
14498
14687
  resolveLocations,
14499
14688
  projectUpsertRequestSchema,
14689
+ projectCreateRequestSchema,
14690
+ normalizeProjectName,
14500
14691
  projectDtoSchema,
14501
14692
  queryDtoSchema,
14502
14693
  keywordDtoSchema,
@@ -14744,9 +14935,20 @@ export {
14744
14935
  siteAuditPagesResponseSchema,
14745
14936
  siteAuditTrendResponseSchema,
14746
14937
  siteCrawlSummarySchema,
14938
+ isSelfLink,
14747
14939
  siteHealthStateSchema,
14748
14940
  SiteCrawlFetchedStates,
14749
14941
  deriveSiteHealthState,
14942
+ TEMPLATE_LINK_RATIO_THRESHOLD,
14943
+ siteHealthTemplateDetectionSchema,
14944
+ SiteHealthTemplateDetections,
14945
+ siteHealthLinkKindSchema,
14946
+ SiteHealthLinkKinds,
14947
+ createTemplateLinkPairIndex,
14948
+ observeTemplateLinkEdges,
14949
+ templateLinkRatio,
14950
+ isTemplateLinkRatio,
14951
+ templateLinkDetection,
14750
14952
  siteCrawlAuditFactorSchema,
14751
14953
  siteCrawlCriticalDefectSchema,
14752
14954
  siteCrawlPageAuditSchema,
@@ -14783,6 +14985,8 @@ export {
14783
14985
  siteHealthScansResponseSchema,
14784
14986
  SITE_HEALTH_SCANS_DEFAULT_LIMIT,
14785
14987
  SITE_HEALTH_SCANS_MAX_LIMIT,
14988
+ siteAuditRunProgressSchema,
14989
+ siteAuditLivePageHealthSchema,
14786
14990
  schedulableRunKindSchema,
14787
14991
  SchedulableRunKinds,
14788
14992
  scheduleDtoSchema,
@@ -15022,6 +15226,7 @@ export {
15022
15226
  escapeLikePattern,
15023
15227
  READ_ONLY_SCOPE,
15024
15228
  WILDCARD_SCOPE,
15229
+ PROJECTS_WRITE_SCOPE,
15025
15230
  ADS_WRITE_SCOPE,
15026
15231
  ADS_APPROVE_SCOPE,
15027
15232
  ADS_ACTIVATE_SCOPE,
package/dist/cli.js CHANGED
@@ -31,7 +31,7 @@ import {
31
31
  showFirstRunNotice,
32
32
  trackCliCommandFinished,
33
33
  trackEvent
34
- } from "./chunk-4VZP64GZ.js";
34
+ } from "./chunk-BZPBVOAM.js";
35
35
  import {
36
36
  CliError,
37
37
  EXIT_SYSTEM_ERROR,
@@ -51,7 +51,7 @@ import {
51
51
  saveConfigPatch,
52
52
  systemError,
53
53
  usageError
54
- } from "./chunk-LK6YFHG4.js";
54
+ } from "./chunk-ZEG43TY2.js";
55
55
  import {
56
56
  CLOUDFLARE_WORKER_BINDINGS,
57
57
  CLOUDFLARE_WORKER_GENERATED_MARKER,
@@ -65,7 +65,7 @@ import {
65
65
  projects,
66
66
  queries,
67
67
  renderReportHtml
68
- } from "./chunk-YP4K2KXH.js";
68
+ } from "./chunk-5AFWHKKI.js";
69
69
  import {
70
70
  AdsDeliverySnapshotStatuses,
71
71
  AdsHistoricalCampaignRollupStatuses,
@@ -129,7 +129,7 @@ import {
129
129
  providerQuotaPolicySchema,
130
130
  resolveProviderInput,
131
131
  winnabilityClassSchema
132
- } from "./chunk-DEVAEC2S.js";
132
+ } from "./chunk-ZL3VY435.js";
133
133
 
134
134
  // src/cli.ts
135
135
  import { pathToFileURL } from "url";
@@ -14354,6 +14354,29 @@ async function technicalAeoTrend(project, opts) {
14354
14354
  }
14355
14355
  console.log(lines.join("\n"));
14356
14356
  }
14357
+ async function technicalAeoProgress(project, opts) {
14358
+ const progress = await getClient27().getTechnicalAeoProgress(project, opts.runId);
14359
+ if (isMachineFormat(opts.format)) {
14360
+ console.log(JSON.stringify(progress, null, 2));
14361
+ return;
14362
+ }
14363
+ const lines = [
14364
+ `Site Health progress: ${progress.phase} (${progress.status})`,
14365
+ `Run: ${progress.runId}`
14366
+ ];
14367
+ if (progress.attempt) {
14368
+ lines.push(
14369
+ `Pages: ${progress.attempt.pagesDiscovered} found \xB7 ${progress.attempt.pagesFetched} checked \xB7 ${progress.attempt.pagesErrored} failed`,
14370
+ `Eligible: ${progress.attempt.pagesEligible} \xB7 Links found: ${progress.attempt.edgesDiscovered}`,
14371
+ `Updated: ${progress.attempt.lastUpdatedAt}`
14372
+ );
14373
+ } else {
14374
+ lines.push("Pages: waiting for the crawl to start");
14375
+ }
14376
+ lines.push(`Layout: ${progress.layout.state}`);
14377
+ if (progress.error) lines.push(`Error: ${progress.error}`);
14378
+ console.log(lines.join("\n"));
14379
+ }
14357
14380
  async function technicalAeoCrawl(project, opts) {
14358
14381
  const crawl = await getClient27().getTechnicalAeoCrawl(project, { runId: opts.runId });
14359
14382
  if (isMachineFormat(opts.format)) {
@@ -14848,6 +14871,21 @@ var TECHNICAL_AEO_CLI_COMMANDS_BASE = [
14848
14871
  await technicalAeoCrawl(project, { runId: getString(input.values, "run-id"), format: input.format });
14849
14872
  }
14850
14873
  },
14874
+ {
14875
+ path: ["technical-aeo", "progress"],
14876
+ usage: "canonry technical-aeo progress <project> --run-id <id> [--format json]",
14877
+ options: { "run-id": stringOption() },
14878
+ run: async (input) => {
14879
+ const usage = "canonry technical-aeo progress <project> --run-id <id> [--format json]";
14880
+ const project = requireProject(input, "technical-aeo.progress", usage);
14881
+ const runId = requireStringOption(input, "run-id", {
14882
+ command: "technical-aeo.progress",
14883
+ usage,
14884
+ message: "--run-id is required"
14885
+ });
14886
+ await technicalAeoProgress(project, { runId, format: input.format });
14887
+ }
14888
+ },
14851
14889
  {
14852
14890
  path: ["technical-aeo", "subgraph"],
14853
14891
  usage: "canonry technical-aeo subgraph <project> [--run-id <id>] [--node-key <key>|--url <url>] [--hops <n>] [--max-nodes <n>] [--max-edges <n>] [--format json]",
package/dist/index.d.ts CHANGED
@@ -215,6 +215,15 @@ interface AgentConfigEntry {
215
215
  mode?: 'disabled';
216
216
  }
217
217
  interface DashboardConfigEntry {
218
+ /**
219
+ * First-open dashboard experience. `legacy` preserves the existing setup
220
+ * wizard, `platform` enables the domain-first Site Health launchpad, and
221
+ * `auto` enables the launchpad only when the authoritative project list is
222
+ * empty. Omitted defaults to `auto`: fresh installs use the launchpad after
223
+ * an authoritative empty project-list response, while existing installs
224
+ * retain the legacy wizard.
225
+ */
226
+ onboardingMode?: 'legacy' | 'platform' | 'auto';
218
227
  /**
219
228
  * Whether the browser dashboard requires Canonry's built-in password/session
220
229
  * gate. Defaults to true. Set false only when an upstream layer enforces auth
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-4VZP64GZ.js";
3
+ } from "./chunk-BZPBVOAM.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-LK6YFHG4.js";
7
- import "./chunk-YP4K2KXH.js";
8
- import "./chunk-DEVAEC2S.js";
6
+ } from "./chunk-ZEG43TY2.js";
7
+ import "./chunk-5AFWHKKI.js";
8
+ import "./chunk-ZL3VY435.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-YP4K2KXH.js";
4
- import "./chunk-DEVAEC2S.js";
3
+ } from "./chunk-5AFWHKKI.js";
4
+ import "./chunk-ZL3VY435.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  PACKAGE_VERSION,
4
4
  canonryMcpTools,
5
5
  createApiClient
6
- } from "./chunk-LK6YFHG4.js";
6
+ } from "./chunk-ZEG43TY2.js";
7
7
  import {
8
8
  isReadOnlyKey
9
- } from "./chunk-DEVAEC2S.js";
9
+ } from "./chunk-ZL3VY435.js";
10
10
 
11
11
  // src/mcp/cli.ts
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";