@canonry/canonry 4.157.0 → 4.158.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 (26) hide show
  1. package/assets/assets/{AuditHistoryPanel-D12lHs6n.js → AuditHistoryPanel-McQfjoU9.js} +1 -1
  2. package/assets/assets/{BacklinksPage-D2ScFYru.js → BacklinksPage-Da8y1Wv8.js} +1 -1
  3. package/assets/assets/{HistoryPage-jZ_I4rwX.js → HistoryPage-9MJfhRyT.js} +1 -1
  4. package/assets/assets/{MeasurementPropertyPage-BIdQjXJT.js → MeasurementPropertyPage-BiNAHEJW.js} +1 -1
  5. package/assets/assets/ProjectPage-mWSMjXrR.js +15 -0
  6. package/assets/assets/{RunRow-Q2X0NR74.js → RunRow-DCG2LBlS.js} +1 -1
  7. package/assets/assets/{RunsPage-Cw7YVsbJ.js → RunsPage-ByWA_FSk.js} +1 -1
  8. package/assets/assets/{SettingsPage-bFl2EXOD.js → SettingsPage-DdSeKAKF.js} +1 -1
  9. package/assets/assets/{TrafficPage-DJjhk7Jw.js → TrafficPage-5u8rPcBt.js} +1 -1
  10. package/assets/assets/{TrafficSourceDetailPage-CGKwIu9A.js → TrafficSourceDetailPage-Dm-fK87u.js} +1 -1
  11. package/assets/assets/{extract-error-message-BHm98tXH.js → extract-error-message-CtxkAd6E.js} +1 -1
  12. package/assets/assets/{index-VO6nKq5b.js → index-DUfg50Pl.js} +25 -25
  13. package/assets/assets/index-cXb3mfeQ.css +1 -0
  14. package/assets/assets/{react-sigma_core.esm.min-EzswMBkr.js → react-sigma_core.esm.min-ComIyDKi.js} +1 -1
  15. package/assets/index.html +2 -2
  16. package/dist/{chunk-AEIKAWOW.js → chunk-4VZP64GZ.js} +189 -11
  17. package/dist/{chunk-TRRVBTU5.js → chunk-DEVAEC2S.js} +50 -1
  18. package/dist/{chunk-KMTRO56O.js → chunk-LK6YFHG4.js} +1 -1
  19. package/dist/{chunk-MEBL4EJO.js → chunk-YP4K2KXH.js} +114 -21
  20. package/dist/cli.js +4 -4
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-YUUPSJRC.js → intelligence-service-LQ6ZRWMF.js} +2 -2
  23. package/dist/mcp.js +2 -2
  24. package/package.json +11 -10
  25. package/assets/assets/ProjectPage-CLvLOOtj.js +0 -15
  26. package/assets/assets/index-BxegEwic.css +0 -1
