@ainyc/canonry 4.87.0 → 4.89.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 (28) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +16 -0
  2. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +19 -0
  3. package/assets/assets/{BacklinksPage-BPvsw_Bi.js → BacklinksPage-Cl3V55iK.js} +1 -1
  4. package/assets/assets/{ChartPrimitives-BdlKCq7y.js → ChartPrimitives-D4D_lSIp.js} +1 -1
  5. package/assets/assets/{ProjectPage-1Q8YC9Vd.js → ProjectPage-Dwwcddk2.js} +1 -1
  6. package/assets/assets/{RunRow-DcSsnE5c.js → RunRow-CNjwUkqj.js} +1 -1
  7. package/assets/assets/{RunsPage-DKoIMkQL.js → RunsPage-owFYxiRU.js} +1 -1
  8. package/assets/assets/{SettingsPage-bH3PdNKb.js → SettingsPage-D3rc2Hze.js} +1 -1
  9. package/assets/assets/{TrafficPage-IW_DX-0V.js → TrafficPage-BDkhe0Iz.js} +1 -1
  10. package/assets/assets/TrafficSourceDetailPage-CUw6Odt1.js +1 -0
  11. package/assets/assets/{arrow-left-B5Du72nk.js → arrow-left-BAOMqvGW.js} +1 -1
  12. package/assets/assets/{extract-error-message-C7Vhd5zH.js → extract-error-message-BO0DeR7V.js} +1 -1
  13. package/assets/assets/{index-C_ZzKZfM.js → index-BbnglhBg.js} +79 -79
  14. package/assets/assets/index-fLUYE9Z5.css +1 -0
  15. package/assets/assets/{trash-2-DWcofmpv.js → trash-2-RQK6oqQJ.js} +1 -1
  16. package/assets/index.html +2 -2
  17. package/dist/{chunk-MDRDX5R2.js → chunk-LRO4CLPL.js} +230 -1
  18. package/dist/{chunk-DUDFNP5Y.js → chunk-MUPIPQBB.js} +50 -7
  19. package/dist/{chunk-6XMXBAEW.js → chunk-PRKBJNAG.js} +1 -1
  20. package/dist/{chunk-5LW7CJAO.js → chunk-WEV4RH25.js} +30 -6
  21. package/dist/cli.js +44 -14
  22. package/dist/index.d.ts +9 -1
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-XUKYOHKL.js → intelligence-service-SO46DMGR.js} +2 -2
  25. package/dist/mcp.js +2 -2
  26. package/package.json +8 -8
  27. package/assets/assets/TrafficSourceDetailPage-DRHOGn9B.js +0 -1
  28. package/assets/assets/index-ClkRAeHL.css +0 -1
