@canonry/canonry 4.129.0 → 4.129.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 (27) hide show
  1. package/README.md +0 -60
  2. package/assets/assets/{AuditHistoryPanel-DwJpVWme.js → AuditHistoryPanel-wJqs99Ak.js} +1 -1
  3. package/assets/assets/{BacklinksPage-K8FQUBxI.js → BacklinksPage-BBDExYo_.js} +1 -1
  4. package/assets/assets/{ChartPrimitives-6oFKrJFk.js → ChartPrimitives-DSr5ldMf.js} +1 -1
  5. package/assets/assets/{HistoryPage-DwOAoN1Z.js → HistoryPage-Ca-GGnTI.js} +1 -1
  6. package/assets/assets/{ProjectPage-CdgFUk7h.js → ProjectPage-BuaSDSwf.js} +1 -1
  7. package/assets/assets/{RunRow-lt9IS99U.js → RunRow-O9TbOdQY.js} +1 -1
  8. package/assets/assets/{RunsPage-DitYcIdx.js → RunsPage-C1Sqd7AN.js} +1 -1
  9. package/assets/assets/{SettingsPage-CAOrh65Q.js → SettingsPage-DPFh62BV.js} +1 -1
  10. package/assets/assets/{TrafficPage-BqzNvx75.js → TrafficPage-5vKoQyoP.js} +1 -1
  11. package/assets/assets/TrafficSourceDetailPage-D17oADs8.js +1 -0
  12. package/assets/assets/{arrow-left-AmG4gtUR.js → arrow-left-De5P5AIE.js} +1 -1
  13. package/assets/assets/extract-error-message-BqMd-fQ6.js +1 -0
  14. package/assets/assets/{index-BiCDkOH4.js → index-C1ABEgkr.js} +53 -53
  15. package/assets/assets/{trash-2-gFHWd1A1.js → trash-2-6ZpABj9G.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-ZHWWSU7H.js → chunk-GYAKRVE2.js} +4 -4
  18. package/dist/{chunk-BLSHHRRU.js → chunk-K4FDDCYG.js} +29 -0
  19. package/dist/{chunk-EF67Y6CI.js → chunk-LPEE5AYY.js} +92 -7
  20. package/dist/{chunk-IHRW6WWV.js → chunk-WWZE7VDQ.js} +10 -6
  21. package/dist/cli.js +18 -6
  22. package/dist/index.js +4 -4
  23. package/dist/{intelligence-service-NTIKN7YG.js → intelligence-service-2QBAGT5X.js} +2 -2
  24. package/dist/mcp.js +2 -2
  25. package/package.json +8 -8
  26. package/assets/assets/TrafficSourceDetailPage-DXbygZUA.js +0 -1
  27. package/assets/assets/extract-error-message-DlVtjoPz.js +0 -1
@@ -1 +1 @@
1
- import{c}from"./index-BiCDkOH4.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-C1ABEgkr.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-BiCDkOH4.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-C1ABEgkr.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-DSxTZhaH.js">
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-IHRW6WWV.js";
13
+ } from "./chunk-WWZE7VDQ.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -116,7 +116,7 @@ import {
116
116
  siteAuditPages,
117
117
  siteAuditSnapshots,
118
118
  usageCounters
119
- } from "./chunk-EF67Y6CI.js";
119
+ } from "./chunk-LPEE5AYY.js";
120
120
  import {
121
121
  AGENT_MEMORY_VALUE_MAX_BYTES,
122
122
  AGENT_PROVIDER_IDS,
@@ -186,7 +186,7 @@ import {
186
186
  validationError,
187
187
  winnabilityClassLabel,
188
188
  withRetry
189
- } from "./chunk-BLSHHRRU.js";
189
+ } from "./chunk-K4FDDCYG.js";
190
190
 
191
191
  // src/telemetry.ts
192
192
  import crypto from "crypto";
@@ -6967,7 +6967,7 @@ function readStoredGroundingSources(rawResponse) {
6967
6967
  return result;
6968
6968
  }
