@canonry/canonry 4.116.0 → 4.117.1

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/README.md +0 -1
  2. package/assets/agent-workspace/skills/aero/references/reporting.md +10 -0
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +6 -0
  4. package/assets/assets/{BacklinksPage-Dy6Xc-LT.js → BacklinksPage-Bkafm10m.js} +1 -1
  5. package/assets/assets/{ChartPrimitives-CHJh5M8x.js → ChartPrimitives-NV_GgqFn.js} +1 -1
  6. package/assets/assets/{ProjectPage-Bjfa4CI5.js → ProjectPage-0-NnRxys.js} +1 -1
  7. package/assets/assets/{RunRow-DmvQnWPf.js → RunRow-df0d24S6.js} +1 -1
  8. package/assets/assets/{RunsPage-BxkxQEKE.js → RunsPage-CU6-4YTM.js} +1 -1
  9. package/assets/assets/{SettingsPage-DCyqHqDh.js → SettingsPage-BKw6B6cd.js} +1 -1
  10. package/assets/assets/{TrafficPage-BT_ae5aO.js → TrafficPage-CzpiYHKj.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-Cps-tSHn.js → TrafficSourceDetailPage-CC5nuqM1.js} +1 -1
  12. package/assets/assets/{arrow-left-D8vdqhx4.js → arrow-left-CbQcW2pL.js} +1 -1
  13. package/assets/assets/{extract-error-message-BOY5txUA.js → extract-error-message-CvqIZ-le.js} +1 -1
  14. package/assets/assets/index-LjcWHaNZ.js +210 -0
  15. package/assets/assets/{trash-2-ap_Fa4zA.js → trash-2-3J2u0woR.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-RVH6QPTA.js → chunk-BMBHUFMI.js} +50 -1
  18. package/dist/{chunk-IQPMK36Y.js → chunk-IAJ5TQ4S.js} +155 -3
  19. package/dist/{chunk-GV665WOU.js → chunk-NY7SC4ES.js} +6 -5
  20. package/dist/{chunk-WEAXJ25Y.js → chunk-V4SW7SVX.js} +1083 -667
  21. package/dist/cli.js +93 -4
  22. package/dist/index.js +4 -4
  23. package/dist/{intelligence-service-HEN5HLUM.js → intelligence-service-C5YLASIP.js} +2 -2
  24. package/dist/mcp.js +2 -2
  25. package/package.json +10 -10
  26. package/assets/assets/index-DxjY63VC.js +0 -210
@@ -1 +1 @@
1
- import{c}from"./index-DxjY63VC.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
1
+ import{c}from"./index-LjcWHaNZ.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
package/assets/index.html CHANGED
@@ -12,7 +12,7 @@
12
12
  <link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
13
13
  <link rel="apple-touch-icon" href="./apple-touch-icon.png" />
14
14
  <title>Canonry</title>
15
- <script type="module" crossorigin src="./assets/index-DxjY63VC.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-LjcWHaNZ.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Be8JdV5M.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/vendor-radix-CQ77EO2y.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/vendor-recharts-C9EZkgbP.js">
@@ -25,7 +25,7 @@ import {
25
25
  trafficConnectVercelRequestSchema,
26
26
  trafficConnectWordpressRequestSchema,
27
27
  trafficEventKindSchema
28
- } from "./chunk-IQPMK36Y.js";
28
+ } from "./chunk-IAJ5TQ4S.js";
29
29
 
30
30
  // src/config.ts
31
31
  import fs from "fs";
@@ -1578,6 +1578,18 @@ var getApiV1ProjectsByNameVisibilityStats = (options) => {
1578
1578
  ...options
1579
1579
  });
1580
1580
  };