@@ -4555,6 +4555,20 @@ function coerceSkillManifest(parsed) {
4555
4555
 
4556
4556
  // ../contracts/src/traffic.ts
4557
4557
  import { z as z30 } from "zod";
4558
+ var trafficCrawlerSegmentsSchema = z30.object({
4559
+ content: z30.number().int().nonnegative(),
4560
+ sitemap: z30.number().int().nonnegative(),
4561
+ robots: z30.number().int().nonnegative(),
4562
+ asset: z30.number().int().nonnegative(),
4563
+ other: z30.number().int().nonnegative()
4564
+ });
4565
+ var trafficPathClassSchema = z30.enum([
4566
+ "content",
4567
+ "sitemap",
4568
+ "robots",
4569
+ "asset",
4570
+ "other"
4571
+ ]);
4558
4572
  var trafficSourceTypeSchema = z30.enum([
4559
4573
  "cloud-run",
4560
4574
  "wordpress",
@@ -4711,7 +4725,18 @@ var trafficBackfillResponseSchema = z30.object({
4711
4725
  daysApplied: z30.number().int().positive()
4712
4726
  });
4713
4727
  var trafficSourceTotalsSchema = z30.object({
4728
+ /**
4729
+ * Total classified-crawler hits in the window. UNCHANGED contract — still the
4730
+ * full count across every path class. Use `crawlerContentHits` for the
4731
+ * "content was actually crawled" signal.
4732
+ */
4714
4733
  crawlerHits: z30.number().int().nonnegative(),
4734
+ /** Crawler hits against content/document paths only (= `crawlerSegments.content`). */
4735
+ crawlerContentHits: z30.number().int().nonnegative(),
4736
+ /** Infrastructure crawler hits — sitemap + robots + asset fetches (`crawlerSegments.{sitemap,robots,asset}`). */
4737
+ crawlerInfraHits: z30.number().int().nonnegative(),
4738
+ /** Full per-class crawler-hit breakdown; the five buckets sum to `crawlerHits`. */
4739
+ crawlerSegments: trafficCrawlerSegmentsSchema,
4715
4740
  aiUserFetchHits: z30.number().int().nonnegative(),
4716
4741
  aiReferralHits: z30.number().int().nonnegative(),
4717
4742
  sampleCount: z30.number().int().nonnegative()
@@ -4742,6 +4767,8 @@ var trafficCrawlerEventEntrySchema = z30.object({
4742
4767
  operator: z30.string(),
4743
4768
  verificationStatus: z30.string(),
4744
4769
  pathNormalized: z30.string(),
4770
+ /** Coarse class of the fetched path — lets the UI split content crawls from sitemap/robots/asset polling. */
4771
+ pathClass: trafficPathClassSchema,
4745
4772
  status: z30.number().int(),
4746
4773
  hits: z30.number().int().nonnegative()
4747
4774
  });
@@ -4777,13 +4804,151 @@ var trafficEventsResponseSchema = z30.object({
4777
4804
  windowStart: z30.string(),
4778
4805
  windowEnd: z30.string(),
4779
4806
  totals: z30.object({
4807
+ /** Total classified-crawler hits across the window. UNCHANGED contract. */
4780
4808
  crawlerHits: z30.number().int().nonnegative(),
4809
+ /** Crawler hits against content/document paths only (= `crawlerSegments.content`). */
4810
+ crawlerContentHits: z30.number().int().nonnegative(),
4811
+ /** Infrastructure crawler hits — sitemap + robots + asset fetches. */
4812
+ crawlerInfraHits: z30.number().int().nonnegative(),
4813
+ /** Full per-class crawler-hit breakdown; the five buckets sum to `crawlerHits`. */
4814
+ crawlerSegments: trafficCrawlerSegmentsSchema,
4781
4815
  aiUserFetchHits: z30.number().int().nonnegative(),
4782
4816
  aiReferralHits: z30.number().int().nonnegative()
4783
4817
  }),
4784
4818
  events: z30.array(trafficEventEntrySchema)
4785
4819
  });
4786
4820
 
4821
+ // ../contracts/src/traffic-path.ts
4822
+ var TrafficPathClasses = {
4823
+ content: "content",
4824
+ sitemap: "sitemap",
4825
+ robots: "robots",
4826
+ asset: "asset",
4827
+ other: "other"
4828
+ };
4829
+ var ROBOTS_BASENAMES = /* @__PURE__ */ new Set(["robots.txt", "llms.txt", "llms-full.txt"]);
4830
+ var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
4831
+ "css",
4832
+ "js",
4833
+ "mjs",
4834
+ "cjs",
4835
+ "map",
4836
+ "json",
4837
+ "png",
4838
+ "jpg",
4839
+ "jpeg",
4840
+ "webp",
4841
+ "avif",
4842
+ "gif",
4843
+ "svg",
4844
+ "ico",
4845
+ "bmp",
4846
+ "tif",
4847
+ "tiff",
4848
+ "woff",
4849
+ "woff2",
4850
+ "ttf",
4851
+ "otf",
4852
+ "eot",
4853
+ "mp4",
4854
+ "webm",
4855
+ "mov",
4856
+ "m4v",
4857
+ "mp3",
4858
+ "wav",
4859
+ "ogg",
4860
+ "flac",
4861
+ "m4a",
4862
+ "wasm"
4863
+ ]);
4864
+ var DOCUMENT_EXTENSIONS = /* @__PURE__ */ new Set([
4865
+ "html",
4866
+ "htm",
4867
+ "xhtml",
4868
+ "shtml",
4869
+ "php",
4870
+ "php5",
4871
+ "php7",
4872
+ "asp",
4873
+ "aspx",
4874
+ "jsp",
4875
+ "jspx",
4876
+ "cfm",
4877
+ "md"
4878
+ ]);
4879
+ var DOWNLOAD_EXTENSIONS = /* @__PURE__ */ new Set([
4880
+ "pdf",
4881
+ "csv",
4882
+ "tsv",
4883
+ "txt",
4884
+ "doc",
4885
+ "docx",
4886
+ "xls",
4887
+ "xlsx",
4888
+ "ppt",
4889
+ "pptx",
4890
+ "odt",
4891
+ "ods",
4892
+ "odp",
4893
+ "rtf",
4894
+ "zip",
4895
+ "gz",
4896
+ "tgz",
4897
+ "tar",
4898
+ "rar",
4899
+ "7z",
4900
+ "bz2",
4901
+ "xz",
4902
+ "rss",
4903
+ "atom",
4904
+ "ics",
4905
+ "vcf",
4906
+ "epub",
4907
+ "mobi",
4908
+ "apk",
4909
+ "dmg",
4910
+ "exe",
4911
+ "bin",
4912
+ "iso",
4913
+ "sql"
4914
+ ]);
4915
+ var SITEMAP_BASENAME = /^sitemap(?:[-_]index)?$/;
4916
+ function classifyTrafficPath(pathNormalized) {
4917
+ const raw = (pathNormalized ?? "").trim();
4918
+ if (!raw) return TrafficPathClasses.other;
4919
+ const rawPath = raw.split(/[?#]/)[0] ?? "";
4920
+ const pathOnly = rawPath.length > 1 ? rawPath.replace(/\/+$/, "") || "/" : rawPath;
4921
+ if (!pathOnly) return TrafficPathClasses.other;
4922
+ const lower = pathOnly.toLowerCase();
4923
+ const segments = lower.split("/");
4924
+ const basename = segments[segments.length - 1] ?? "";
4925
+ if (ROBOTS_BASENAMES.has(basename)) return TrafficPathClasses.robots;
4926
+ if (lower.endsWith(".xml") || lower.endsWith(".xml.gz")) return TrafficPathClasses.sitemap;
4927
+ if (SITEMAP_BASENAME.test(basename)) return TrafficPathClasses.sitemap;
4928
+ const dot = basename.lastIndexOf(".");
4929
+ const ext = dot > 0 ? basename.slice(dot + 1) : "";
4930
+ if (ext) {
4931
+ if (ASSET_EXTENSIONS.has(ext)) return TrafficPathClasses.asset;
4932
+ if (DOCUMENT_EXTENSIONS.has(ext)) return TrafficPathClasses.content;
4933
+ if (DOWNLOAD_EXTENSIONS.has(ext)) return TrafficPathClasses.other;
4934
+ return TrafficPathClasses.content;
4935
+ }
4936
+ return TrafficPathClasses.content;
4937
+ }
4938
+ function emptyCrawlerSegments() {
4939
+ return { content: 0, sitemap: 0, robots: 0, asset: 0, other: 0 };
4940
+ }
4941
+ function segmentCrawlerHits(rows) {
4942
+ const segments = emptyCrawlerSegments();
4943
+ for (const row of rows) {
4944
+ segments[classifyTrafficPath(row.pathNormalized)] += row.hits;
4945
+ }
4946
+ return segments;
4947
+ }
4948
+ function sumInfraHits(segments) {
4949
+ return segments.sitemap + segments.robots + segments.asset;
4950
+ }
4951
+
4787
4952
  // ../contracts/src/formatting.ts
4788
4953
  function formatRatio(value) {
4789
4954
  if (!Number.isFinite(value) || value === 0) return "0%";
@@ -5018,6 +5183,63 @@ function escapeLikePattern(value) {
5018
5183
  return value.replace(/[\\%_]/g, (match) => `\\${match}`);
5019
5184
  }
5020
5185
 
5186
+ // ../contracts/src/embed.ts
5187
+ var SELF_TOKEN = "'self'";
5188
+ function normalizeFrameOrigin(raw) {
5189
+ const trimmed = raw.trim();
5190
+ if (!trimmed) return null;
5191
+ if (trimmed === SELF_TOKEN) return SELF_TOKEN;
5192
+ if (trimmed.includes("*")) return null;
5193
+ let url;
5194
+ try {
5195
+ url = new URL(trimmed);
5196
+ } catch {
5197
+ return null;
5198
+ }
5199
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
5200
+ if (url.username || url.password) return null;
5201
+ if (url.pathname && url.pathname !== "/" || url.search || url.hash) return null;
5202
+ if (!url.hostname) return null;
5203
+ return url.origin;
5204
+ }
5205
+ function splitList(raw) {
5206
+ if (raw === void 0) return [];
5207
+ const tokens = (Array.isArray(raw) ? raw : [raw]).flatMap(
5208
+ (entry) => String(entry).split(/[\s,]+/)
5209
+ );
5210
+ const seen = /* @__PURE__ */ new Set();
5211
+ const out = [];
5212
+ for (const token of tokens) {
5213
+ const trimmed = token.trim();
5214
+ if (!trimmed || seen.has(trimmed)) continue;
5215
+ seen.add(trimmed);
5216
+ out.push(trimmed);
5217
+ }
5218
+ return out;
5219
+ }
5220
+ function parseOriginList(raw) {
5221
+ const seen = /* @__PURE__ */ new Set();
5222
+ const out = [];
5223
+ for (const token of splitList(raw)) {
5224
+ const origin = normalizeFrameOrigin(token);
5225
+ if (!origin || seen.has(origin)) continue;
5226
+ seen.add(origin);
5227
+ out.push(origin);
5228
+ }
5229
+ return out;
5230
+ }
5231
+ function frameAncestorsHeaderValue(origins) {
5232
+ if (origins.length === 0) return "frame-ancestors 'none'";
5233
+ return `frame-ancestors ${origins.join(" ")}`;
5234
+ }
5235
+ function buildEmbedClientConfig(resolved) {
5236
+ if (!resolved.enabled) return void 0;
5237
+ const client = { enabled: true };
5238
+ if (resolved.views && resolved.views.length > 0) client.views = resolved.views;
5239
+ if (resolved.theme && Object.keys(resolved.theme).length > 0) client.theme = resolved.theme;
5240
+ return client;
5241
+ }
5242
+
5021
5243
  export {
5022
5244
  __export,
5023
5245
  apiKeyDtoSchema,
@@ -5285,6 +5507,9 @@ export {
5285
5507
  trafficEventKindSchema,
5286
5508
  TrafficEventKinds,
5287
5509
  trafficEventsResponseSchema,
5510
+ classifyTrafficPath,
5511
+ segmentCrawlerHits,
5512
+ sumInfraHits,
5288
5513
  formatRatio,
5289
5514
  formatNumber,
5290
5515
  formatDate,
@@ -5312,5 +5537,9 @@ export {
5312
5537
  AI_PROVIDER_INFRA_DOMAINS,
5313
5538
  escapeLikePattern,
5314
5539
  READ_ONLY_SCOPE,
5315
- isReadOnlyKey
5540
+ isReadOnlyKey,
5541
+ splitList,
5542
+ parseOriginList,
5543
+ frameAncestorsHeaderValue,
5544
+ buildEmbedClientConfig
5316
5545
  };
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  loadConfigRaw,
11
11
  saveConfigPatch
12
- } from "./chunk-6XMXBAEW.js";
12
+ } from "./chunk-PRKBJNAG.js";
13
13
  import {
14
14
  CC_CACHE_DIR,
15
15
  DUCKDB_SPEC,
@@ -104,7 +104,7 @@ import {
104
104
  siteAuditPages,
105
105
  siteAuditSnapshots,
106
106
  usageCounters
107
- } from "./chunk-5LW7CJAO.js";
107
+ } from "./chunk-WEV4RH25.js";
108
108
  import {
109
109
  AGENT_MEMORY_VALUE_MAX_BYTES,
110
110
  AGENT_PROVIDER_IDS,
@@ -136,6 +136,7 @@ import {
136
136
  agentMemoryUpsertRequestSchema,
137
137
  authInvalid,
138
138
  authRequired,
139
+ buildEmbedClientConfig,
139
140
  buildRunErrorFromMessages,
140
141
  classifySkillFile,
141
142
  coerceSkillManifest,
@@ -147,20 +148,23 @@ import {
147
148
  effectiveBrandNames,
148
149
  effectiveDomains,
149
150
  factorStatusFromScore,
151
+ frameAncestorsHeaderValue,
150
152
  hostOf,
151
153
  isAgentProviderId,
152
154
  isBrowserProvider,
153
155
  isRetryableHttpError,
154
156
  normalizeUrlPath,
155
157
  notFound,
158
+ parseOriginList,
156
159
  providerError,
157
160
  resolveSnapshotRequestQueries,
158
161
  serializeRunError,
159
162
  skillsClientSchema,
163
+ splitList,
160
164
  validationError,
161
165
  winnabilityClassLabel,
162
166
  withRetry
163
- } from "./chunk-MDRDX5R2.js";
167
+ } from "./chunk-LRO4CLPL.js";
164
168
 
165
169
  // src/telemetry.ts
166
170
  import crypto from "crypto";
@@ -2728,6 +2732,34 @@ var perplexityAdapter = {
2728
2732
  }
2729
2733
  };
2730
2734
 
2735
+ // src/embed.ts
2736
+ function resolveEmbedConfig(env, config) {
2737
+ const embed = config.embed;
2738
+ const rawEnabled = env.CANONRY_EMBED?.trim();
2739
+ const enabled = rawEnabled ? rawEnabled === "1" || rawEnabled.toLowerCase() === "true" : embed?.enabled === true;
2740
+ const allowedOrigins = env.CANONRY_EMBED_ORIGINS !== void 0 ? parseOriginList(env.CANONRY_EMBED_ORIGINS) : parseOriginList(embed?.allowOrigins);
2741
+ const rawViews = env.CANONRY_EMBED_VIEWS !== void 0 ? splitList(env.CANONRY_EMBED_VIEWS) : splitList(embed?.views);
2742
+ const views = normalizeViews(rawViews);
2743
+ return {
2744
+ enabled,
2745
+ allowedOrigins,
2746
+ ...views ? { views } : {},
2747
+ ...embed?.theme ? { theme: embed.theme } : {}
2748
+ };
2749
+ }
2750
+ function normalizeViews(raw) {
2751
+ if (raw.length === 0) return void 0;
2752
+ const seen = /* @__PURE__ */ new Set();
2753
+ const out = [];
2754
+ for (const view of raw) {
2755
+ const id = view.toLowerCase();
2756
+ if (seen.has(id)) continue;
2757
+ seen.add(id);
2758
+ out.push(id);
2759
+ }
2760
+ return out.length > 0 ? out : void 0;
2761
+ }
2762
+
2731
2763
  // src/places-config.ts
2732
2764
  var DEFAULT_TIER = "atmosphere";
2733
2765
  var DEFAULT_REFRESH_INTERVAL_DAYS = 7;
@@ -6289,7 +6321,7 @@ function readStoredGroundingSources(rawResponse) {
6289
6321
  return result;
6290
6322
  }
6291
6323
  async function backfillInsightsCommand(project, opts) {
6292
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-XUKYOHKL.js");
6324
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-SO46DMGR.js");
6293
6325
  const config = loadConfig();
6294
6326
  const db = createClient(config.database);
6295
6327
  migrate(db);
@@ -10538,6 +10570,8 @@ async function createServer(opts) {
10538
10570
  const rawBasePath = process.env.CANONRY_BASE_PATH ?? opts.config.basePath;
10539
10571
  const normalizedBasePath = rawBasePath ? "/" + rawBasePath.replace(/^\//, "").replace(/\/?$/, "/") : void 0;
10540
10572
  const basePath = normalizedBasePath === "/" ? void 0 : normalizedBasePath;
10573
+ const embed = resolveEmbedConfig(process.env, opts.config);
10574
+ const embedCsp = embed.enabled ? frameAncestorsHeaderValue(embed.allowedOrigins) : void 0;
10541
10575
  const apiPrefix = basePath ? `${basePath}api/v1` : "/api/v1";
10542
10576
  if (opts.config.apiKey) {
10543
10577
  const keyHash = hashApiKey(opts.config.apiKey);
@@ -11173,16 +11207,25 @@ async function createServer(opts) {
11173
11207
  }
11174
11208
  });
11175
11209
  const dirname = path9.dirname(fileURLToPath3(import.meta.url));
11176
- const assetsDir = path9.join(dirname, "..", "assets");
11210
+ const assetsDir = opts.assetsDir ?? path9.join(dirname, "..", "assets");
11177
11211
  if (fs8.existsSync(assetsDir)) {
11178
11212
  const indexPath = path9.join(assetsDir, "index.html");
11179
11213
  const injectConfig = (html) => {
11180
11214
  const clientConfig = {};
11181
11215
  if (basePath) clientConfig.basePath = basePath;
11216
+ if (embed.enabled) {
11217
+ const embedClient = buildEmbedClientConfig(embed);
11218
+ if (embedClient) clientConfig.embed = embedClient;
11219
+ }
11182
11220
  const configScript = `<script>window.__CANONRY_CONFIG__=${JSON.stringify(clientConfig)}</script>`;
11183
11221
  const baseTag = `<base href="${basePath ?? "/"}">`;
11184
11222
  return html.replace("<head>", `<head>${baseTag}`).replace("</head>", `${configScript}</head>`);
11185
11223
  };
11224
+ const sendSpaDocument = (reply, html) => {
11225
+ reply.header("Cache-Control", "no-cache, must-revalidate");
11226
+ if (embedCsp) reply.header("Content-Security-Policy", embedCsp);
11227
+ return reply.type("text/html").send(injectConfig(html));
11228
+ };
11186
11229
  const fastifyStatic = await import("@fastify/static");
11187
11230
  await app.register(fastifyStatic.default, {
11188
11231
  root: assetsDir,
@@ -11209,7 +11252,7 @@ async function createServer(opts) {
11209
11252
  const serveIndex = (_request, reply) => {
11210
11253
  if (fs8.existsSync(indexPath)) {
11211
11254
  const html = fs8.readFileSync(indexPath, "utf-8");
11212
- return reply.header("Cache-Control", "no-cache, must-revalidate").type("text/html").send(injectConfig(html));
11255
+ return sendSpaDocument(reply, html);
11213
11256
  }
11214
11257
  return reply.status(404).send({ error: "Dashboard not built" });
11215
11258
  };
@@ -11230,7 +11273,7 @@ async function createServer(opts) {
11230
11273
  }
11231
11274
  if (fs8.existsSync(indexPath)) {
11232
11275
  const html = fs8.readFileSync(indexPath, "utf-8");
11233
- return reply.header("Cache-Control", "no-cache, must-revalidate").type("text/html").send(injectConfig(html));
11276
+ return sendSpaDocument(reply, html);
11234
11277
  }
11235
11278
  return reply.status(404).send({ error: "Not found" });
11236
11279
  });
@@ -23,7 +23,7 @@ import {
23
23
  trafficConnectVercelRequestSchema,
24
24
  trafficConnectWordpressRequestSchema,
25
25
  trafficEventKindSchema
26
- } from "./chunk-MDRDX5R2.js";
26
+ } from "./chunk-LRO4CLPL.js";
27
27
 
28
28
  // src/config.ts
29
29
  import fs from "fs";
@@ -86,6 +86,7 @@ import {
86
86
  classifyCitedSurface,
87
87
  classifySkillFile,
88
88
  classifySurfaceFromCategory,
89
+ classifyTrafficPath,
89
90
  clusterByCosine,
90
91
  coerceSkillManifest,
91
92
  competitorBatchRequestSchema,
@@ -210,6 +211,7 @@ import {
210
211
  scheduleDtoSchema,
211
212
  scheduleUpsertRequestSchema,
212
213
  seedCollapseWarning,
214
+ segmentCrawlerHits,
213
215
  serializeRunError,
214
216
  settingsDtoSchema,
215
217
  siteAuditPagesResponseSchema,
@@ -222,6 +224,7 @@ import {
222
224
  snapshotReportSchema,
223
225
  snapshotRequestSchema,
224
226
  sourceBreakdownDtoSchema,
227
+ sumInfraHits,
225
228
  summarizeCheckResults,
226
229
  surfaceClassFromCompetitorType,
227
230
  surfaceClassLabel,
@@ -255,7 +258,7 @@ import {
255
258
  wordpressSchemaDeployResultDtoSchema,
256
259
  wordpressSchemaStatusResultDtoSchema,
257
260
  wordpressStatusDtoSchema
258
- } from "./chunk-MDRDX5R2.js";
261
+ } from "./chunk-LRO4CLPL.js";
259
262
 
260
263
  // src/intelligence-service.ts
261
264
  import { eq as eq37, desc as desc18, asc as asc5, and as and27, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
@@ -30625,12 +30628,19 @@ async function trafficRoutes(app, opts) {
30625
30628
  return response;
30626
30629
  });
30627
30630
  function buildSourceDetail(projectId, row, since) {
30628
- const crawlerTotals = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
30631
+ const crawlerPathRows = app.db.select({
30632
+ pathNormalized: crawlerEventsHourly.pathNormalized,
30633
+ hits: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
30634
+ }).from(crawlerEventsHourly).where(
30629
30635
  and21(
30630
30636
  eq27(crawlerEventsHourly.sourceId, row.id),
30631
30637
  gte4(crawlerEventsHourly.tsHour, since)
30632
30638
  )
30633
- ).get();
30639
+ ).groupBy(crawlerEventsHourly.pathNormalized).all();
30640
+ const crawlerSegments = segmentCrawlerHits(
30641
+ crawlerPathRows.map((r) => ({ pathNormalized: r.pathNormalized, hits: Number(r.hits) }))
30642
+ );
30643
+ const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
30634
30644
  const aiUserFetchTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
30635
30645
  and21(
30636
30646
  eq27(aiUserFetchEventsHourly.sourceId, row.id),
@@ -30659,7 +30669,10 @@ async function trafficRoutes(app, opts) {
30659
30669
  return {
30660
30670
  ...rowToDto(row),
30661
30671
  totals24h: {
30662
- crawlerHits: Number(crawlerTotals?.total ?? 0),
30672
+ crawlerHits: crawlerTotal,
30673
+ crawlerContentHits: crawlerSegments.content,
30674
+ crawlerInfraHits: sumInfraHits(crawlerSegments),
30675
+ crawlerSegments,
30663
30676
  aiUserFetchHits: Number(aiUserFetchTotals?.total ?? 0),
30664
30677
  aiReferralHits: Number(aiTotals?.total ?? 0),
30665
30678
  sampleCount: Number(sampleTotals?.total ?? 0)
@@ -30776,6 +30789,7 @@ async function trafficRoutes(app, opts) {
30776
30789
  const untilIso = until.toISOString();
30777
30790
  const events = [];
30778
30791
  let crawlerTotal = 0;
30792
+ let crawlerSegments = { content: 0, sitemap: 0, robots: 0, asset: 0, other: 0 };
30779
30793
  let aiUserFetchTotal = 0;
30780
30794
  let aiReferralTotal = 0;
30781
30795
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
@@ -30786,8 +30800,14 @@ async function trafficRoutes(app, opts) {
30786
30800
  ];
30787
30801
  if (sourceIdParam) crawlerFilters.push(eq27(crawlerEventsHourly.sourceId, sourceIdParam));
30788
30802
  const crawlerWhere = and21(...crawlerFilters);
30789
- const total = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(crawlerWhere).get();
30790
- crawlerTotal = Number(total?.total ?? 0);
30803
+ const pathTotals = app.db.select({
30804
+ pathNormalized: crawlerEventsHourly.pathNormalized,
30805
+ hits: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
30806
+ }).from(crawlerEventsHourly).where(crawlerWhere).groupBy(crawlerEventsHourly.pathNormalized).all();
30807
+ crawlerSegments = segmentCrawlerHits(
30808
+ pathTotals.map((r) => ({ pathNormalized: r.pathNormalized, hits: Number(r.hits) }))
30809
+ );
30810
+ crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
30791
30811
  const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc15(crawlerEventsHourly.tsHour)).limit(limit).all();
30792
30812
  for (const r of rows) {
30793
30813
  events.push({
@@ -30798,6 +30818,7 @@ async function trafficRoutes(app, opts) {
30798
30818
  operator: r.operator,
30799
30819
  verificationStatus: r.verificationStatus,
30800
30820
  pathNormalized: r.pathNormalized,
30821
+ pathClass: classifyTrafficPath(r.pathNormalized),
30801
30822
  status: r.status,
30802
30823
  hits: r.hits
30803
30824
  });
@@ -30861,6 +30882,9 @@ async function trafficRoutes(app, opts) {
30861
30882
  windowEnd: untilIso,
30862
30883
  totals: {
30863
30884
  crawlerHits: crawlerTotal,
30885
+ crawlerContentHits: crawlerSegments.content,
30886
+ crawlerInfraHits: sumInfraHits(crawlerSegments),
30887
+ crawlerSegments,
30864
30888
  aiUserFetchHits: aiUserFetchTotal,
30865
30889
  aiReferralHits: aiReferralTotal
30866
30890
  },