@@ -82,6 +82,8 @@ import {
82
82
  SITE_HEALTH_PATH_DEFAULT_MAX_DEPTH,
83
83
  SITE_HEALTH_PATH_MAX_DEPTH,
84
84
  SITE_HEALTH_PATH_MAX_VISITED_NODES,
85
+ SITE_HEALTH_SCANS_DEFAULT_LIMIT,
86
+ SITE_HEALTH_SCANS_MAX_LIMIT,
85
87
  SITE_HEALTH_SUBGRAPH_DEFAULT_MAX_EDGES,
86
88
  SITE_HEALTH_SUBGRAPH_DEFAULT_MAX_NODES,
87
89
  SITE_HEALTH_SUBGRAPH_MAX_EDGES,
@@ -508,6 +510,8 @@ import {
508
510
  siteCrawlSummarySchema,
509
511
  siteHealthChangesResponseSchema,
510
512
  siteHealthPathResponseSchema,
513
+ siteHealthScansResponseSchema,
514
+ siteHealthStateSchema,
511
515
  siteHealthSubgraphResponseSchema,
512
516
  snapshotDiffResponseSchema,
513
517
  snapshotListResponseSchema,
@@ -559,7 +563,7 @@ import {
559
563
  wordpressSchemaDeployResultDtoSchema,
560
564
  wordpressSchemaStatusResultDtoSchema,
561
565
  wordpressStatusDtoSchema
562
- } from "./chunk-TRRVBTU5.js";
566
+ } from "./chunk-DEVAEC2S.js";
563
567
 
564
568
  // src/intelligence-service.ts
565
569
  import { eq as eq58, desc as desc26, asc as asc11, and as and46, ne as ne7, or as or12, inArray as inArray20, gte as gte14, lte as lte11 } from "drizzle-orm";
@@ -8678,7 +8682,7 @@ function isOverviewRead(url) {
8678
8682
  }
8679
8683
  function isTechnicalAeoRead(url) {
8680
8684
  const rest = projectRouteRest(url);
8681
- return rest === "technical-aeo" || rest === "technical-aeo/pages" || rest === "technical-aeo/trend" || rest === "technical-aeo/crawl" || rest === "technical-aeo/graph" || rest === "technical-aeo/subgraph" || rest === "technical-aeo/path" || rest === "technical-aeo/changes" || rest === "technical-aeo/crawl/pages/audit" || rest === "technical-aeo/crawl/pages" || rest === "technical-aeo/structure" || rest === "technical-aeo/internal-links" || rest === "technical-aeo/internal-links/neighbors" || rest === "technical-aeo/dead-links";
8685
+ return rest === "technical-aeo" || rest === "technical-aeo/pages" || rest === "technical-aeo/trend" || rest === "technical-aeo/crawl" || rest === "technical-aeo/graph" || rest === "technical-aeo/subgraph" || rest === "technical-aeo/path" || rest === "technical-aeo/changes" || rest === "technical-aeo/crawl/pages/audit" || rest === "technical-aeo/crawl/pages" || rest === "technical-aeo/runs" || rest === "technical-aeo/structure" || rest === "technical-aeo/internal-links" || rest === "technical-aeo/internal-links/neighbors" || rest === "technical-aeo/dead-links";
8682
8686
  }
8683
8687
  function isReportRead(url) {
8684
8688
  const rest = projectRouteRest(url);
@@ -27412,6 +27416,7 @@ var SCHEMA_TABLE = {
27412
27416
  SiteCrawlSummaryDto: siteCrawlSummarySchema,
27413
27417
  SiteHealthChangesResponseDto: siteHealthChangesResponseSchema,
27414
27418
  SiteHealthPathResponseDto: siteHealthPathResponseSchema,
27419
+ SiteHealthScansResponseDto: siteHealthScansResponseSchema,
27415
27420
  SiteHealthSubgraphResponseDto: siteHealthSubgraphResponseSchema,
27416
27421
  SnapshotDiffResponse: snapshotDiffResponseSchema,
27417
27422
  SnapshotListResponse: snapshotListResponseSchema,
@@ -33396,7 +33401,7 @@ var routeCatalog = [
33396
33401
  ],
33397
33402
  responses: {
33398
33403
  200: jsonResponse("Persisted crawl summary returned.", "SiteCrawlSummaryDto"),
33399
- 404: errorResponse("Project or crawl-bearing site-audit run not found.")
33404
+ 404: errorResponse("Project or site-audit run not found. A known run that published no crawl returns 200 with the no-crawl state instead.")
33400
33405
  }
33401
33406
  },
33402
33407
  {
@@ -33413,7 +33418,7 @@ var routeCatalog = [
33413
33418
  ],
33414
33419
  responses: {
33415
33420
  200: jsonResponse("Bounded Site Health graph projection returned.", "SiteCrawlGraphResponseDto"),
33416
- 404: errorResponse("Project or crawl-bearing site-audit run not found.")
33421
+ 404: errorResponse("Project or site-audit run not found. A known run that published no crawl returns 200 with the no-crawl state instead.")
33417
33422
  }
33418
33423
  },
33419
33424
  {
@@ -33508,6 +33513,7 @@ var routeCatalog = [
33508
33513
  { name: "inventoryEligible", in: "query", description: "Filter Canonry technical inventory eligibility (`true` or `false`).", schema: booleanSchema },
33509
33514
  { name: "fetchState", in: "query", description: "Filter by persisted fetch state.", schema: stringSchema },
33510
33515
  { name: "indexabilityState", in: "query", description: "Filter by crawler-derived indexability state; this is not Google index coverage.", schema: stringSchema },
33516
+ { name: "healthState", in: "query", description: "Filter by the derived Site Health state shared with the dashboard and agents. Unlike `indexabilityState` this folds fetch state, canonical identity, and the crawler reasons into one decision, so `hidden` also covers redirects, robots blocks, non-HTML, and canonical-away pages. Values: `eligible`, `hidden`, `failed`, `unchecked`.", schema: { type: "string", enum: ["eligible", "hidden", "failed", "unchecked"] } },
33511
33517
  { name: "auditState", in: "query", description: "Filter by audit state.", schema: stringSchema },
33512
33518
  { name: "sort", in: "query", description: "Sort order. Defaults to `url`.", schema: { type: "string", enum: ["url", "path", "score-asc", "score-desc"] } },
33513
33519
  crawlCursorParameter,
@@ -33515,7 +33521,7 @@ var routeCatalog = [
33515
33521
  ],
33516
33522
  responses: {
33517
33523
  200: jsonResponse("Crawl pages returned.", "SiteCrawlPagesResponseDto"),
33518
- 404: errorResponse("Project or crawl-bearing site-audit run not found.")
33524
+ 404: errorResponse("Project or site-audit run not found. A known run that published no crawl returns 200 with the no-crawl state instead.")
33519
33525
  }
33520
33526
  },
33521
33527
  {
@@ -33533,7 +33539,7 @@ var routeCatalog = [
33533
33539
  ],
33534
33540
  responses: {
33535
33541
  200: jsonResponse("Immediate crawl children returned.", "SiteCrawlStructureResponseDto"),
33536
- 404: errorResponse("Project or crawl-bearing site-audit run not found.")
33542
+ 404: errorResponse("Project or site-audit run not found. A known run that published no crawl returns 200 with the no-crawl state instead.")
33537
33543
  }
33538
33544
  },
33539
33545
  {
@@ -33553,7 +33559,7 @@ var routeCatalog = [
33553
33559
  ],
33554
33560
  responses: {
33555
33561
  200: jsonResponse("Internal crawl links returned.", "SiteCrawlInternalLinksResponseDto"),
33556
- 404: errorResponse("Project or crawl-bearing site-audit run not found.")
33562
+ 404: errorResponse("Project or site-audit run not found. A known run that published no crawl returns 200 with the no-crawl state instead.")
33557
33563
  }
33558
33564
  },
33559
33565
  {
@@ -33572,7 +33578,7 @@ var routeCatalog = [
33572
33578
  responses: {
33573
33579
  200: jsonResponse("Bounded internal-link neighborhood returned.", "SiteCrawlNeighborsResponseDto"),
33574
33580
  400: errorResponse("A nodeKey or URL is required."),
33575
- 404: errorResponse("Project or crawl-bearing site-audit run not found.")
33581
+ 404: errorResponse("Project or site-audit run not found. A known run that published no crawl returns 200 with the no-crawl state instead.")
33576
33582
  }
33577
33583
  },
33578
33584
  {
@@ -33589,7 +33595,22 @@ var routeCatalog = [
33589
33595
  ],
33590
33596
  responses: {
33591
33597
  200: jsonResponse("Dead-link status and findings returned.", "SiteCrawlDeadLinksResponseDto"),
33592
- 404: errorResponse("Project or crawl-bearing site-audit run not found.")
33598
+ 404: errorResponse("Project or site-audit run not found. A known run that published no crawl returns 200 with the no-crawl state instead.")
33599
+ }
33600
+ },
33601
+ {
33602
+ method: "get",
33603
+ path: "/api/v1/projects/{name}/technical-aeo/runs",
33604
+ summary: "List Site Health scan history",
33605
+ description: "Returns every non-probe `site-audit` run for the project, newest first, including runs still queued or running and runs that failed. `hasCrawlData` says whether that scan published a page and internal-link crawl: a legacy score-only scan is listed with `hasCrawlData=false` rather than hidden, and the crawl-scoped reads answer it with their no-crawl state instead of a 404.",
33606
+ tags: ["technical-aeo"],
33607
+ parameters: [
33608
+ nameParameter,
33609
+ { name: "limit", in: "query", description: "Max scans returned (most recent first). Defaults to 20; max 100.", schema: { type: "integer", minimum: 1, maximum: 100 } }
33610
+ ],
33611
+ responses: {
33612
+ 200: jsonResponse("Site Health scan history returned.", "SiteHealthScansResponseDto"),
33613
+ 404: errorResponse("Project not found.")
33593
33614
  }
33594
33615
  },
33595
33616
  {
@@ -57082,6 +57103,14 @@ function parseBoundedLimit(value, fallback, max) {
57082
57103
  const parsed = parsePositiveInt(value, fallback, max);
57083
57104
  return Math.max(1, parsed);
57084
57105
  }
57106
+ function parseSiteHealthState(value) {
57107
+ if (value === void 0 || value === "") return null;
57108
+ const allowed = siteHealthStateSchema.safeParse(value);
57109
+ if (!allowed.success) {
57110
+ throw validationError(`"healthState" must be one of: ${siteHealthStateSchema.options.join(", ")}`);
57111
+ }
57112
+ return allowed.data;
57113
+ }
57085
57114
  function parseBoolean2(value) {
57086
57115
  if (value === true || value === "true" || value === "1") return true;
57087
57116
  if (value === false || value === "false" || value === "0") return false;
@@ -57317,6 +57346,16 @@ async function technicalAeoRoutes(app, opts) {
57317
57346
  eq56(siteCrawlPages.attemptId, scope.attemptId),
57318
57347
  eq56(siteCrawlPages.depth, 0)
57319
57348
  )).orderBy(asc10(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
57349
+ const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and44(
57350
+ eq56(runs.id, runId),
57351
+ eq56(runs.projectId, projectId),
57352
+ eq56(runs.kind, RunKinds["site-audit"]),
57353
+ inArray19(runs.status, SURFACEABLE_STATUSES),
57354
+ notProbeRun()
57355
+ )).limit(1).get());
57356
+ const assertKnownAuditRun = (projectId, runId) => {
57357
+ if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
57358
+ };
57320
57359
  const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq56(siteAuditSnapshots.runId, runs.id)).where(and44(
57321
57360
  eq56(siteAuditSnapshots.projectId, projectId),
57322
57361
  eq56(runs.projectId, projectId),
@@ -57437,7 +57476,7 @@ async function technicalAeoRoutes(app, opts) {
57437
57476
  const target = resolveCrawl(project.id, request.query.runId);
57438
57477
  const legacyAuditAvailable = hasLegacyAudit(project.id);
57439
57478
  if (!target) {
57440
- if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
57479
+ assertKnownAuditRun(project.id, request.query.runId);
57441
57480
  return emptyCrawlSummary(project.name, legacyAuditAvailable);
57442
57481
  }
57443
57482
  const snapshot = target.snapshot;
@@ -57473,11 +57512,12 @@ async function technicalAeoRoutes(app, opts) {
57473
57512
  const project = resolveProject(app.db, request.params.name);
57474
57513
  const target = resolveCrawl(project.id, request.query.runId);
57475
57514
  if (!target) {
57476
- if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
57515
+ assertKnownAuditRun(project.id, request.query.runId);
57477
57516
  return {
57478
57517
  project: project.name,
57479
57518
  hasCrawlData: false,
57480
57519
  runId: null,
57520
+ rootNodeKey: null,
57481
57521
  layout: { state: "unavailable", version: null, reason: "no-crawl" },
57482
57522
  totalNodes: 0,
57483
57523
  totalEdges: 0,
@@ -57494,6 +57534,7 @@ async function technicalAeoRoutes(app, opts) {
57494
57534
  project: project.name,
57495
57535
  hasCrawlData: true,
57496
57536
  runId: snapshot.runId,
57537
+ rootNodeKey: null,
57497
57538
  layout: { state: "unavailable", version: null, reason: "details-unavailable" },
57498
57539
  totalNodes: 0,
57499
57540
  totalEdges: 0,
@@ -57504,6 +57545,10 @@ async function technicalAeoRoutes(app, opts) {
57504
57545
  sampled: false
57505
57546
  };
57506
57547
  }
57548
+ const rootNodeKey = rootPageInScope(
57549
+ { projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
57550
+ snapshot.rootUrl
57551
+ )?.nodeKey ?? null;
57507
57552
  const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and44(
57508
57553
  eq56(siteCrawlGraphLayouts.projectId, project.id),
57509
57554
  eq56(siteCrawlGraphLayouts.runId, snapshot.runId),
@@ -57514,6 +57559,7 @@ async function technicalAeoRoutes(app, opts) {
57514
57559
  project: project.name,
57515
57560
  hasCrawlData: true,
57516
57561
  runId: snapshot.runId,
57562
+ rootNodeKey,
57517
57563
  layout: { state: "unavailable", version: null, reason: "legacy-snapshot" },
57518
57564
  totalNodes: 0,
57519
57565
  totalEdges: 0,
@@ -57531,6 +57577,7 @@ async function technicalAeoRoutes(app, opts) {
57531
57577
  project: project.name,
57532
57578
  hasCrawlData: true,
57533
57579
  runId: snapshot.runId,
57580
+ rootNodeKey,
57534
57581
  layout: {
57535
57582
  state: "unavailable",
57536
57583
  version: null,
@@ -57611,6 +57658,7 @@ async function technicalAeoRoutes(app, opts) {
57611
57658
  project: project.name,
57612
57659
  hasCrawlData: true,
57613
57660
  runId: snapshot.runId,
57661
+ rootNodeKey,
57614
57662
  layout: { state: "ready", version: persistedLayout.layoutVersion, computedAt: persistedLayout.createdAt },
57615
57663
  totalNodes,
57616
57664
  totalEdges,
@@ -57655,7 +57703,7 @@ async function technicalAeoRoutes(app, opts) {
57655
57703
  truncated: false
57656
57704
  });
57657
57705
  if (!target) {
57658
- if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
57706
+ assertKnownAuditRun(project.id, request.query.runId);
57659
57707
  return empty("no-crawl", null, false, false, null);
57660
57708
  }
57661
57709
  const snapshot = target.snapshot;
@@ -57781,7 +57829,7 @@ async function technicalAeoRoutes(app, opts) {
57781
57829
  }
57782
57830
  const target = resolveCrawl(project.id, request.query.runId);
57783
57831
  if (!target) {
57784
- if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
57832
+ assertKnownAuditRun(project.id, request.query.runId);
57785
57833
  return {
57786
57834
  project: project.name,
57787
57835
  runId: null,
@@ -58253,7 +58301,7 @@ async function technicalAeoRoutes(app, opts) {
58253
58301
  }
58254
58302
  const target = resolveCrawl(project.id, runId);
58255
58303
  if (!target) {
58256
- if (runId) throw notFound("Site crawl run", runId);
58304
+ assertKnownAuditRun(project.id, runId);
58257
58305
  return { state: "no-crawl", project: project.name, runId: null };
58258
58306
  }
58259
58307
  const snapshot = target.snapshot;
@@ -58294,7 +58342,7 @@ async function technicalAeoRoutes(app, opts) {
58294
58342
  const project = resolveProject(app.db, request.params.name);
58295
58343
  const target = resolveCrawl(project.id, request.query.runId);
58296
58344
  if (!target) {
58297
- if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
58345
+ assertKnownAuditRun(project.id, request.query.runId);
58298
58346
  return { project: project.name, hasCrawlData: false, runId: null, total: 0, nextCursor: null, pages: [] };
58299
58347
  }
58300
58348
  const snapshot = target.snapshot;
@@ -58311,12 +58359,25 @@ async function technicalAeoRoutes(app, opts) {
58311
58359
  if (request.query.fetchState) filters.push(eq56(siteCrawlPages.fetchState, request.query.fetchState));
58312
58360
  if (request.query.indexabilityState) filters.push(eq56(siteCrawlPages.indexabilityState, request.query.indexabilityState));
58313
58361
  if (request.query.auditState) filters.push(eq56(siteCrawlPages.auditState, request.query.auditState));
58362
+ const healthState = parseSiteHealthState(request.query.healthState);
58314
58363
  const where = and44(...filters);
58315
- const total = app.db.select({ value: count() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
58316
58364
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
58317
58365
  const offset = decodeCursor(request.query.cursor);
58318
58366
  const orderBy = request.query.sort === "score-desc" ? [desc24(siteCrawlPages.auditScore), asc10(siteCrawlPages.nodeKey)] : request.query.sort === "score-asc" ? [asc10(siteCrawlPages.auditScore), asc10(siteCrawlPages.nodeKey)] : request.query.sort === "path" ? [asc10(siteCrawlPages.path), asc10(siteCrawlPages.nodeKey)] : [asc10(siteCrawlPages.url), asc10(siteCrawlPages.nodeKey)];
58319
- const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
58367
+ let total;
58368
+ let rows;
58369
+ if (healthState) {
58370
+ const candidates = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
58371
+ if (candidates.length > MAX_STRUCTURE_SOURCE_ROWS) {
58372
+ throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page health-state filter limit`);
58373
+ }
58374
+ const matching = candidates.filter((row) => deriveSiteHealthState(row) === healthState);
58375
+ total = matching.length;
58376
+ rows = matching.slice(offset, offset + limit);
58377
+ } else {
58378
+ total = app.db.select({ value: count() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
58379
+ rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
58380
+ }
58320
58381
  const nextOffset = offset + rows.length;
58321
58382
  return {
58322
58383
  project: project.name,
@@ -58332,7 +58393,7 @@ async function technicalAeoRoutes(app, opts) {
58332
58393
  const parentPath = normalizeParentPath(request.query.parentPath);
58333
58394
  const target = resolveCrawl(project.id, request.query.runId);
58334
58395
  if (!target) {
58335
- if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
58396
+ assertKnownAuditRun(project.id, request.query.runId);
58336
58397
  return { project: project.name, hasCrawlData: false, runId: null, parentPath, nextCursor: null, children: [] };
58337
58398
  }
58338
58399
  const snapshot = target.snapshot;
@@ -58398,7 +58459,7 @@ async function technicalAeoRoutes(app, opts) {
58398
58459
  const project = resolveProject(app.db, request.params.name);
58399
58460
  const target = resolveCrawl(project.id, request.query.runId);
58400
58461
  if (!target) {
58401
- if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
58462
+ assertKnownAuditRun(project.id, request.query.runId);
58402
58463
  return { project: project.name, hasCrawlData: false, runId: null, total: 0, nextCursor: null, edges: [] };
58403
58464
  }
58404
58465
  const snapshot = target.snapshot;
@@ -58437,7 +58498,7 @@ async function technicalAeoRoutes(app, opts) {
58437
58498
  }
58438
58499
  const target = resolveCrawl(project.id, request.query.runId);
58439
58500
  if (!target) {
58440
- if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
58501
+ assertKnownAuditRun(project.id, request.query.runId);
58441
58502
  return {
58442
58503
  project: project.name,
58443
58504
  hasCrawlData: false,
@@ -58492,7 +58553,7 @@ async function technicalAeoRoutes(app, opts) {
58492
58553
  const target = resolveCrawl(project.id, request.query.runId);
58493
58554
  const legacyAuditAvailable = hasLegacyAudit(project.id);
58494
58555
  if (!target) {
58495
- if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
58556
+ assertKnownAuditRun(project.id, request.query.runId);
58496
58557
  return { project: project.name, runId: null, state: "unavailable", legacyAuditAvailable };
58497
58558
  }
58498
58559
  const snapshot = target.snapshot;
@@ -58538,6 +58599,38 @@ async function technicalAeoRoutes(app, opts) {
58538
58599
  deadLinks
58539
58600
  };
58540
58601
  });
58602
+ app.get("/projects/:name/technical-aeo/runs", async (request) => {
58603
+ const project = resolveProject(app.db, request.params.name);
58604
+ const limit = parseBoundedLimit(request.query.limit, SITE_HEALTH_SCANS_DEFAULT_LIMIT, SITE_HEALTH_SCANS_MAX_LIMIT);
58605
+ const rows = app.db.select({
58606
+ runId: runs.id,
58607
+ status: runs.status,
58608
+ createdAt: runs.createdAt,
58609
+ startedAt: runs.startedAt,
58610
+ finishedAt: runs.finishedAt
58611
+ }).from(runs).where(and44(
58612
+ eq56(runs.projectId, project.id),
58613
+ eq56(runs.kind, RunKinds["site-audit"]),
58614
+ notProbeRun()
58615
+ )).orderBy(desc24(runs.createdAt), desc24(runs.id)).limit(limit).all();
58616
+ const runIds = rows.map((row) => row.runId);
58617
+ const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq56(siteCrawlSnapshots.runId, runs.id)).where(and44(
58618
+ eq56(siteCrawlSnapshots.projectId, project.id),
58619
+ eq56(runs.projectId, project.id),
58620
+ eq56(runs.kind, RunKinds["site-audit"]),
58621
+ inArray19(runs.status, SURFACEABLE_STATUSES),
58622
+ notProbeRun(),
58623
+ inArray19(siteCrawlSnapshots.runId, runIds)
58624
+ )).all().map((row) => row.runId));
58625
+ return {
58626
+ project: project.name,
58627
+ scans: rows.map((row) => ({
58628
+ ...row,
58629
+ status: row.status,
58630
+ hasCrawlData: crawlRunIds.has(row.runId)
58631
+ }))
58632
+ };
58633
+ });
58541
58634
  app.post("/projects/:name/technical-aeo/runs", async (request) => {
58542
58635
  const project = resolveProject(app.db, request.params.name);
58543
58636
  const parsed = siteAuditRunRequestSchema.safeParse(request.body ?? {});
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-4VZP64GZ.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-LK6YFHG4.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-YP4K2KXH.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-DEVAEC2S.js";
133
133
 
134
134
  // src/cli.ts
135
135
  import { pathToFileURL } from "url";
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-AEIKAWOW.js";
3
+ } from "./chunk-4VZP64GZ.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-KMTRO56O.js";
7
- import "./chunk-MEBL4EJO.js";
8
- import "./chunk-TRRVBTU5.js";
6
+ } from "./chunk-LK6YFHG4.js";
7
+ import "./chunk-YP4K2KXH.js";
8
+ import "./chunk-DEVAEC2S.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-MEBL4EJO.js";
4
- import "./chunk-TRRVBTU5.js";
3
+ } from "./chunk-YP4K2KXH.js";
4
+ import "./chunk-DEVAEC2S.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-KMTRO56O.js";
6
+ } from "./chunk-LK6YFHG4.js";
7
7
  import {
8
8
  isReadOnlyKey
9
- } from "./chunk-TRRVBTU5.js";
9
+ } from "./chunk-DEVAEC2S.js";
10
10
 
11
11
  // src/mcp/cli.ts
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonry/canonry",
3
- "version": "4.157.0",
3
+ "version": "4.158.0",
4
4
  "type": "module",
5
5
  "description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -39,8 +39,8 @@
39
39
  "node": ">=22.14.0 <26"
40
40
  },
41
41
  "dependencies": {
42
- "@canonry/aeo-audit": "4.6.2",
43
42
  "@anthropic-ai/sdk": "^0.91.1",
43
+ "@canonry/aeo-audit": "4.6.2",
44
44
  "@fastify/rate-limit": "^11.2.0",
45
45
  "@fastify/static": "^9.1.1",
46
46
  "@google/genai": "^1.46.0",
@@ -55,6 +55,7 @@
55
55
  "fastify": "^5.8.5",
56
56
  "graphology": "0.26.0",
57
57
  "graphology-layout-forceatlas2": "0.10.1",
58
+ "graphology-layout-noverlap": "0.4.2",
58
59
  "node-cron": "^4.2.1",
59
60
  "openai": "^6.0.0",
60
61
  "pdf-lib": "^1.17.1",
@@ -70,24 +71,24 @@
70
71
  "tsx": "^4.19.0",
71
72
  "@ainyc/canonry-api-client": "0.0.0",
72
73
  "@ainyc/canonry-config": "0.0.0",
73
- "@ainyc/canonry-contracts": "0.0.0",
74
- "@ainyc/canonry-api-routes": "0.0.0",
75
74
  "@ainyc/canonry-db": "0.0.0",
76
- "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
75
+ "@ainyc/canonry-api-routes": "0.0.0",
76
+ "@ainyc/canonry-contracts": "0.0.0",
77
77
  "@ainyc/canonry-integration-bing": "0.0.0",
78
78
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
79
+ "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
80
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
79
81
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
80
- "@ainyc/canonry-integration-google-business-profile": "0.0.0",
81
82
  "@ainyc/canonry-integration-google": "0.0.0",
83
+ "@ainyc/canonry-integration-google-business-profile": "0.0.0",
82
84
  "@ainyc/canonry-integration-google-places": "0.0.0",
83
- "@ainyc/canonry-integration-wordpress": "0.0.0",
84
85
  "@ainyc/canonry-intelligence": "0.0.0",
85
- "@ainyc/canonry-integration-traffic": "0.0.0",
86
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
87
- "@ainyc/canonry-provider-cdp": "0.0.0",
88
86
  "@ainyc/canonry-provider-claude": "0.0.0",
87
+ "@ainyc/canonry-integration-wordpress": "0.0.0",
89
88
  "@ainyc/canonry-provider-gemini": "0.0.0",
89
+ "@ainyc/canonry-provider-cdp": "0.0.0",
90
90
  "@ainyc/canonry-provider-local": "0.0.0",
91
+ "@ainyc/canonry-integration-traffic": "0.0.0",
91
92
  "@ainyc/canonry-provider-openai": "0.0.0",
92
93
  "@ainyc/canonry-provider-perplexity": "0.0.0"
93
94
  },