6969
6969
  async function backfillInsightsCommand(project, opts) {
6970
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-NTIKN7YG.js");
6970
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-2QBAGT5X.js");
6971
6971
  const config = loadConfig();
6972
6972
  const db = createClient(config.database);
6973
6973
  migrate(db);
@@ -5995,6 +5995,18 @@ var trafficStatusResponseSchema = z34.object({
5995
5995
  });
5996
5996
  var trafficEventKindSchema = z34.enum(["crawler", "ai-user-fetch", "ai-referral"]);
5997
5997
  var TrafficEventKinds = trafficEventKindSchema.enum;
5998
+ var trafficSeriesGranularitySchema = z34.enum(["hour", "day"]);
5999
+ var TrafficSeriesGranularities = trafficSeriesGranularitySchema.enum;
6000
+ var trafficSeriesPointSchema = z34.object({
6001
+ /**
6002
+ * UTC bucket key: an ISO hour for hourly series, or `YYYY-MM-DD` for daily
6003
+ * series. The response includes zero-value buckets across the full window.
6004
+ */
6005
+ bucket: z34.string(),
6006
+ crawlerHits: z34.number().int().nonnegative(),
6007
+ aiUserFetchHits: z34.number().int().nonnegative(),
6008
+ aiReferralHits: z34.number().int().nonnegative()
6009
+ });
5998
6010
  var trafficCrawlerEventEntrySchema = z34.object({
5999
6011
  kind: z34.literal(TrafficEventKinds.crawler),
6000
6012
  sourceId: z34.string(),
@@ -6050,6 +6062,14 @@ var trafficEventEntrySchema = z34.discriminatedUnion("kind", [
6050
6062
  var trafficEventsResponseSchema = z34.object({
6051
6063
  windowStart: z34.string(),
6052
6064
  windowEnd: z34.string(),
6065
+ /**
6066
+ * Full-window chart data, aggregated independently from the capped detail
6067
+ * rows below. This prevents dense windows from silently losing old buckets.
6068
+ */
6069
+ series: z34.object({
6070
+ granularity: trafficSeriesGranularitySchema,
6071
+ points: z34.array(trafficSeriesPointSchema)
6072
+ }),
6053
6073
  totals: z34.object({
6054
6074
  /** Total classified-crawler hits across the window. UNCHANGED contract. */
6055
6075
  crawlerHits: z34.number().int().nonnegative(),
@@ -6069,6 +6089,13 @@ var trafficEventsResponseSchema = z34.object({
6069
6089
  /** AI-referral sessions ingested before the classifier shipped; unresolvable, never organic. */
6070
6090
  aiReferralUnknownHits: z34.number().int().nonnegative()
6071
6091
  }),
6092
+ eventRows: z34.object({
6093
+ /** Total detail rows matching the window/source/kind filters before `limit`. */
6094
+ total: z34.number().int().nonnegative(),
6095
+ /** Number of newest detail rows included in `events`. */
6096
+ returned: z34.number().int().nonnegative(),
6097
+ truncated: z34.boolean()
6098
+ }),
6072
6099
  events: z34.array(trafficEventEntrySchema)
6073
6100
  });
6074
6101
 
@@ -7420,6 +7447,8 @@ export {
7420
7447
  trafficStatusResponseSchema,
7421
7448
  trafficEventKindSchema,
7422
7449
  TrafficEventKinds,
7450
+ trafficSeriesGranularitySchema,
7451
+ TrafficSeriesGranularities,
7423
7452
  trafficEventsResponseSchema,
7424
7453
  classifyTrafficPath,
7425
7454
  segmentCrawlerHits,
@@ -47,6 +47,7 @@ import {
47
47
  TrafficEventConfidences,
48
48
  TrafficEventKinds,
49
49
  TrafficEvidenceKinds,
50
+ TrafficSeriesGranularities,
50
51
  TrafficSourceAuthModes,
51
52
  TrafficSourceStatuses,
52
53
  TrafficSourceTypes,
@@ -336,7 +337,7 @@ import {
336
337
  wordpressSchemaDeployResultDtoSchema,
337
338
  wordpressSchemaStatusResultDtoSchema,
338
339
  wordpressStatusDtoSchema
339
- } from "./chunk-BLSHHRRU.js";
340
+ } from "./chunk-K4FDDCYG.js";
340
341
 
341
342
  // src/intelligence-service.ts
342
343
  import { eq as eq41, desc as desc18, asc as asc9, and as and32, ne as ne6, or as or8, inArray as inArray15, gte as gte10, lte as lte6 } from "drizzle-orm";
@@ -20791,15 +20792,16 @@ var routeCatalog = [
20791
20792
  method: "get",
20792
20793
  path: "/api/v1/projects/{name}/traffic/events",
20793
20794
  summary: "List rolled-up crawler hits, AI user-fetch hits, and AI-referral sessions within a window",
20794
- description: "Returns hourly rollup rows from `crawler_events_hourly`, `ai_user_fetch_events_hourly`, and `ai_referral_events_hourly`. Defaults to the last 24h. Totals reflect the full window; the `events` array is capped by `limit` (default 500, max 5000).",
20795
+ description: "Returns full-window hourly or daily chart series plus newest-first detail rows from `crawler_events_hourly`, `ai_user_fetch_events_hourly`, and `ai_referral_events_hourly`. Defaults to the last 24h. Totals and `series.points` reflect the full window; only the `events` array is capped by `limit` (default 500, max 5000). `eventRows` reports the true pre-limit row count.",
20795
20796
  tags: ["traffic"],
20796
20797
  parameters: [
20797
20798
  nameParameter,
20798
20799
  { name: "since", in: "query", description: "ISO-8601 window start (defaults to 24h ago).", schema: stringSchema },
20799
20800
  { name: "until", in: "query", description: "ISO-8601 window end (defaults to now).", schema: stringSchema },
20800
20801
  { name: "kind", in: "query", description: 'Filter to "crawler", "ai-user-fetch", "ai-referral", or "all" (default).', schema: stringSchema },
20801
- { name: "limit", in: "query", description: "Max rows per kind in the events array (default 500, max 5000).", schema: stringSchema },
20802
- { name: "sourceId", in: "query", description: "Restrict to a single traffic source.", schema: stringSchema }
20802
+ { name: "limit", in: "query", description: "Max newest combined detail rows in the events array (default 500, max 5000).", schema: stringSchema },
20803
+ { name: "sourceId", in: "query", description: "Restrict to a single traffic source.", schema: stringSchema },
20804
+ { name: "granularity", in: "query", description: 'Full-window series bucket size: "hour" (default) or "day".', schema: { type: "string", enum: ["hour", "day"] } }
20803
20805
  ],
20804
20806
  responses: {
20805
20807
  200: jsonResponse("Events returned with windowed totals.", "TrafficEventsResponse"),
@@ -36922,6 +36924,35 @@ var DEFAULT_TRAFFIC_SYNC_CRON = "*/30 * * * *";
36922
36924
  var MAX_BACKFILL_DAYS = 90;
36923
36925
  var BACKFILL_MAX_PAGES = 1e3;
36924
36926
  var BACKFILL_SAMPLE_LIMIT = 500;
36927
+ function emptyTrafficSeriesPoint(bucket) {
36928
+ return { bucket, crawlerHits: 0, aiUserFetchHits: 0, aiReferralHits: 0 };
36929
+ }
36930
+ function completeTrafficSeries(windowStart, windowEnd, granularity, pointsByBucket) {
36931
+ const cursor = new Date(windowStart);
36932
+ const last = new Date(windowEnd);
36933
+ if (granularity === TrafficSeriesGranularities.day) {
36934
+ cursor.setUTCHours(0, 0, 0, 0);
36935
+ last.setUTCHours(0, 0, 0, 0);
36936
+ } else {
36937
+ cursor.setUTCMinutes(0, 0, 0);
36938
+ last.setUTCMinutes(0, 0, 0);
36939
+ }
36940
+ const points = [];
36941
+ while (cursor <= last) {
36942
+ const bucket = granularity === TrafficSeriesGranularities.day ? cursor.toISOString().slice(0, 10) : cursor.toISOString();
36943
+ points.push(pointsByBucket.get(bucket) ?? emptyTrafficSeriesPoint(bucket));
36944
+ if (granularity === TrafficSeriesGranularities.day) cursor.setUTCDate(cursor.getUTCDate() + 1);
36945
+ else cursor.setUTCHours(cursor.getUTCHours() + 1);
36946
+ }
36947
+ return points;
36948
+ }
36949
+ function trafficSeriesPoint(pointsByBucket, bucket) {
36950
+ const existing = pointsByBucket.get(bucket);
36951
+ if (existing) return existing;
36952
+ const created = emptyTrafficSeriesPoint(bucket);
36953
+ pointsByBucket.set(bucket, created);
36954
+ return created;
36955
+ }
36925
36956
  function parseSourceConfig(row) {
36926
36957
  return row.configJson;
36927
36958
  }
@@ -38297,6 +38328,17 @@ async function trafficRoutes(app, opts) {
38297
38328
  throw validationError('"limit" must be a positive integer');
38298
38329
  }
38299
38330
  const limit = Math.min(requestedLimit, 5e3);
38331
+ const granularityParam = request.query?.granularity;
38332
+ let granularity = TrafficSeriesGranularities.hour;
38333
+ if (granularityParam !== void 0) {
38334
+ if (granularityParam === TrafficSeriesGranularities.hour || granularityParam === TrafficSeriesGranularities.day) {
38335
+ granularity = granularityParam;
38336
+ } else {
38337
+ throw validationError(
38338
+ `"granularity" must be one of: ${TrafficSeriesGranularities.hour}, ${TrafficSeriesGranularities.day}`
38339
+ );
38340
+ }
38341
+ }
38300
38342
  const sourceIdParam = request.query?.sourceId;
38301
38343
  const sinceIso = since.toISOString();
38302
38344
  const untilIso = until.toISOString();
@@ -38305,6 +38347,8 @@ async function trafficRoutes(app, opts) {
38305
38347
  let crawlerSegments = { content: 0, sitemap: 0, robots: 0, asset: 0, other: 0 };
38306
38348
  let aiUserFetchTotal = 0;
38307
38349
  let aiReferralCounts = aiReferralClassCounts(0, 0, 0);
38350
+ let totalEventRows = 0;
38351
+ const seriesByBucket = /* @__PURE__ */ new Map();
38308
38352
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
38309
38353
  const crawlerFilters = [
38310
38354
  eq31(crawlerEventsHourly.projectId, project.id),
@@ -38315,12 +38359,22 @@ async function trafficRoutes(app, opts) {
38315
38359
  const crawlerWhere = and26(...crawlerFilters);
38316
38360
  const pathTotals = app.db.select({
38317
38361
  pathNormalized: crawlerEventsHourly.pathNormalized,
38318
- hits: sql14`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
38362
+ hits: sql14`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
38363
+ rows: sql14`COUNT(*)`
38319
38364
  }).from(crawlerEventsHourly).where(crawlerWhere).groupBy(crawlerEventsHourly.pathNormalized).all();
38320
38365
  crawlerSegments = segmentCrawlerHits(
38321
38366
  pathTotals.map((r) => ({ pathNormalized: r.pathNormalized, hits: Number(r.hits) }))
38322
38367
  );
38323
38368
  crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
38369
+ totalEventRows += pathTotals.reduce((sum, row) => sum + Number(row.rows), 0);
38370
+ const crawlerSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql14`substr(${crawlerEventsHourly.tsHour}, 1, 10)` : crawlerEventsHourly.tsHour;
38371
+ const crawlerSeries = app.db.select({
38372
+ bucket: crawlerSeriesBucket,
38373
+ hits: sql14`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
38374
+ }).from(crawlerEventsHourly).where(crawlerWhere).groupBy(crawlerSeriesBucket).all();
38375
+ for (const row of crawlerSeries) {
38376
+ trafficSeriesPoint(seriesByBucket, row.bucket).crawlerHits = Number(row.hits);
38377
+ }
38324
38378
  const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc15(crawlerEventsHourly.tsHour)).limit(limit).all();
38325
38379
  for (const r of rows) {
38326
38380
  events.push({
@@ -38345,8 +38399,20 @@ async function trafficRoutes(app, opts) {
38345
38399
  ];
38346
38400
  if (sourceIdParam) userFetchFilters.push(eq31(aiUserFetchEventsHourly.sourceId, sourceIdParam));
38347
38401
  const userFetchWhere = and26(...userFetchFilters);
38348
- const total = app.db.select({ total: sql14`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
38402
+ const total = app.db.select({
38403
+ total: sql14`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`,
38404
+ rows: sql14`COUNT(*)`
38405
+ }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
38349
38406
  aiUserFetchTotal = Number(total?.total ?? 0);
38407
+ totalEventRows += Number(total?.rows ?? 0);
38408
+ const userFetchSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql14`substr(${aiUserFetchEventsHourly.tsHour}, 1, 10)` : aiUserFetchEventsHourly.tsHour;
38409
+ const userFetchSeries = app.db.select({
38410
+ bucket: userFetchSeriesBucket,
38411
+ hits: sql14`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
38412
+ }).from(aiUserFetchEventsHourly).where(userFetchWhere).groupBy(userFetchSeriesBucket).all();
38413
+ for (const row of userFetchSeries) {
38414
+ trafficSeriesPoint(seriesByBucket, row.bucket).aiUserFetchHits = Number(row.hits);
38415
+ }
38350
38416
  const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc15(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
38351
38417
  for (const r of rows) {
38352
38418
  events.push({
@@ -38373,13 +38439,23 @@ async function trafficRoutes(app, opts) {
38373
38439
  const total = app.db.select({
38374
38440
  total: sql14`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
38375
38441
  paid: sql14`COALESCE(SUM(${aiReferralEventsHourly.paidSessionsOrHits}), 0)`,
38376
- organic: sql14`COALESCE(SUM(${aiReferralEventsHourly.organicSessionsOrHits}), 0)`
38442
+ organic: sql14`COALESCE(SUM(${aiReferralEventsHourly.organicSessionsOrHits}), 0)`,
38443
+ rows: sql14`COUNT(*)`
38377
38444
  }).from(aiReferralEventsHourly).where(aiWhere).get();
38378
38445
  aiReferralCounts = aiReferralClassCounts(
38379
38446
  Number(total?.total ?? 0),
38380
38447
  Number(total?.paid ?? 0),
38381
38448
  Number(total?.organic ?? 0)
38382
38449
  );
38450
+ totalEventRows += Number(total?.rows ?? 0);
38451
+ const referralSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql14`substr(${aiReferralEventsHourly.tsHour}, 1, 10)` : aiReferralEventsHourly.tsHour;
38452
+ const referralSeries = app.db.select({
38453
+ bucket: referralSeriesBucket,
38454
+ hits: sql14`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
38455
+ }).from(aiReferralEventsHourly).where(aiWhere).groupBy(referralSeriesBucket).all();
38456
+ for (const row of referralSeries) {
38457
+ trafficSeriesPoint(seriesByBucket, row.bucket).aiReferralHits = Number(row.hits);
38458
+ }
38383
38459
  const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc15(aiReferralEventsHourly.tsHour)).limit(limit).all();
38384
38460
  for (const r of rows) {
38385
38461
  const counts2 = aiReferralClassCounts(r.sessionsOrHits, r.paidSessionsOrHits, r.organicSessionsOrHits);
@@ -38405,6 +38481,10 @@ async function trafficRoutes(app, opts) {
38405
38481
  const response = {
38406
38482
  windowStart: sinceIso,
38407
38483
  windowEnd: untilIso,
38484
+ series: {
38485
+ granularity,
38486
+ points: completeTrafficSeries(since, until, granularity, seriesByBucket)
38487
+ },
38408
38488
  totals: {
38409
38489
  crawlerHits: crawlerTotal,
38410
38490
  crawlerContentHits: crawlerSegments.content,
@@ -38416,6 +38496,11 @@ async function trafficRoutes(app, opts) {
38416
38496
  aiReferralOrganicHits: aiReferralCounts.organic,
38417
38497
  aiReferralUnknownHits: aiReferralCounts.unknown
38418
38498
  },
38499
+ eventRows: {
38500
+ total: totalEventRows,
38501
+ returned: trimmed.length,
38502
+ truncated: trimmed.length < totalEventRows
38503
+ },
38419
38504
  events: trimmed
38420
38505
  };
38421
38506
  return response;
@@ -36,8 +36,9 @@ import {
36
36
  trafficConnectCloudRunRequestSchema,
37
37
  trafficConnectVercelRequestSchema,
38
38
  trafficConnectWordpressRequestSchema,
39
- trafficEventKindSchema
40
- } from "./chunk-BLSHHRRU.js";
39
+ trafficEventKindSchema,
40
+ trafficSeriesGranularitySchema
41
+ } from "./chunk-K4FDDCYG.js";
41
42
 
42
43
  // src/config.ts
43
44
  import fs from "fs";
@@ -5335,7 +5336,8 @@ var ApiClient = class {
5335
5336
  until: params?.until,
5336
5337
  kind: params?.kind,
5337
5338
  limit: params?.limit !== void 0 ? String(params.limit) : void 0,
5338
- sourceId: params?.sourceId
5339
+ sourceId: params?.sourceId,
5340
+ granularity: params?.granularity
5339
5341
  }
5340
5342
  })
5341
5343
  );
@@ -6190,9 +6192,10 @@ var trafficEventsInputSchema = z2.object({
6190
6192
  project: projectNameSchema,
6191
6193
  since: z2.string().optional().describe("ISO 8601 lower bound. Defaults to 24h ago when omitted."),
6192
6194
  until: z2.string().optional().describe("ISO 8601 upper bound. Defaults to now when omitted."),
6193
- kind: z2.union([trafficEventKindSchema, z2.literal("all")]).optional().describe('Filter to "crawler" or "ai-referral"; "all" (default) returns both.'),
6195
+ kind: z2.union([trafficEventKindSchema, z2.literal("all")]).optional().describe('Filter to one traffic kind; "all" (default) returns every kind.'),
6194
6196
  sourceId: z2.string().min(1).optional().describe("Restrict to a single traffic source ID."),
6195
- limit: z2.number().int().positive().max(5e3).optional().describe("Max combined rows. Defaults to 500, max 5000. Totals always reflect the full window.")
6197
+ limit: z2.number().int().positive().max(5e3).optional().describe("Max combined rows. Defaults to 500, max 5000. Totals always reflect the full window."),
6198
+ granularity: trafficSeriesGranularitySchema.optional().describe("Full-window chart series bucket size: hour (default) or day.")
6196
6199
  });
6197
6200
  var trafficSourceIdInputSchema = z2.object({
6198
6201
  project: projectNameSchema,
@@ -7110,7 +7113,7 @@ var canonryMcpTools = [
7110
7113
  defineTool({
7111
7114
  name: "canonry_traffic_events",
7112
7115
  title: "List traffic events",
7113
- description: 'Read crawler and AI-referral hourly rollups from server-side traffic sources. Returns a discriminated list (kind="crawler" rows carry botId/operator/verificationStatus; kind="ai-referral" rows carry product/sourceDomain/evidenceType) plus totals over the full window even when limit truncates rows. Window defaults to last 24h.',
7116
+ description: "Read crawler, AI user-fetch, and AI-referral rollups from server-side traffic sources. Returns complete full-window chart series and totals plus a capped discriminated detail list with explicit truncation metadata. Window defaults to last 24h.",
7114
7117
  access: "read",
7115
7118
  tier: "traffic",
7116
7119
  inputSchema: trafficEventsInputSchema,
@@ -7123,6 +7126,7 @@ var canonryMcpTools = [
7123
7126
  if (input.kind) params.kind = input.kind;
7124
7127
  if (input.sourceId) params.sourceId = input.sourceId;
7125
7128
  if (input.limit !== void 0) params.limit = input.limit;
7129
+ if (input.granularity) params.granularity = input.granularity;
7126
7130
  return client2.trafficListEvents(input.project, params);
7127
7131
  }
7128
7132
  }),
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  setTelemetrySource,
30
30
  showFirstRunNotice,
31
31
  trackEvent
32
- } from "./chunk-ZHWWSU7H.js";
32
+ } from "./chunk-GYAKRVE2.js";
33
33
  import {
34
34
  CliError,
35
35
  EXIT_SYSTEM_ERROR,
@@ -46,7 +46,7 @@ import {
46
46
  saveConfig,
47
47
  saveConfigPatch,
48
48
  usageError
49
- } from "./chunk-IHRW6WWV.js";
49
+ } from "./chunk-WWZE7VDQ.js";
50
50
  import {
51
51
  apiKeys,
52
52
  createClient,
@@ -54,7 +54,7 @@ import {
54
54
  projects,
55
55
  queries,
56
56
  renderReportHtml
57
- } from "./chunk-EF67Y6CI.js";
57
+ } from "./chunk-LPEE5AYY.js";
58
58
  import {
59
59
  AdsOperationKinds,
60
60
  AdsOperationStates,
@@ -68,6 +68,7 @@ import {
68
68
  REPORT_PERIOD_OPTIONS,
69
69
  RunStatuses,
70
70
  TrafficEventKinds,
71
+ TrafficSeriesGranularities,
71
72
  adsActivateTreeRequestSchema,
72
73
  adsActivationGrantCreateRequestSchema,
73
74
  adsAdCreateRequestSchema,
@@ -95,7 +96,7 @@ import {
95
96
  providerQuotaPolicySchema,
96
97
  resolveProviderInput,
97
98
  winnabilityClassSchema
98
- } from "./chunk-BLSHHRRU.js";
99
+ } from "./chunk-K4FDDCYG.js";
99
100
 
100
101
  // src/cli.ts
101
102
  import { pathToFileURL } from "url";
@@ -5276,10 +5277,19 @@ async function trafficEvents(project, opts) {
5276
5277
  details: { project, kind: opts.kind }
5277
5278
  });
5278
5279
  }
5280
+ if (opts.granularity && opts.granularity !== TrafficSeriesGranularities.hour && opts.granularity !== TrafficSeriesGranularities.day) {
5281
+ throw new CliError({
5282
+ code: "TRAFFIC_INVALID_GRANULARITY",
5283
+ message: `--granularity must be one of: ${TrafficSeriesGranularities.hour}, ${TrafficSeriesGranularities.day}`,
5284
+ displayMessage: `Error: --granularity must be "${TrafficSeriesGranularities.hour}" or "${TrafficSeriesGranularities.day}"`,
5285
+ details: { project, granularity: opts.granularity }
5286
+ });
5287
+ }
5279
5288
  const params = {};
5280
5289
  if (opts.kind && opts.kind !== "all") params.kind = opts.kind;
5281
5290
  if (opts.source) params.sourceId = opts.source;
5282
5291
  if (opts.limit !== void 0) params.limit = opts.limit;
5292
+ if (opts.granularity) params.granularity = opts.granularity;
5283
5293
  if (opts.sinceMinutes !== void 0) {
5284
5294
  const since = new Date(Date.now() - opts.sinceMinutes * 6e4).toISOString();
5285
5295
  params.since = since;
@@ -5531,14 +5541,15 @@ var TRAFFIC_CLI_COMMANDS = [
5531
5541
  },
5532
5542
  {
5533
5543
  path: ["traffic", "events"],
5534
- usage: "canonry traffic events <project> [--kind crawler|ai-referral|all] [--source <id>] [--since-minutes 1440] [--since <iso>] [--until <iso>] [--limit 500] [--format json]",
5544
+ usage: "canonry traffic events <project> [--kind crawler|ai-referral|all] [--source <id>] [--since-minutes 1440] [--since <iso>] [--until <iso>] [--limit 500] [--granularity hour|day] [--format json]",
5535
5545
  options: {
5536
5546
  kind: stringOption(),
5537
5547
  source: stringOption(),
5538
5548
  "since-minutes": stringOption(),
5539
5549
  since: stringOption(),
5540
5550
  until: stringOption(),
5541
- limit: stringOption()
5551
+ limit: stringOption(),
5552
+ granularity: stringOption()
5542
5553
  },
5543
5554
  run: async (input) => {
5544
5555
  const project = requireProject(
@@ -5559,6 +5570,7 @@ var TRAFFIC_CLI_COMMANDS = [
5559
5570
  await trafficEvents(project, {
5560
5571
  kind: getString(input.values, "kind"),
5561
5572
  source: getString(input.values, "source"),
5573
+ granularity: getString(input.values, "granularity"),
5562
5574
  sinceMinutes,
5563
5575
  since: getString(input.values, "since"),
5564
5576
  until: getString(input.values, "until"),
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-ZHWWSU7H.js";
3
+ } from "./chunk-GYAKRVE2.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-IHRW6WWV.js";
7
- import "./chunk-EF67Y6CI.js";
8
- import "./chunk-BLSHHRRU.js";
6
+ } from "./chunk-WWZE7VDQ.js";
7
+ import "./chunk-LPEE5AYY.js";
8
+ import "./chunk-K4FDDCYG.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-EF67Y6CI.js";
4
- import "./chunk-BLSHHRRU.js";
3
+ } from "./chunk-LPEE5AYY.js";
4
+ import "./chunk-K4FDDCYG.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-IHRW6WWV.js";
6
+ } from "./chunk-WWZE7VDQ.js";
7
7
  import {
8
8
  isReadOnlyKey
9
- } from "./chunk-BLSHHRRU.js";
9
+ } from "./chunk-K4FDDCYG.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.129.0",
3
+ "version": "4.129.1",
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",
@@ -66,25 +66,25 @@
66
66
  "tsup": "^8.5.1",
67
67
  "tsx": "^4.19.0",
68
68
  "@ainyc/canonry-api-client": "0.0.0",
69
- "@ainyc/canonry-config": "0.0.0",
70
69
  "@ainyc/canonry-api-routes": "0.0.0",
71
- "@ainyc/canonry-db": "0.0.0",
70
+ "@ainyc/canonry-config": "0.0.0",
72
71
  "@ainyc/canonry-contracts": "0.0.0",
72
+ "@ainyc/canonry-db": "0.0.0",
73
73
  "@ainyc/canonry-integration-bing": "0.0.0",
74
74
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
75
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
76
75
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
77
- "@ainyc/canonry-integration-google": "0.0.0",
78
- "@ainyc/canonry-integration-google-places": "0.0.0",
79
- "@ainyc/canonry-integration-traffic": "0.0.0",
80
76
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
77
+ "@ainyc/canonry-integration-google-places": "0.0.0",
81
78
  "@ainyc/canonry-integration-wordpress": "0.0.0",
79
+ "@ainyc/canonry-integration-google": "0.0.0",
80
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
82
81
  "@ainyc/canonry-intelligence": "0.0.0",
82
+ "@ainyc/canonry-integration-traffic": "0.0.0",
83
83
  "@ainyc/canonry-provider-cdp": "0.0.0",
84
84
  "@ainyc/canonry-provider-claude": "0.0.0",
85
85
  "@ainyc/canonry-provider-gemini": "0.0.0",
86
- "@ainyc/canonry-provider-local": "0.0.0",
87
86
  "@ainyc/canonry-provider-openai": "0.0.0",
87
+ "@ainyc/canonry-provider-local": "0.0.0",
88
88
  "@ainyc/canonry-provider-perplexity": "0.0.0"
89
89
  },
90
90
  "scripts": {
@@ -1 +0,0 @@
1
- import{j as e,B as $e,C as Ue,r as h,L as Ee}from"./vendor-tanstack-Be8JdV5M.js";import{I as D,ci as Y,cj as u,T as Fe,i as le,B as Me,g as P,b as He,ck as Pe,cl as Oe}from"./index-BiCDkOH4.js";import{g as De,h as _e,i as Be,t as ze,R as Ge,d as Ke}from"./extract-error-message-DlVtjoPz.js";import{e as ee,i as We,C as ie,a as ce,c as Ve,h as Ye}from"./ChartPrimitives-6oFKrJFk.js";import{R as Xe,B as Qe,a as qe,X as Ze,Y as Je,T as et,f as X,g as Q}from"./vendor-recharts-DSxTZhaH.js";import{A as oe}from"./arrow-left-AmG4gtUR.js";import"./vendor-radix-CQ77EO2y.js";import"./vendor-markdown-6dwjPOKK.js";function q({value:s,label:a,delta:l,tone:n,description:x,tooltip:i,isNumeric:c=!0,progress:o,providerCoverage:d}){const v=2*Math.PI*48,m=Number.parseInt(s,10),L=typeof o=="number"&&Number.isFinite(o)?Math.min(Math.max(o,0),100)/100:c&&!Number.isNaN(m)?Math.min(m/100,1):.5,j=v*(1-L);return e.jsxs("div",{className:"score-gauge",children:[e.jsxs("div",{className:"gauge-ring-wrapper",children:[e.jsxs("svg",{className:"gauge-ring",viewBox:"0 0 120 120","aria-hidden":"true",children:[e.jsx("circle",{className:"gauge-bg",cx:"60",cy:"60",r:48,strokeWidth:6}),e.jsx("circle",{className:`gauge-fill gauge-fill-${n}`,cx:"60",cy:"60",r:48,strokeWidth:6,strokeDasharray:v,strokeDashoffset:j,transform:"rotate(-90 60 60)"})]}),e.jsx("div",{className:"gauge-center",children:e.jsx("span",{className:c?"gauge-value":"gauge-value-text",children:s.split(" / ")[0]})})]}),e.jsxs("p",{className:"gauge-label",children:[a,i&&e.jsx(D,{text:i})]}),e.jsx("p",{className:"gauge-delta",children:l}),d&&e.jsx("p",{className:"gauge-provider-coverage",children:d}),e.jsx("p",{className:"gauge-description",children:x})]})}const tt=[{value:"all",label:"All status"},{value:"2xx",label:"2xx success"},{value:"3xx",label:"3xx redirect"},{value:"4xx",label:"4xx client error"},{value:"5xx",label:"5xx server error"}],pe=[{value:"all",label:"All claims"},{value:Y.verified,label:"Verified"},{value:Y.claimed_unverified,label:"Claimed unverified"},{value:Y.unknown_ai_like,label:"Unknown AI-like"}];function st(s){return s==="all"?null:Number.parseInt(s[0],10)}function ge(s){switch(s.kind){case u.crawler:case u["ai-user-fetch"]:return s.botId;case u["ai-referral"]:return s.product}}function be(s){switch(s.kind){case u.crawler:case u["ai-user-fetch"]:return s.pathNormalized;case u["ai-referral"]:return s.landingPathNormalized}}function at(s){switch(s.kind){case u.crawler:case u["ai-user-fetch"]:return s.verificationStatus;case u["ai-referral"]:return null}}function ye(s,a){if(a==="hour")return s;const l=new Date(s),n=l.getFullYear(),x=String(l.getMonth()+1).padStart(2,"0"),i=String(l.getDate()).padStart(2,"0");return`${n}-${x}-${i}`}function rt(s,a,l){const n=a.pathQuery.trim().toLowerCase(),x=st(a.statusClass);return s.filter(i=>!(a.selectedBucket&&ye(i.tsHour,l)!==a.selectedBucket||a.identity&&ge(i)!==a.identity||a.operator&&i.operator!==a.operator||n&&!be(i).toLowerCase().includes(n)||x!==null&&Math.floor(i.status/100)!==x||a.verification!=="all"&&at(i)!==a.verification))}function nt(s,a){if(!s||typeof s!="object")return null;const l=s.activeTooltipIndex;let n;if(typeof l=="number")n=l;else if(typeof l=="string"&&l!=="")n=Number(l);else return null;return!Number.isInteger(n)||n<0||n>=a.length?null:a[n]?.bucket??null}const _=[{value:60,label:"1h",granularity:"hour",fetchLimit:500},{value:360,label:"6h",granularity:"hour",fetchLimit:500},{value:1440,label:"24h",granularity:"hour",fetchLimit:500},{value:10080,label:"7d",granularity:"hour",fetchLimit:1e3},{value:720*60,label:"30d",granularity:"day",fetchLimit:2e3},{value:2160*60,label:"90d",granularity:"day",fetchLimit:5e3}],ue=_.find(s=>s.label==="24h")??_[2],$=50,de=ee[0],xe=ee[2],he=ee[1],me=Pe();function je(s){const a=new Date(s);return`${a.getMonth()+1}/${a.getDate()} ${String(a.getHours()).padStart(2,"0")}:00`}function lt(s){const a=new Date(`${s}T00:00:00`);return`${a.getMonth()+1}/${a.getDate()}`}function U(s){if(!s)return"never";const a=Date.now()-new Date(s).getTime(),l=Math.floor(a/6e4);if(l<1)return"just now";if(l<60)return`${l}m ago`;const n=Math.floor(l/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}function fe({canGoBack:s,className:a}){const l="inline-flex items-center gap-1";if(s){const n=()=>{window.history.back()};return e.jsxs("button",{type:"button",onClick:n,className:`${l} ${a??""}`,children:[e.jsx(oe,{className:"size-3"})," Back"]})}return e.jsxs(Ee,{to:"/traffic",className:`${l} ${a??""}`,children:[e.jsx(oe,{className:"size-3"})," All sources"]})}function vt(){const s=$e({strict:!1}),a=s.projectName??"",l=Ue(),n=s.sourceId??"",[x,i]=h.useState(ue.value),[c,o]=h.useState(()=>new Set(["crawler","ai-user-fetch","ai-referral"])),[d,C]=h.useState(null),[f,v]=h.useState(""),[m,L]=h.useState(""),[j,B]=h.useState(""),[k,z]=h.useState("all"),[T,G]=h.useState("all"),[ve,K]=h.useState(1),[te,se]=h.useState(null),[ae,re]=h.useState(null),b=h.useMemo(()=>_.find(t=>t.value===x)??ue,[x]),W=De(a||null,n||null),y=_e(a||null,{kind:"all",sourceId:n||void 0,sinceMinutes:x,limit:b.fetchLimit}),E=Be(a||null,n||null),r=W.data,N=y.data?.events??[],S=y.data?.totals,F=h.useMemo(()=>N.filter(t=>{switch(t.kind){case u.crawler:return c.has("crawler");case u["ai-user-fetch"]:return c.has("ai-user-fetch");case u["ai-referral"]:return c.has("ai-referral")}}),[N,c]),Ne=h.useMemo(()=>{const t=new Set;for(const p of N)t.add(ge(p));return f&&t.add(f),[...t].sort((p,g)=>p.localeCompare(g))},[N,f]),Se=h.useMemo(()=>{const t=new Set;for(const p of N)t.add(p.operator);return m&&t.add(m),[...t].sort((p,g)=>p.localeCompare(g))},[N,m]),w=h.useMemo(()=>rt(F,{selectedBucket:d,identity:f,operator:m,pathQuery:j,statusClass:k,verification:T},b.granularity),[F,d,f,m,j,k,T,b.granularity]);h.useEffect(()=>{K(1)},[F,d,f,m,j,k,T]);const M=Math.max(1,Math.ceil(w.length/$)),H=Math.min(Math.max(1,ve),M),R=(H-1)*$,we=h.useMemo(()=>w.slice(R,R+$),[w,R]),Ce=w.length>$,ke=w.length===0?0:R+1,Te=Math.min(R+$,w.length),ne=h.useMemo(()=>d?O(d,b.granularity):null,[d,b.granularity]),I=h.useMemo(()=>it(N,b.granularity,y.data?.windowStart,y.data?.windowEnd),[N,b.granularity,y.data?.windowStart,y.data?.windowEnd]),V=t=>{o(p=>{const g=new Set(p);return g.has(t)?g.size>1&&g.delete(t):g.add(t),g})},Ie=t=>{const p=nt(t,I);p&&C(g=>g===p?null:p)},Ae=()=>{C(null),v(""),L(""),B(""),z("all"),G("all")},Le=!!(d||f||m||j.trim()||k!=="all"||T!=="all"),Re=async()=>{se(null),re(null);try{const t=await E.mutateAsync({sinceMinutes:60});re(`Pulled ${t.pulledEvents} entries · ${t.crawlerHits} crawler · ${t.aiUserFetchHits} AI user fetch · ${t.aiReferralHits} AI referral · ${t.unknownHits} unknown`)}catch(t){se(Ke(t))}};return!a||!n?e.jsx("div",{className:"page-container",children:e.jsx("p",{className:"text-sm text-muted",children:"Missing project name or source id in URL."})}):W.isLoading?e.jsx("div",{className:"page-container",children:e.jsx("p",{className:"text-sm text-muted",children:"Loading source…"})}):W.isError||!r?e.jsxs("div",{className:"page-container",children:[e.jsx("p",{className:"text-sm text-negative",children:"Could not load this source."}),e.jsx(fe,{canGoBack:l,className:"mt-2 text-xs text-secondary hover:text-strong"})]}):e.jsxs("div",{className:"page-container space-y-8",children:[e.jsxs("div",{className:"page-header",children:[e.jsxs("div",{className:"page-header-left",children:[e.jsx(fe,{canGoBack:l,className:"text-xs text-muted hover:text-strong"}),e.jsx("h1",{className:"page-title mt-2",children:r.displayName}),e.jsxs("p",{className:"page-subtitle",children:[r.sourceType," · project ",e.jsx("span",{className:"text-neutral",children:a})," ·",e.jsx("span",{className:"ml-1 font-mono text-secondary",children:r.id})]})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(Fe,{tone:ze(r.status),children:r.status}),!le()&&e.jsxs(Me,{type:"button",variant:"outline",size:"sm",disabled:E.isPending,onClick:()=>{Re()},children:[e.jsx(Ge,{className:`size-3.5 ${E.isPending?"animate-spin":""}`}),E.isPending?"Syncing…":"Sync now"]})]})]}),te?e.jsx("div",{className:"rounded-md border border-negative-800/50 bg-negative-950/30 px-3 py-2 text-xs text-negative-200",children:te}):null,ae?e.jsx("div",{className:"rounded-md border border-positive-800/50 bg-positive-950/30 px-3 py-2 text-xs text-positive-200",children:ae}):null,e.jsxs("section",{className:"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",children:[e.jsx(q,{label:"24h content crawls",value:String(r.totals24h.crawlerContentHits),delta:r.lastSyncedAt?`last sync ${U(r.lastSyncedAt)}`:"never synced",tone:r.totals24h.crawlerContentHits>0?"positive":"neutral",description:`Pages crawled — ${r.totals24h.crawlerInfraHits.toLocaleString("en-US")} sitemap/robots/asset fetches excluded.`,tooltip:`Content-page crawls by GPTBot, OAI-SearchBot, PerplexityBot, Googlebot, etc. Infrastructure fetches (sitemap ${r.totals24h.crawlerSegments.sitemap.toLocaleString("en-US")}, robots ${r.totals24h.crawlerSegments.robots.toLocaleString("en-US")}, assets ${r.totals24h.crawlerSegments.asset.toLocaleString("en-US")}) are tracked separately so polling doesn't inflate this number. Total crawler hits: ${r.totals24h.crawlerHits.toLocaleString("en-US")}.`,isNumeric:!0,progress:Math.min(100,Math.round(r.totals24h.crawlerContentHits/1e3*100))}),e.jsx(q,{label:"24h AI user fetches",value:String(r.totals24h.aiUserFetchHits),delta:r.lastSyncedAt?`last sync ${U(r.lastSyncedAt)}`:"never synced",tone:r.totals24h.aiUserFetchHits>0?"positive":"neutral",description:"ChatGPT-User, Perplexity-User — fetches initiated by a real user inside an AI surface (citation click, URL read).",isNumeric:!0,progress:Math.min(100,Math.round(r.totals24h.aiUserFetchHits/1e3*100))}),e.jsx(q,{label:"24h AI referral sessions",value:String(r.totals24h.aiReferralHits),delta:r.lastSyncedAt?`last sync ${U(r.lastSyncedAt)}`:"never synced",tone:r.totals24h.aiReferralHits>0?"positive":"neutral",description:"Browser click-throughs from chatgpt.com, perplexity.ai, etc. (Referer / UTM evidence).",isNumeric:!0,progress:Math.min(100,Math.round(r.totals24h.aiReferralHits/1e3*100))})]}),e.jsxs("section",{children:[e.jsx("p",{className:"mb-4 text-[10px] font-semibold uppercase tracking-wider text-muted",children:"Latest sync run"}),r.latestRun?e.jsxs(P,{className:"p-4 text-sm",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-x-6 gap-y-1.5",children:[e.jsxs("span",{className:"text-heading",children:["Status: ",e.jsx("span",{className:"font-medium",children:r.latestRun.status})]}),e.jsxs("span",{className:"text-muted",children:["Started: ",U(r.latestRun.startedAt)]}),r.latestRun.finishedAt?e.jsxs("span",{className:"text-muted",children:["Finished: ",U(r.latestRun.finishedAt)]}):null,e.jsx("span",{className:"font-mono text-[11px] text-faint",children:r.latestRun.runId})]}),r.latestRun.error?e.jsx("p",{className:"mt-2 rounded border border-negative-900/40 bg-negative-950/30 px-3 py-2 text-xs text-negative",children:r.latestRun.error}):null]}):e.jsx(P,{className:"px-4 py-3 text-sm text-muted",children:le()?"No traffic-sync runs recorded yet.":'No traffic-sync runs recorded yet. Hit "Sync now" above to create one.'})]}),e.jsxs("section",{children:[e.jsxs("div",{className:"mb-4 flex flex-wrap items-end justify-between gap-x-4 gap-y-3",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] font-semibold uppercase tracking-wider text-muted",children:"Events"}),e.jsx("h2",{className:"mt-1 text-base font-semibold text-primary",children:b.granularity==="day"?"Daily rollups":"Hourly rollups"}),S?e.jsxs("p",{className:"mt-1.5 text-xs text-muted",children:[S.crawlerContentHits.toLocaleString("en-US")," content crawls ·"," ",S.crawlerInfraHits.toLocaleString("en-US")," infra (sitemap/robots/assets) ·"," ",S.aiUserFetchHits.toLocaleString("en-US")," AI user fetches ·"," ",S.aiReferralHits.toLocaleString("en-US")," AI referral sessions · last ",b.label," · ",me]}):null]}),e.jsx("div",{className:"filter-row mb-0",role:"toolbar","aria-label":"Window",children:_.map(t=>e.jsx("button",{type:"button",className:`filter-chip ${x===t.value?"filter-chip-active":""}`,"aria-pressed":x===t.value,onClick:()=>{i(t.value),C(null)},children:t.label},t.value))})]}),e.jsxs(P,{className:"p-4",children:[e.jsxs("div",{className:"mb-3 flex flex-wrap items-center gap-2",role:"toolbar","aria-label":"Series",children:[e.jsx(J,{label:"Crawler",color:de,count:S?.crawlerHits??0,active:c.has("crawler"),onToggle:()=>V("crawler")}),e.jsx(J,{label:"AI user fetches",color:xe,count:S?.aiUserFetchHits??0,active:c.has("ai-user-fetch"),onToggle:()=>V("ai-user-fetch")}),e.jsx(J,{label:"AI referral sessions",color:he,count:S?.aiReferralHits??0,active:c.has("ai-referral"),onToggle:()=>V("ai-referral")})]}),y.isError?e.jsxs("p",{className:"py-12 text-center text-xs text-negative-400",children:["Failed to load events: ",y.error instanceof Error?y.error.message:"Unknown error"]}):y.isLoading?e.jsx("p",{className:"py-12 text-center text-xs text-muted",children:"Loading events…"}):I.length===0?e.jsx("p",{className:"py-12 text-center text-xs text-muted",children:"No events in this window."}):e.jsx("div",{className:"h-72",children:e.jsx(Xe,{children:e.jsxs(Qe,{data:I,margin:{top:4,right:4,bottom:0,left:0},onClick:Ie,style:{cursor:"pointer"},children:[e.jsx(qe,{stroke:We,strokeDasharray:"3 3"}),e.jsx(Ze,{dataKey:"label",tick:ce,stroke:ie,interval:"preserveStartEnd",minTickGap:b.granularity==="day"?24:32}),e.jsx(Je,{tick:ce,stroke:ie,allowDecimals:!1}),e.jsx(et,{...Ve}),c.has("crawler")?e.jsx(X,{dataKey:"crawler",name:"Crawler",fill:de,stackId:"a",children:I.map(t=>e.jsx(Q,{fillOpacity:d&&d!==t.bucket?.25:1},t.bucket))}):null,c.has("ai-user-fetch")?e.jsx(X,{dataKey:"aiUserFetch",name:"AI user fetch",fill:xe,stackId:"a",children:I.map(t=>e.jsx(Q,{fillOpacity:d&&d!==t.bucket?.25:1},t.bucket))}):null,c.has("ai-referral")?e.jsx(X,{dataKey:"aiReferral",name:"AI referral",fill:he,stackId:"a",children:I.map(t=>e.jsx(Q,{fillOpacity:d&&d!==t.bucket?.25:1},t.bucket))}):null]})})})]})]}),e.jsxs("section",{children:[e.jsxs("div",{className:"mb-4 flex flex-wrap items-end justify-between gap-x-4 gap-y-2",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] font-semibold uppercase tracking-wider text-muted",children:"Event rows"}),e.jsxs("p",{className:"mt-1 text-xs text-muted",children:["Showing ",e.jsx("span",{className:"tabular-nums text-neutral",children:w.length.toLocaleString("en-US")})," of"," ",e.jsx("span",{className:"tabular-nums text-muted",children:F.length.toLocaleString("en-US")})," events · ",me]})]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsxs("span",{className:"inline-flex items-center text-secondary",children:[e.jsxs("select",{"aria-label":"Filter by identity",value:f,onChange:t=>v(t.target.value),className:"rounded-md border border-base bg-bg px-2.5 py-1.5 text-xs text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:[e.jsx("option",{value:"",children:"All identities"}),Ne.map(t=>e.jsx("option",{value:t,children:t},t))]}),e.jsx(D,{text:"The specific bot or AI product making the request (e.g., GPTBot, ChatGPT-User, Perplexity). One operator usually runs several identities."})]}),e.jsxs("span",{className:"inline-flex items-center text-secondary",children:[e.jsxs("select",{"aria-label":"Filter by operator",value:m,onChange:t=>L(t.target.value),className:"rounded-md border border-base bg-bg px-2.5 py-1.5 text-xs text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:[e.jsx("option",{value:"",children:"All operators"}),Se.map(t=>e.jsx("option",{value:t,children:t},t))]}),e.jsx(D,{text:"The company that runs the bot or product (e.g., OpenAI, Anthropic, Perplexity). Filtering by operator includes every identity under that company."})]}),e.jsx("select",{"aria-label":"Filter by HTTP status class",value:k,onChange:t=>z(t.target.value),className:"rounded-md border border-base bg-bg px-2.5 py-1.5 text-xs text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:tt.map(t=>e.jsx("option",{value:t.value,children:t.label},t.value))}),e.jsx("select",{"aria-label":"Filter by verification claim",value:T,onChange:t=>G(t.target.value),className:"rounded-md border border-base bg-bg px-2.5 py-1.5 text-xs text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:pe.map(t=>e.jsx("option",{value:t.value,children:t.label},t.value))}),e.jsx("input",{type:"search","aria-label":"Filter by path",placeholder:"path contains…",value:j,onChange:t=>B(t.target.value),className:"w-44 rounded-md border border-base bg-bg px-2.5 py-1.5 text-xs text-strong placeholder:text-faint focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600"})]})]}),Le?e.jsxs("div",{className:"mb-3 flex flex-wrap items-center gap-2",children:[ne?e.jsx(A,{label:`Bucket: ${ne}`,onClear:()=>C(null)}):null,f?e.jsx(A,{label:`Identity: ${f}`,onClear:()=>v("")}):null,m?e.jsx(A,{label:`Operator: ${m}`,onClear:()=>L("")}):null,j.trim()?e.jsx(A,{label:`Path: ${j.trim()}`,onClear:()=>B("")}):null,k!=="all"?e.jsx(A,{label:`Status: ${k}`,onClear:()=>z("all")}):null,T!=="all"?e.jsx(A,{label:`Claim: ${ct(T)}`,onClear:()=>G("all")}):null,e.jsx("button",{type:"button",onClick:Ae,className:"text-xs text-muted underline-offset-4 hover:text-strong hover:underline",children:"Clear all"})]}):null,e.jsx(xt,{events:we}),Ce?e.jsxs("div",{className:"mt-3 flex flex-wrap items-center justify-between gap-x-4 gap-y-2 text-xs text-secondary",children:[e.jsxs("p",{className:"tabular-nums",children:["Showing ",e.jsx("span",{className:"text-neutral",children:ke.toLocaleString("en-US")}),"–",e.jsx("span",{className:"text-neutral",children:Te.toLocaleString("en-US")})," of"," ",e.jsx("span",{className:"text-neutral",children:w.length.toLocaleString("en-US")})," events"]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("button",{type:"button",onClick:()=>K(t=>Math.max(1,t-1)),disabled:H<=1,"aria-label":"Previous page",className:"inline-flex items-center gap-1 rounded-md border border-base bg-bg px-2.5 py-1.5 text-strong transition hover:border-strong hover:text-primary disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:border-base disabled:hover:text-strong",children:[e.jsx(Ye,{className:"size-3.5"}),"Prev"]}),e.jsxs("span",{className:"tabular-nums",children:["Page ",e.jsx("span",{className:"text-strong",children:H})," of"," ",e.jsx("span",{className:"text-strong",children:M})]}),e.jsxs("button",{type:"button",onClick:()=>K(t=>Math.min(M,t+1)),disabled:H>=M,"aria-label":"Next page",className:"inline-flex items-center gap-1 rounded-md border border-base bg-bg px-2.5 py-1.5 text-strong transition hover:border-strong hover:text-primary disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:border-base disabled:hover:text-strong",children:["Next",e.jsx(He,{className:"size-3.5"})]})]})]}):null]})]})}function Z(s,a){return{bucket:s,label:a,crawler:0,aiUserFetch:0,aiReferral:0}}function O(s,a){return a==="day"?lt(s):je(s)}function it(s,a,l,n){const x=new Map;for(const i of s){const c=ye(i.tsHour,a);let o=x.get(c);switch(o||(o=Z(c,O(c,a)),x.set(c,o)),i.kind){case u.crawler:o.crawler+=i.hits;break;case u["ai-user-fetch"]:o.aiUserFetch+=i.hits;break;case u["ai-referral"]:o.aiReferral+=i.hits;break}}if(l&&n){const i=new Date(l),c=new Date(n);if(a==="day"){const o=new Date(Date.UTC(i.getUTCFullYear(),i.getUTCMonth(),i.getUTCDate())),d=new Date(Date.UTC(c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate()));for(;o<=d;){const C=o.getUTCFullYear(),f=String(o.getUTCMonth()+1).padStart(2,"0"),v=String(o.getUTCDate()).padStart(2,"0"),m=`${C}-${f}-${v}`;x.has(m)||x.set(m,Z(m,O(m,a))),o.setUTCDate(o.getUTCDate()+1)}}else{const o=new Date(i);for(o.setUTCMinutes(0,0,0);o<=c;){const d=o.toISOString();x.has(d)||x.set(d,Z(d,O(d,a))),o.setUTCHours(o.getUTCHours()+1)}}}return[...x.values()].sort((i,c)=>i.bucket<c.bucket?-1:i.bucket>c.bucket?1:0)}function ct(s){return pe.find(a=>a.value===s)?.label??s}function A({label:s,onClear:a}){return e.jsxs("span",{className:"inline-flex items-center gap-1.5 rounded-full border border-strong bg-surface-inset px-2.5 py-1 text-[11px] text-strong",children:[s,e.jsx("button",{type:"button",onClick:a,"aria-label":`Clear ${s}`,className:"rounded-full p-0.5 text-secondary hover:bg-mono-700/60 hover:text-heading",children:e.jsx(Oe,{className:"size-3"})})]})}function J({label:s,color:a,count:l,active:n,onToggle:x}){return e.jsxs("button",{type:"button",onClick:x,"aria-pressed":n,className:`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium transition ${n?"border-strong bg-surface-inset text-heading":"border-base bg-transparent text-muted hover:text-neutral"}`,children:[e.jsx("span",{"aria-hidden":"true",className:`size-2 rounded-full transition-opacity ${n?"opacity-100":"opacity-30"}`,style:{backgroundColor:a}}),e.jsx("span",{children:s}),e.jsx("span",{className:`tabular-nums ${n?"text-secondary":"text-faint"}`,children:l.toLocaleString("en-US")})]})}function ot(s){switch(s){case u.crawler:return"Crawler";case u["ai-user-fetch"]:return"AI hit";case u["ai-referral"]:return"AI referral"}}function ut(s){switch(s.kind){case u.crawler:case u["ai-user-fetch"]:return s.botId;case u["ai-referral"]:return s.product}}function dt(s){switch(s.kind){case u.crawler:case u["ai-user-fetch"]:return`${s.verificationStatus} · HTTP ${s.status}`;case u["ai-referral"]:return`${s.evidenceType} · ${s.sourceDomain}`}}function xt({events:s}){return s.length===0?e.jsx(P,{className:"p-6 text-center text-sm text-muted",children:"No event rows match the current filters."}):e.jsx("div",{className:"rounded-xl border border-default bg-surface overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{className:"bg-bg-elevated/50 text-[10px] font-semibold uppercase tracking-wider text-muted",children:e.jsxs("tr",{children:[e.jsx("th",{className:"px-4 py-2 text-left",children:"Hour"}),e.jsx("th",{className:"px-4 py-2 text-left",children:"Kind"}),e.jsx("th",{className:"px-4 py-2 text-left",children:"Identity"}),e.jsx("th",{className:"px-4 py-2 text-left",children:"Evidence / status"}),e.jsx("th",{className:"px-4 py-2 text-left",children:"Path"}),e.jsx("th",{className:"px-4 py-2 text-right",children:e.jsxs("span",{className:"inline-flex items-center",children:["Hits",e.jsx(D,{text:"Each row is one hour-bucket, not one request. Hits is the number of requests in that hour that shared the same identity, path, HTTP status, and verification claim."})]})})]})}),e.jsx("tbody",{className:"divide-y divide-mono-800/60",children:s.map((a,l)=>e.jsxs("tr",{className:"hover:bg-bg-elevated/40 transition-colors",children:[e.jsx("td",{className:"px-4 py-2 font-mono text-xs text-neutral",children:je(a.tsHour)}),e.jsx("td",{className:"px-4 py-2 text-neutral",children:ot(a.kind)}),e.jsxs("td",{className:"px-4 py-2 text-heading",children:[ut(a),e.jsx("span",{className:"ml-2 text-[11px] text-muted",children:a.operator})]}),e.jsx("td",{className:"px-4 py-2 text-neutral",children:dt(a)}),e.jsx("td",{className:"px-4 py-2 truncate font-mono text-xs text-neutral",children:be(a)}),e.jsx("td",{className:"px-4 py-2 text-right tabular-nums text-heading",children:a.hits})]},`${a.kind}:${a.tsHour}:${l}`))})]})})}export{vt as TrafficSourceDetailPage};
@@ -1 +0,0 @@
1
- import{c as m,k as a,cm as l,cg as y,cn as S,aF as g,m as t,co as v,ch as s,cp as p,cq as T,cr as h,cs as q}from"./index-BiCDkOH4.js";import{u as c,r as C,n as o,o as u}from"./vendor-tanstack-Be8JdV5M.js";const w=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],F=m("refresh-cw",w);function P(e){switch(e){case s.connected:return"positive";case s.paused:return"caution";case s.error:return"negative";case s.archived:return"neutral"}}function M(e){const n={};return e.kind&&e.kind!=="all"&&(n.kind=e.kind),e.sourceId&&(n.sourceId=e.sourceId),e.sinceMinutes!==void 0&&(n.since=new Date(Date.now()-e.sinceMinutes*6e4).toISOString()),e.limit!==void 0&&(n.limit=String(e.limit)),n}function f(e){e.invalidateQueries({predicate:n=>{const r=n.queryKey[0];return typeof r?._id=="string"&&r._id.startsWith("getApiV1ProjectsByNameTraffic")}})}function A(e){return c({...l({client:t,path:{name:e??""}}),enabled:!!e,staleTime:a})}function R(e){return c({...q({client:t,path:{name:e??""}}),enabled:!!e,staleTime:a})}function V(e,n){return c({...y({client:t,path:{name:e??"",id:n??""}}),enabled:!!(e&&n),staleTime:a})}function I(e,n){const r=C.useMemo(()=>M(n),[n.kind,n.sourceId,n.sinceMinutes,n.limit]);return c({...S({client:t,path:{name:e??""},query:r}),enabled:!!e,staleTime:a})}function b(e){const n=o();return u({mutationFn:r=>{if(!e)throw new Error("Project is required to connect a Cloud Run source");return h(e,r)},onSuccess:()=>{e&&f(n)}})}function Q(e){const n=o();return u({mutationFn:r=>{if(!e)throw new Error("Project is required to connect a WordPress source");return p(e,r)},onSuccess:()=>{e&&f(n)}})}function N(e){const n=o();return u({mutationFn:r=>{if(!e)throw new Error("Project is required to connect a Vercel source");return T(e,r)},onSuccess:()=>{e&&f(n)}})}function _(e,n){const r=o();return u({mutationFn:d=>{if(!e||!n)throw new Error("Project and sourceId are required to sync");return v(e,n,d??void 0)},onSuccess:()=>{e&&(f(r),r.invalidateQueries({queryKey:g({client:t})}))}})}function i(e){return typeof e=="object"&&e!==null}function E(e){if(e instanceof Error){const n=e;return{message:e.message,code:typeof n.code=="string"?n.code:void 0,details:i(n.details)?n.details:void 0}}if(i(e)){const n=e.error;if(i(n))return{message:typeof n.message=="string"?n.message:"Request failed",code:typeof n.code=="string"?n.code:void 0,details:i(n.details)?n.details:void 0};if(typeof n=="string")return{message:n};const r=e.message;if(typeof r=="string")return{message:r}}return typeof e=="string"?{message:e}:{message:String(e)}}function O(e){return E(e).message}export{F as R,Q as a,N as b,b as c,O as d,E as e,A as f,V as g,I as h,_ as i,P as t,R as u};