@ainyc/canonry 4.157.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-D12lHs6n.js → AuditHistoryPanel-BiloGcQA.js} +1 -1
  6. package/assets/assets/{BacklinksPage-D2ScFYru.js → BacklinksPage-CPM1O2bC.js} +1 -1
  7. package/assets/assets/{HistoryPage-jZ_I4rwX.js → HistoryPage-1YOjtdJy.js} +1 -1
  8. package/assets/assets/{MeasurementPropertyPage-BIdQjXJT.js → MeasurementPropertyPage-CVDTY_gC.js} +1 -1
  9. package/assets/assets/ProjectPage-DnGXMx1n.js +12 -0
  10. package/assets/assets/{RunRow-Q2X0NR74.js → RunRow-fIaadH4h.js} +1 -1
  11. package/assets/assets/{RunsPage-Cw7YVsbJ.js → RunsPage-DfifdM5z.js} +1 -1
  12. package/assets/assets/{SettingsPage-bFl2EXOD.js → SettingsPage-efA2EEmS.js} +1 -1
  13. package/assets/assets/SiteHealthSection-BROtQGfu.js +4 -0
  14. package/assets/assets/{TrafficPage-DJjhk7Jw.js → TrafficPage-Wk43Bm_q.js} +1 -1
  15. package/assets/assets/{TrafficSourceDetailPage-CGKwIu9A.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-EzswMBkr.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-MEBL4EJO.js → chunk-5AFWHKKI.js} +875 -38
  38. package/dist/{chunk-AEIKAWOW.js → chunk-BZPBVOAM.js} +753 -212
  39. package/dist/{chunk-KMTRO56O.js → chunk-ZEG43TY2.js} +45 -15
  40. package/dist/{chunk-TRRVBTU5.js → chunk-ZL3VY435.js} +264 -10
  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-YUUPSJRC.js → intelligence-service-DHY5CYWX.js} +2 -2
  45. package/dist/mcp.js +2 -2
  46. package/package.json +12 -10
  47. package/assets/assets/ProjectPage-CLvLOOtj.js +0 -15
  48. package/assets/assets/extract-error-message-BHm98tXH.js +0 -1
  49. package/assets/assets/index-BxegEwic.css +0 -1
  50. package/assets/assets/index-VO6nKq5b.js +0 -83
@@ -81,7 +81,7 @@ import {
81
81
  trafficConnectWordpressRequestSchema,
82
82
  trafficEventKindSchema,
83
83
  trafficSeriesGranularitySchema
84
- } from "./chunk-TRRVBTU5.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([]),
@@ -8561,7 +8568,37 @@ var siteCrawlSummarySchema = z24.object({
8561
8568
  counts: siteCrawlCountsSchema,
8562
8569
  deadLinks: siteCrawlDeadLinksSummarySchema
8563
8570
  });