1581
+ var getApiV1ProjectsByNameVisibilityCompare = (options) => {
1582
+ return (options.client ?? client).get({
1583
+ security: [
1584
+ {
1585
+ scheme: "bearer",
1586
+ type: "http"
1587
+ }
1588
+ ],
1589
+ url: "/api/v1/projects/{name}/visibility-compare",
1590
+ ...options
1591
+ });
1592
+ };
1581
1593
  var getApiV1ProjectsByNameSnapshotsDiff = (options) => {
1582
1594
  return (options.client ?? client).get({
1583
1595
  security: [
@@ -3774,6 +3786,19 @@ var ApiClient = class {
3774
3786
  `
3775
3787
  );
3776
3788
  }
3789
+ const contentType = result.response.headers.get("content-type");
3790
+ if (result.response.ok && contentType?.toLowerCase().startsWith("text/html")) {
3791
+ throw new CliError({
3792
+ code: "UNEXPECTED_RESPONSE_FORMAT",
3793
+ message: `Expected a JSON response from the canonry API at ${result.request.url}, but received ${contentType} (HTTP ${result.response.status}). Check the server URL and base path.`,
3794
+ exitCode: EXIT_SYSTEM_ERROR,
3795
+ details: {
3796
+ requestUrl: result.request.url,
3797
+ contentType,
3798
+ httpStatus: result.response.status
3799
+ }
3800
+ });
3801
+ }
3777
3802
  if (result.error !== void 0 && result.error !== null) {
3778
3803
  const errorObj = typeof result.error === "object" && result.error !== null && "error" in result.error && typeof result.error.error === "object" && result.error.error !== null ? result.error.error : null;
3779
3804
  const msg = errorObj?.message ? String(errorObj.message) : `HTTP ${result.response.status}: ${result.response.statusText}`;
@@ -5171,6 +5196,15 @@ var ApiClient = class {
5171
5196
  })
5172
5197
  );
5173
5198
  }
5199
+ async getVisibilityCompare(project, from, to) {
5200
+ return this.invoke(
5201
+ () => getApiV1ProjectsByNameVisibilityCompare({
5202
+ client: this.heyClient,
5203
+ path: { name: project },
5204
+ query: { from, to }
5205
+ })
5206
+ );
5207
+ }
5174
5208
  // ── Backlinks — workspace-level ────────────────────────────────────────
5175
5209
  async backlinksStatus() {
5176
5210
  return this.invoke(() => getApiV1BacklinksStatus({ client: this.heyClient }));
@@ -5915,6 +5949,21 @@ var canonryMcpTools = [
5915
5949
  shareOfVoice: input.shareOfVoice
5916
5950
  })
5917
5951
  }),
5952
+ defineTool({
5953
+ name: "canonry_visibility_compare",
5954
+ title: "Compare AEO visibility month over month",
5955
+ description: "Statistically honest month-over-month AEO comparison in ONE call \u2014 use this instead of hand-computing deltas from two visibility-stats calls. Share of voice (`mention-share-of-voice`, `driftRobust: true`) is less exposed to broad model-wide naming propensity than absolute rates, but it never overrides model continuity. The response restricts to common query/provider pairs, then includes only providers with exactly one known, identical configured model id in both months. `continuity` surfaces every provider, its model evidence, and whether it was excluded for a changed, mixed mid-month, or unknown model. When no provider remains, metrics return `model-discontinuous` or `model-unknown`, never a directional call. A silent upstream version bump under an unchanged configured id remains undetectable. `from` must be a month strictly before `to`.",
5956
+ access: "read",
5957
+ tier: "monitoring",
5958
+ inputSchema: z2.object({
5959
+ project: projectNameSchema,
5960
+ from: z2.string().describe('Earlier calendar month (YYYY-MM), the baseline. Must be strictly before "to".'),
5961
+ to: z2.string().describe('Later calendar month (YYYY-MM), compared against "from".')
5962
+ }),
5963
+ annotations: readAnnotations(),
5964
+ openApiOperations: ["GET /api/v1/projects/{name}/visibility-compare"],
5965
+ handler: (client2, input) => client2.getVisibilityCompare(input.project, input.from, input.to)
5966
+ }),
5918
5967
  defineTool({
5919
5968
  name: "canonry_content_targets",
5920
5969
  title: "Get content targets",
@@ -1998,7 +1998,7 @@ var DEDUP_SIMILARITY_BAND_HIGH = 0.97;
1998
1998
  function computeDedupSimilarityStats(vectors, indexClusters) {
1999
1999
  const n = vectors.length;
2000
2000
  if (n < 2) return { perClusterMinSimilarity: [], bandPairFraction: null, pairsTotal: 0 };
2001
- const round = (v) => Math.round(v * 1e4) / 1e4;
2001
+ const round2 = (v) => Math.round(v * 1e4) / 1e4;
2002
2002
  const perClusterMinSimilarity = [];
2003
2003
  for (const cluster of indexClusters) {
2004
2004
  if (cluster.length < 2) continue;
@@ -2008,7 +2008,7 @@ function computeDedupSimilarityStats(vectors, indexClusters) {
2008
2008
  min = Math.min(min, cosineSimilarity(vectors[cluster[a]], vectors[cluster[b]]));
2009
2009
  }
2010
2010
  }
2011
- perClusterMinSimilarity.push(round(min));
2011
+ perClusterMinSimilarity.push(round2(min));
2012
2012
  }
2013
2013
  let inBand = 0;
2014
2014
  let pairsTotal = 0;
@@ -2019,7 +2019,7 @@ function computeDedupSimilarityStats(vectors, indexClusters) {
2019
2019
  if (sim >= DEDUP_SIMILARITY_BAND_LOW && sim < DEDUP_SIMILARITY_BAND_HIGH) inBand++;
2020
2020
  }
2021
2021
  }
2022
- return { perClusterMinSimilarity, bandPairFraction: round(inBand / pairsTotal), pairsTotal };
2022
+ return { perClusterMinSimilarity, bandPairFraction: round2(inBand / pairsTotal), pairsTotal };
2023
2023
  }
2024
2024
 
2025
2025
  // ../contracts/src/url-normalize.ts
@@ -2894,6 +2894,137 @@ var visibilityStatsDtoSchema = z20.object({
2894
2894
  /** Pooled share of voice vs tracked competitors — present only when `shareOfVoice=1` was requested. */
2895
2895
  shareOfVoice: visibilityStatsShareOfVoiceSchema.optional()
2896
2896
  });
2897
+ var visibilityCompareVerdictSchema = z20.enum([
2898
+ "within-noise",
2899
+ "moved",
2900
+ "insufficient-data",
2901
+ "model-discontinuous",
2902
+ "model-unknown"
2903
+ ]);
2904
+ var visibilityCompareDirectionSchema = z20.enum(["up", "down", "flat"]);
2905
+ var visibilityCompareMetricPeriodSchema = z20.object({
2906
+ /** The proportion in `[0,1]`, rounded to 4 dp; `null` when `denominator === 0` (undefined over no data). */
2907
+ point: z20.number().nullable(),
2908
+ /** Wilson 95% lower bound `[0,1]`; `null` when `denominator === 0`. */
2909
+ ciLow: z20.number().nullable(),
2910
+ /** Wilson 95% upper bound `[0,1]`; `null` when `denominator === 0`. */
2911
+ ciHigh: z20.number().nullable(),
2912
+ /** Successes (mentions / citations / project-brand mentions). */
2913
+ numerator: z20.number().int(),
2914
+ /** Sample size the proportion is over (checked snapshots / total / project+competitor brand mentions). */
2915
+ denominator: z20.number().int()
2916
+ });
2917
+ var visibilityCompareMetricKeySchema = z20.enum([
2918
+ "mention-share-of-voice",
2919
+ "cited-share-of-voice",
2920
+ "mention-rate",
2921
+ "cited-rate"
2922
+ ]);
2923
+ var visibilityCompareMetricSchema = z20.object({
2924
+ key: visibilityCompareMetricKeySchema,
2925
+ /** Human label ("Named share of voice", "Cited rate", …). */
2926
+ label: z20.string(),
2927
+ /**
2928
+ * `true` for share of voice, which is less exposed to broad model-wide naming
2929
+ * propensity than an absolute rate. It does not bypass model continuity: no
2930
+ * metric can make a directional call when every provider is excluded.
2931
+ */
2932
+ driftRobust: z20.boolean(),
2933
+ from: visibilityCompareMetricPeriodSchema,
2934
+ to: visibilityCompareMetricPeriodSchema,
2935
+ /** `to.point / from.point`; `null` when `from.point` is `0` or `null` (ratio undefined). */
2936
+ rateRatio: z20.number().nullable(),
2937
+ /** Sign of `to.point - from.point`; `null` when either point is `null`. */
2938
+ direction: z20.union([visibilityCompareDirectionSchema, z20.null()]),
2939
+ verdict: visibilityCompareVerdictSchema
2940
+ });
2941
+ var visibilityComparePeriodWindowSchema = z20.object({
2942
+ /** The `YYYY-MM` requested. */
2943
+ month: z20.string(),
2944
+ /** Resolved inclusive ISO bounds the month expanded to. */
2945
+ since: z20.string(),
2946
+ until: z20.string(),
2947
+ /** Completed/partial, non-probe answer-visibility runs in the month. */
2948
+ runCount: z20.number().int(),
2949
+ /**
2950
+ * `true` when `runCount` is below the reliability floor (`< 5` sweeps): the
2951
+ * intervals are wide and a `moved` verdict is unlikely to be reachable. A
2952
+ * signal to raise the sweep schedule, surfaced so the report can caveat.
2953
+ */
2954
+ lowRunCount: z20.boolean()
2955
+ });
2956
+ var visibilityCompareBasketSchema = z20.object({
2957
+ /** Distinct tracked queries with at least one common provider pair — the compared universe. */
2958
+ queryCount: z20.number().int(),
2959
+ /** Queries observed only in `from` (dropped from the comparison). */
2960
+ excludedFromOnly: z20.number().int(),
2961
+ /** Queries observed only in `to`. */
2962
+ excludedToOnly: z20.number().int(),
2963
+ /**
2964
+ * The compared engine set: providers with ≥1 common query/provider pair.
2965
+ */
2966
+ providers: z20.array(z20.string()),
2967
+ /** Providers observed in either period that did not make the basket (dropped from the comparison). */
2968
+ excludedProviders: z20.array(z20.string())
2969
+ });
2970
+ var visibilityCompareModelChangeSchema = z20.object({
2971
+ provider: z20.string(),
2972
+ fromModels: z20.array(z20.string()),
2973
+ toModels: z20.array(z20.string())
2974
+ });
2975
+ var visibilityCompareProviderContinuityStatusSchema = z20.enum([
2976
+ "included",
2977
+ "model-discontinuous",
2978
+ "model-unknown"
2979
+ ]);
2980
+ var visibilityCompareProviderContinuitySchema = z20.object({
2981
+ provider: z20.string(),
2982
+ /** `included` only when both months carry exactly one known model id, identical across the two, and NO snapshot with an unrecorded (null) model — a null mixed alongside a known id still marks the period `model-unknown`. */
2983
+ status: visibilityCompareProviderContinuityStatusSchema,
2984
+ fromModels: z20.array(z20.string()),
2985
+ toModels: z20.array(z20.string())
2986
+ });
2987
+ var visibilityCompareContinuityStatusSchema = z20.enum([
2988
+ "comparable",
2989
+ "model-discontinuous",
2990
+ "model-unknown",
2991
+ "insufficient-data"
2992
+ ]);
2993
+ var visibilityCompareContinuitySchema = z20.object({
2994
+ status: visibilityCompareContinuityStatusSchema,
2995
+ /** Providers retained in the directional comparison after the continuity gate. */
2996
+ comparedProviders: z20.array(z20.string()),
2997
+ /** Every provider with common query/provider pairs, including excluded ones and their model evidence. */
2998
+ providers: z20.array(visibilityCompareProviderContinuitySchema)
2999
+ });
3000
+ var visibilityCompareProviderRowSchema = z20.object({
3001
+ provider: z20.string(),
3002
+ from: z20.object({ checked: z20.number().int(), mentioned: z20.number().int(), cited: z20.number().int() }),
3003
+ to: z20.object({ checked: z20.number().int(), mentioned: z20.number().int(), cited: z20.number().int() })
3004
+ });
3005
+ var visibilityCompareQueriesMentionedSchema = z20.object({
3006
+ from: z20.object({ count: z20.number().int(), of: z20.number().int() }),
3007
+ to: z20.object({ count: z20.number().int(), of: z20.number().int() })
3008
+ });
3009
+ var visibilityCompareDtoSchema = z20.object({
3010
+ project: z20.string(),
3011
+ from: visibilityComparePeriodWindowSchema,
3012
+ to: visibilityComparePeriodWindowSchema,
3013
+ basket: visibilityCompareBasketSchema,
3014
+ /** Ordered: mention SoV (primary), cited SoV, mention rate (level), cited rate. */
3015
+ metrics: z20.array(visibilityCompareMetricSchema),
3016
+ queriesMentioned: visibilityCompareQueriesMentionedSchema,
3017
+ byProvider: z20.array(visibilityCompareProviderRowSchema),
3018
+ /** Providers whose configured model id set changed between the periods (empty = none detected). */
3019
+ modelChanges: z20.array(visibilityCompareModelChangeSchema),
3020
+ /** Strict model-continuity evidence and the providers that remain comparable. */
3021
+ continuity: visibilityCompareContinuitySchema,
3022
+ /** Per-competitor mention counts within each period's basket (for the SoV detail). */
3023
+ competitors: z20.object({
3024
+ from: z20.array(visibilityStatsShareCompetitorSchema),
3025
+ to: z20.array(visibilityStatsShareCompetitorSchema)
3026
+ })
3027
+ });
2897
3028
  function calendarMonthBounds(month) {
2898
3029
  const match = /^(\d{4})-(\d{2})$/.exec(month);
2899
3030
  if (!match) throw new RangeError("month must be in YYYY-MM format");
@@ -5501,6 +5632,25 @@ function sumInfraHits(segments) {
5501
5632
  return segments.sitemap + segments.robots + segments.asset;
5502
5633
  }
5503
5634
 
5635
+ // ../contracts/src/statistics.ts
5636
+ function round(value, dp = 4) {
5637
+ const f = 10 ** dp;
5638
+ return (Math.round(value * f) + 0) / f;
5639
+ }
5640
+ function wilsonInterval(successes, n, z33 = 1.96) {
5641
+ if (!Number.isFinite(n) || n <= 0) return null;
5642
+ const s = Math.max(0, Math.min(successes, n));
5643
+ const p = s / n;
5644
+ const z210 = z33 * z33;
5645
+ const denom = 1 + z210 / n;
5646
+ const center = (p + z210 / (2 * n)) / denom;
5647
+ const margin = z33 / denom * Math.sqrt(p * (1 - p) / n + z210 / (4 * n * n));
5648
+ return {
5649
+ low: round(Math.max(0, center - margin)),
5650
+ high: round(Math.min(1, center + margin))
5651
+ };
5652
+ }
5653
+
5504
5654
  // ../contracts/src/ads.ts
5505
5655
  import { z as z32 } from "zod";
5506
5656
  var adsConnectRequestSchema = z32.object({
@@ -5984,6 +6134,7 @@ export {
5984
6134
  parseWindow,
5985
6135
  windowCutoff,
5986
6136
  visibilityStatsDtoSchema,
6137
+ visibilityCompareDtoSchema,
5987
6138
  calendarMonthBounds,
5988
6139
  formatRatio,
5989
6140
  formatNumber,
@@ -6097,6 +6248,7 @@ export {
6097
6248
  classifyTrafficPath,
6098
6249
  segmentCrawlerHits,
6099
6250
  sumInfraHits,
6251
+ wilsonInterval,
6100
6252
  adsConnectRequestSchema,
6101
6253
  adsConnectionStatusDtoSchema,
6102
6254
  adsDisconnectResponseSchema,
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-RVH6QPTA.js";
13
+ } from "./chunk-BMBHUFMI.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -115,7 +115,7 @@ import {
115
115
  siteAuditPages,
116
116
  siteAuditSnapshots,
117
117
  usageCounters
118
- } from "./chunk-WEAXJ25Y.js";
118
+ } from "./chunk-V4SW7SVX.js";
119
119
  import {
120
120
  AGENT_MEMORY_VALUE_MAX_BYTES,
121
121
  AGENT_PROVIDER_IDS,
@@ -178,7 +178,7 @@ import {
178
178
  validationError,
179
179
  winnabilityClassLabel,
180
180
  withRetry
181
- } from "./chunk-IQPMK36Y.js";
181
+ } from "./chunk-IAJ5TQ4S.js";
182
182
 
183
183
  // src/telemetry.ts
184
184
  import crypto from "crypto";
@@ -6515,7 +6515,7 @@ function readStoredGroundingSources(rawResponse) {
6515
6515
  return result;
6516
6516
  }
6517
6517
  async function backfillInsightsCommand(project, opts) {
6518
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-HEN5HLUM.js");
6518
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-C5YLASIP.js");
6519
6519
  const config = loadConfig();
6520
6520
  const db = createClient(config.database);
6521
6521
  migrate(db);
@@ -12432,7 +12432,8 @@ async function createServer(opts) {
12432
12432
  const url = request.url.split("?")[0];
12433
12433
  const isApiRoute = url.startsWith("/api/") || basePath !== void 0 && url.startsWith(`${basePath}api/`);
12434
12434
  if (isApiRoute) {
12435
- return reply.status(404).send({ error: "Not found", path: request.url });
12435
+ const error = notFound("API route", url);
12436
+ return reply.status(error.statusCode).send(error.toJSON());
12436
12437
  }
12437
12438
  if (basePath && !url.startsWith(basePath)) {
12438
12439
  return reply.status(404).send({ error: "Not found", path: request.url });