8564
- var siteHealthStateSchema = z24.enum(["eligible", "hidden", "failed", "unchecked"]);
8571
+ var siteCrawlTerminationSchema = z24.enum([
8572
+ "complete",
8573
+ "unknown",
8574
+ "max-pages",
8575
+ "max-edges",
8576
+ "max-fetches",
8577
+ "max-duration",
8578
+ "max-bytes",
8579
+ "max-page-bytes",
8580
+ "max-depth",
8581
+ "max-links-per-page",
8582
+ "max-query-variants",
8583
+ "max-sitemap-fanout",
8584
+ "max-sitemap-urls",
8585
+ "root-host-redirect"
8586
+ ]);
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
+ ]);
8600
+ var SiteHealthStates = siteHealthStateSchema.enum;
8601
+ var SITE_GRAPH_LEGEND_STATES = siteHealthStateSchema.options;
8565
8602
  var SiteCrawlFetchStates = {
8566
8603
  discovered: "discovered",
8567
8604
  robotsBlocked: "robots-blocked",
@@ -8599,9 +8636,16 @@ function deriveSiteHealthState(input) {
8599
8636
  return "failed";
8600
8637
  case SiteCrawlFetchStates.discovered:
8601
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".
8602
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.
8603
8648
  case SiteCrawlFetchStates.robotsBlocked:
8604
- case SiteCrawlFetchStates.nonHtml:
8605
8649
  return "hidden";
8606
8650
  case SiteCrawlFetchStates.html:
8607
8651
  break;
@@ -8620,6 +8664,56 @@ function deriveSiteHealthState(input) {
8620
8664
  return "unchecked";
8621
8665
  }
8622
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
+ }
8623
8717
  var siteCrawlPageSchema = z24.object({
8624
8718
  nodeKey: z24.string(),
8625
8719
  url: z24.string(),
@@ -8697,12 +8791,15 @@ var siteCrawlPageAuditSchema = z24.discriminatedUnion("state", [
8697
8791
  criticalDefects: z24.array(siteCrawlCriticalDefectSchema).default([])
8698
8792
  })
8699
8793
  ]);
8794
+ var siteCrawlPagesFilterStateSchema = z24.enum(["applied", "unavailable-legacy-scan"]);
8700
8795
  var siteCrawlPagesResponseSchema = z24.object({
8701
8796
  project: z24.string(),
8702
8797
  hasCrawlData: z24.boolean(),
8703
8798
  runId: z24.string().nullable(),
8704
8799
  total: z24.number().int().nonnegative(),
8705
8800
  nextCursor: z24.string().nullable(),
8801
+ /** Null when no `healthState` filter was requested. */
8802
+ healthStateFilter: z24.union([siteCrawlPagesFilterStateSchema, z24.null()]).default(null),
8706
8803
  pages: z24.array(siteCrawlPageSchema).default([])
8707
8804
  });
8708
8805
  var siteCrawlStructureChildSchema = z24.object({
@@ -8734,7 +8831,19 @@ var siteCrawlEdgeSchema = z24.object({
8734
8831
  occurrences: z24.number().int().positive(),
8735
8832
  followableOccurrences: z24.number().int().nonnegative(),
8736
8833
  nofollowOccurrences: z24.number().int().nonnegative(),
8737
- 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()])
8738
8847
  });
8739
8848
  var siteCrawlGraphNodeSchema = z24.object({
8740
8849
  nodeKey: z24.string(),
@@ -8760,29 +8869,69 @@ var siteCrawlGraphEdgeSchema = z24.object({
8760
8869
  sourceNodeKey: z24.string(),
8761
8870
  targetNodeKey: z24.string(),
8762
8871
  followable: z24.boolean(),
8763
- occurrences: z24.number().int().positive()
8764
- });
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()
8879
+ });
8880
+ var siteCrawlGraphLayoutUnavailableReasonSchema = z24.enum([
8881
+ "no-crawl",
8882
+ "legacy-snapshot",
8883
+ "details-unavailable",
8884
+ "layout-failed",
8885
+ "empty-crawl"
8886
+ ]);
8765
8887
  var siteCrawlGraphLayoutSchema = z24.discriminatedUnion("state", [
8766
8888
  z24.object({
8767
8889
  state: z24.literal("ready"),
8768
8890
  version: z24.string().min(1),
8769
- 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()
8770
8900
  }),
8771
8901
  z24.object({
8772
8902
  state: z24.literal("unavailable"),
8773
8903
  version: z24.null(),
8774
- reason: z24.enum(["no-crawl", "legacy-snapshot", "details-unavailable", "layout-failed", "empty-crawl"])
8904
+ reason: siteCrawlGraphLayoutUnavailableReasonSchema
8775
8905
  })
8776
8906
  ]);
8777
8907
  var siteCrawlGraphResponseSchema = z24.object({
8778
8908
  project: z24.string(),
8779
8909
  hasCrawlData: z24.boolean(),
8780
8910
  runId: z24.string().nullable(),
8911
+ /**
8912
+ * Server-owned identity of the crawl root, resolved from the snapshot root
8913
+ * URL (or the depth-0 page). Consumers must not infer the home page from a
8914
+ * path or a link score. It is null when the crawl persisted no page detail,
8915
+ * and it can name a page that graph sampling left out of `nodes`.
8916
+ */
8917
+ rootNodeKey: z24.string().nullable(),
8781
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,
8782
8923
  /** Total canonical pages in the persisted crawl before graph sampling. */
8783
8924
  totalNodes: z24.number().int().nonnegative(),
8784
- /** 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
+ */
8785
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(),
8786
8935
  nodes: z24.array(siteCrawlGraphNodeSchema).default([]),
8787
8936
  edges: z24.array(siteCrawlGraphEdgeSchema).default([]),
8788
8937
  omittedNodes: z24.number().int().nonnegative(),
@@ -8928,8 +9077,13 @@ var siteCrawlInternalLinksResponseSchema = z24.object({
8928
9077
  project: z24.string(),
8929
9078
  hasCrawlData: z24.boolean(),
8930
9079
  runId: z24.string().nullable(),
9080
+ /** Total links matching every requested filter, `linkKind` included. */
8931
9081
  total: z24.number().int().nonnegative(),
8932
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,
8933
9087
  edges: z24.array(siteCrawlEdgeSchema).default([])
8934
9088
  });
8935
9089
  var siteCrawlNeighborsResponseSchema = z24.object({
@@ -8938,6 +9092,10 @@ var siteCrawlNeighborsResponseSchema = z24.object({
8938
9092
  runId: z24.string().nullable(),
8939
9093
  nodeKey: z24.string().nullable(),
8940
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,
8941
9099
  inbound: z24.array(siteCrawlEdgeSchema).default([]),
8942
9100
  outbound: z24.array(siteCrawlEdgeSchema).default([]),
8943
9101
  inboundTruncated: z24.boolean(),
@@ -9020,6 +9178,75 @@ var siteAuditRunResponseSchema = z24.object({
9020
9178
  runId: z24.string(),
9021
9179
  status: runStatusSchema
9022
9180
  });
9181
+ var siteHealthScanSchema = z24.object({
9182
+ runId: z24.string(),
9183
+ status: runStatusSchema,
9184
+ createdAt: z24.string(),
9185
+ startedAt: z24.string().nullable(),
9186
+ finishedAt: z24.string().nullable(),
9187
+ hasCrawlData: z24.boolean()
9188
+ });
9189
+ var siteHealthScansResponseSchema = z24.object({
9190
+ project: z24.string(),
9191
+ scans: z24.array(siteHealthScanSchema).default([])
9192
+ });
9193
+ var SITE_HEALTH_SCANS_DEFAULT_LIMIT = 20;
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
+ });
9023
9250
 
9024
9251
  // ../contracts/src/schedule.ts
9025
9252
  import { z as z25 } from "zod";
@@ -14235,9 +14462,15 @@ function buildEmbedClientConfig(resolved) {
14235
14462
  const client = { enabled: true };
14236
14463
  if (resolved.views && resolved.views.length > 0) client.views = resolved.views;
14237
14464
  if (resolved.projectTabs && resolved.projectTabs.length > 0) client.projectTabs = resolved.projectTabs;
14238
- 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;
14239
14467
  return client;
14240
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
+ }
14241
14474
  function normalizeIdTokens(raw) {
14242
14475
  if (raw.length === 0) return void 0;
14243
14476
  const seen = /* @__PURE__ */ new Set();
@@ -14264,6 +14497,7 @@ function parseThemeOverride(raw) {
14264
14497
  for (const [k, v] of Object.entries(parsed)) {
14265
14498
  if (Object.keys(out).length >= 12) break;
14266
14499
  if (typeof k !== "string" || k.length === 0 || k.length > 32) continue;
14500
+ if (k.toLowerCase() === "font") continue;
14267
14501
  if (typeof v !== "string" || v.length > 256) continue;
14268
14502
  out[k] = v;
14269
14503
  }
@@ -14452,6 +14686,8 @@ export {
14452
14686
  orderLocationsDefaultFirst,
14453
14687
  resolveLocations,
14454
14688
  projectUpsertRequestSchema,
14689
+ projectCreateRequestSchema,
14690
+ normalizeProjectName,
14455
14691
  projectDtoSchema,
14456
14692
  queryDtoSchema,
14457
14693
  keywordDtoSchema,
@@ -14699,8 +14935,20 @@ export {
14699
14935
  siteAuditPagesResponseSchema,
14700
14936
  siteAuditTrendResponseSchema,
14701
14937
  siteCrawlSummarySchema,
14938
+ isSelfLink,
14939
+ siteHealthStateSchema,
14702
14940
  SiteCrawlFetchedStates,
14703
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,
14704
14952
  siteCrawlAuditFactorSchema,
14705
14953
  siteCrawlCriticalDefectSchema,
14706
14954
  siteCrawlPageAuditSchema,
@@ -14734,6 +14982,11 @@ export {
14734
14982
  normalizeSiteAuditRunRequest,
14735
14983
  siteAuditRequestIdentity,
14736
14984
  siteAuditRunResponseSchema,
14985
+ siteHealthScansResponseSchema,
14986
+ SITE_HEALTH_SCANS_DEFAULT_LIMIT,
14987
+ SITE_HEALTH_SCANS_MAX_LIMIT,
14988
+ siteAuditRunProgressSchema,
14989
+ siteAuditLivePageHealthSchema,
14737
14990
  schedulableRunKindSchema,
14738
14991
  SchedulableRunKinds,
14739
14992
  scheduleDtoSchema,
@@ -14973,6 +15226,7 @@ export {
14973
15226
  escapeLikePattern,
14974
15227
  READ_ONLY_SCOPE,
14975
15228
  WILDCARD_SCOPE,
15229
+ PROJECTS_WRITE_SCOPE,
14976
15230
  ADS_WRITE_SCOPE,
14977
15231
  ADS_APPROVE_SCOPE,
14978
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-AEIKAWOW.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-KMTRO56O.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-MEBL4EJO.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-TRRVBTU5.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