@canonry/canonry 7.95.0 → 7.96.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 (138) hide show
  1. package/assets/agent-workspace/skills/aero/references/ai-traffic-tracking.md +4 -1
  2. package/assets/agent-workspace/skills/canonry/references/ai-traffic-tracking.md +4 -1
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +6 -0
  4. package/assets/assets/{AgentSetupPage-BV0TvyWO.js → AgentSetupPage-DBiPrrdJ.js} +1 -1
  5. package/assets/assets/{AuditHistoryPanel-CTVhEVjr.js → AuditHistoryPanel-DSnIs064.js} +1 -1
  6. package/assets/assets/{BacklinksPage-CE1Bfpf6.js → BacklinksPage-tPiG0nEP.js} +1 -1
  7. package/assets/assets/{HistoryPage-BhqA-Udl.js → HistoryPage-sxYHrfNk.js} +1 -1
  8. package/assets/assets/{MeasurementPropertyPage-BiJ06H7w.js → MeasurementPropertyPage-B8EbifR2.js} +1 -1
  9. package/assets/assets/ProjectPage-C1_A7iGy.js +11 -0
  10. package/assets/assets/{RunsPage-BII3_JsE.js → RunsPage-RE6nLBTv.js} +1 -1
  11. package/assets/assets/{SettingsPage-DS4ivpsX.js → SettingsPage-Dn93SUD1.js} +1 -1
  12. package/assets/assets/{SiteHealthSection-IywsGGIA.js → SiteHealthSection-BEBYoCNu.js} +2 -2
  13. package/assets/assets/ToneBadge-DFlmWh2A.js +60 -0
  14. package/assets/assets/{TrafficPage-Bg9JrnxC.js → TrafficPage-cNokjIJa.js} +1 -1
  15. package/assets/assets/{TrafficSourceDetailPage-BuD8BlaT.js → TrafficSourceDetailPage-DFGbx0zs.js} +1 -1
  16. package/assets/assets/{account-context-ylqeghOr.js → account-context-RM43ek4w.js} +1 -1
  17. package/assets/assets/{agent-trust-copy-DxpavxIA.js → agent-trust-copy-CRZ_oe24.js} +1 -1
  18. package/assets/assets/{card-rZ7BA4sl.js → card-GFeSoV1m.js} +1 -1
  19. package/assets/assets/{format-helpers-PdOXn5Yf.js → format-helpers-4dA9HFgE.js} +1 -1
  20. package/assets/assets/{index-CU7tAhKY.js → index-CohgAQ9L.js} +4 -4
  21. package/assets/assets/{query-client-Cq4kP41m.js → query-client-B3Z7Vqgv.js} +1 -1
  22. package/assets/assets/{react-query-DVctucqC.js → react-query-HC0PcMhZ.js} +1 -1
  23. package/assets/assets/{react-sigma_core.esm.min-DYhAEr2t.js → react-sigma_core.esm.min-IsYtRW95.js} +1 -1
  24. package/assets/assets/{server-traffic-Owp6jMwT.js → server-traffic-BtWOYZXf.js} +1 -1
  25. package/assets/assets/{service-y3nQY3Ue.js → service-Bu4M0T0x.js} +1 -1
  26. package/assets/index.html +7 -7
  27. package/dist/{ads-GN7QSEUV.js → ads-NS4KNR2U.js} +3 -3
  28. package/dist/{agent-J46OIQHH.js → agent-773TJYGJ.js} +7 -7
  29. package/dist/{agent-ask-755DZ4YB.js → agent-ask-MQXNRMAN.js} +3 -3
  30. package/dist/{agent-conversations-QO7C6F6L.js → agent-conversations-5EEEEWEQ.js} +3 -3
  31. package/dist/{agent-journeys-SVI6LWKM.js → agent-journeys-U5ITMUIM.js} +3 -3
  32. package/dist/{agent-memory-V3ZJZT5W.js → agent-memory-6PEE54ST.js} +3 -3
  33. package/dist/{agent-providers-QIHZ222U.js → agent-providers-6D76GSYA.js} +3 -3
  34. package/dist/{agent-transcript-6TS3ZOCA.js → agent-transcript-6M5RYZIR.js} +3 -3
  35. package/dist/{analytics-LFT5OAOR.js → analytics-WBFQJTHV.js} +3 -3
  36. package/dist/{apply-3XXMVLBK.js → apply-AGNDBHNV.js} +3 -3
  37. package/dist/{backfill-F62Y47XV.js → backfill-ZDPS7VON.js} +10 -10
  38. package/dist/{backlinks-AOA5KHHB.js → backlinks-TNPNJXPS.js} +3 -3
  39. package/dist/{bing-75F4W4E2.js → bing-5WB7JCBP.js} +3 -3
  40. package/dist/{bootstrap-CDGVAICZ.js → bootstrap-XF77XL7U.js} +10 -10
  41. package/dist/{cdp-YBCWRWY4.js → cdp-NBTJLUPM.js} +7 -7
  42. package/dist/{chunk-TXW2FPNJ.js → chunk-24CUAPPY.js} +4 -4
  43. package/dist/{chunk-Q6WHXY7D.js → chunk-35AEUG77.js} +1 -1
  44. package/dist/{chunk-GUVN5OSX.js → chunk-377PJ4AT.js} +3 -3
  45. package/dist/{chunk-YTRXALSU.js → chunk-3FHPUOMW.js} +1 -1
  46. package/dist/{chunk-IATTIKXF.js → chunk-4GECW7XZ.js} +3 -3
  47. package/dist/{chunk-FESPLVBO.js → chunk-5VLCPNTR.js} +6 -6
  48. package/dist/{chunk-6RDBULDN.js → chunk-6X24IDPZ.js} +1 -1
  49. package/dist/{chunk-NMUL63XG.js → chunk-CPJX2RWA.js} +2 -2
  50. package/dist/{chunk-C6UU2PDJ.js → chunk-F2O42DSO.js} +21 -21
  51. package/dist/{chunk-BUZBBVDZ.js → chunk-FUMYCZ62.js} +1 -1
  52. package/dist/{chunk-TG5D4E2Y.js → chunk-G6JSK2RY.js} +1 -1
  53. package/dist/{chunk-46YXUJSW.js → chunk-HKUH75YH.js} +2 -2
  54. package/dist/{chunk-73AUPWSG.js → chunk-HN2YNYPO.js} +4 -4
  55. package/dist/{chunk-JVOOKVCW.js → chunk-I663X7WB.js} +2 -2
  56. package/dist/{chunk-T3FYWUQL.js → chunk-IO4BZLM7.js} +2 -2
  57. package/dist/{chunk-J3NS6NGM.js → chunk-JGLX5WU6.js} +1 -1
  58. package/dist/{chunk-WK6IPTTM.js → chunk-JLC3CCFM.js} +4 -4
  59. package/dist/{chunk-RPPB4H4D.js → chunk-KCKUCJOG.js} +1 -1
  60. package/dist/{chunk-LHU3KIKG.js → chunk-LXKWFM74.js} +2 -2
  61. package/dist/{chunk-6S6XEEGX.js → chunk-MAOKDX3W.js} +5 -5
  62. package/dist/{chunk-NVWTGVZ4.js → chunk-MJIL4BWR.js} +3 -3
  63. package/dist/{chunk-KMT7I5KZ.js → chunk-N4SEITGT.js} +5 -5
  64. package/dist/{chunk-MKLAX5KY.js → chunk-OCXDY2DG.js} +1 -1
  65. package/dist/{chunk-FWY5HUSB.js → chunk-PC7ZUW2H.js} +2 -2
  66. package/dist/{chunk-7PUUMJXY.js → chunk-PSCXQGJE.js} +109 -50
  67. package/dist/{chunk-NJG6WFRK.js → chunk-QBIIQF3J.js} +1 -1
  68. package/dist/{chunk-TS5Q63NE.js → chunk-S3HNS2YJ.js} +3 -3
  69. package/dist/{chunk-EWSVH5TU.js → chunk-SDCH5EBA.js} +1 -1
  70. package/dist/{chunk-L7W72K4X.js → chunk-SPWGDODX.js} +358 -358
  71. package/dist/{chunk-PHOMW3R4.js → chunk-SXHD2NFT.js} +4 -4
  72. package/dist/{chunk-VOP74COR.js → chunk-UPALIEP2.js} +2 -2
  73. package/dist/{chunk-CVCUADCM.js → chunk-W4ZRFNOX.js} +43 -15
  74. package/dist/{chunk-A6O6X6FU.js → chunk-XOCSSHYD.js} +1 -1
  75. package/dist/{chunk-KK7NWOGH.js → chunk-YL77WFHQ.js} +7 -7
  76. package/dist/{chunk-5TPLZD4I.js → chunk-ZZVRDHSK.js} +1 -1
  77. package/dist/{citations-SQYZTJQ6.js → citations-MZ6XQDI3.js} +3 -3
  78. package/dist/{cli-catalog-N3JLEU77.js → cli-catalog-QPB2ADGD.js} +3 -3
  79. package/dist/{cli-commands-QOJJMX2X.js → cli-commands-RRHOMF6X.js} +7 -7
  80. package/dist/cli.js +13 -13
  81. package/dist/{competitor-C5JAHHR7.js → competitor-VF77UMUR.js} +3 -3
  82. package/dist/{content-RVPODQI6.js → content-BINAMQFF.js} +3 -3
  83. package/dist/{dashboard-3NAY2SX6.js → dashboard-OE4BGMKP.js} +6 -6
  84. package/dist/{demo-OSYK452E.js → demo-3HVJUP3G.js} +1 -1
  85. package/dist/{demo-server-GO4V5UXK.js → demo-server-BEFPBA7P.js} +12 -12
  86. package/dist/{discover-U2DNK2TO.js → discover-3XUPBCXL.js} +3 -3
  87. package/dist/{discover-eval-ESLS56AH.js → discover-eval-3DWJPIU2.js} +3 -3
  88. package/dist/{doctor-SBF54WXE.js → doctor-KYWVYS74.js} +3 -3
  89. package/dist/{evidence-WHDCM7Z5.js → evidence-DCTXENHR.js} +3 -3
  90. package/dist/{export-cmd-7CHS6K3S.js → export-cmd-E5OUTSMR.js} +3 -3
  91. package/dist/{ga-XRASDAVX.js → ga-KY7MHW2G.js} +3 -3
  92. package/dist/{gbp-FEOFMTBH.js → gbp-2TJ6JACW.js} +3 -3
  93. package/dist/{google-MWJ4HAUB.js → google-BZKYIIA6.js} +4 -4
  94. package/dist/{google-marketing-GELTQ32D.js → google-marketing-6ACZP4JB.js} +1 -1
  95. package/dist/{health-cmd-IO7YMTRQ.js → health-cmd-LCVJY5MG.js} +3 -3
  96. package/dist/{history-I74D3NTT.js → history-MAZOPZZ5.js} +3 -3
  97. package/dist/index.d.ts +41 -41
  98. package/dist/index.js +25 -25
  99. package/dist/{init-ZLJMCJ3Q.js → init-Z35R7A6X.js} +12 -12
  100. package/dist/{insights-EAMVEB5Y.js → insights-PAZHZP74.js} +3 -3
  101. package/dist/intelligence-service-YA7PBFQV.js +16 -0
  102. package/dist/{journeys-E373TZZS.js → journeys-H6OPK5XY.js} +3 -3
  103. package/dist/{keys-27JRRWKZ.js → keys-DF6LV4T5.js} +3 -3
  104. package/dist/{keyword-45H2TBRG.js → keyword-ILICCRQK.js} +3 -3
  105. package/dist/{logs-K4K4OJDY.js → logs-PKKCPDIN.js} +3 -3
  106. package/dist/{mcp-734WXNIA.js → mcp-7GVOYYX4.js} +4 -4
  107. package/dist/mcp.js +9 -9
  108. package/dist/{measurement-plan-XZXK3YLO.js → measurement-plan-V7E7UHE2.js} +5 -5
  109. package/dist/{notify-ZGLVTSVC.js → notify-ISU7XBAW.js} +3 -3
  110. package/dist/{organic-evidence-CRTYFYAN.js → organic-evidence-FIL7YJ4Z.js} +3 -3
  111. package/dist/{overview-5URBJKI4.js → overview-NVBLGY53.js} +4 -4
  112. package/dist/{project-R5W4Y4L4.js → project-NSNMLL3Z.js} +3 -3
  113. package/dist/{query-LPGNI3ZR.js → query-TPZUUJ4X.js} +3 -3
  114. package/dist/{research-GTVKPIO2.js → research-WILAIDCK.js} +3 -3
  115. package/dist/{results-export-UNQQHELR.js → results-export-XP7XUKI2.js} +3 -3
  116. package/dist/{run-DLAFXVF4.js → run-L646IKUB.js} +4 -4
  117. package/dist/{schedule-7I5VIJFS.js → schedule-I2LW344O.js} +3 -3
  118. package/dist/{search-MUS7FSOJ.js → search-7EBWXQ6X.js} +3 -3
  119. package/dist/{serve-5TV26VAH.js → serve-LNBKTV5N.js} +26 -26
  120. package/dist/{session-FWALDL3F.js → session-U2OPQSJE.js} +13 -13
  121. package/dist/{settings-EB433IDI.js → settings-IGJ6ZA7C.js} +3 -3
  122. package/dist/{sitemaps-5EVGGGQ5.js → sitemaps-B4G4PXIE.js} +3 -3
  123. package/dist/{skills-2HS4MYNT.js → skills-WQXXY2HN.js} +2 -2
  124. package/dist/{snapshot-4DMW7AK2.js → snapshot-2TD4WIEO.js} +3 -3
  125. package/dist/{src-OYMKXD6L.js → src-DI4RNYAG.js} +2 -2
  126. package/dist/{status-2CRLY5B6.js → status-SKGGPZFE.js} +4 -4
  127. package/dist/{technical-aeo-HIQJCEML.js → technical-aeo-AFAXF5M5.js} +3 -3
  128. package/dist/{telemetry-H3SGPKZC.js → telemetry-ZRRX7I75.js} +4 -4
  129. package/dist/{tools-PHYD6WYF.js → tools-RY7ZXQJ5.js} +11 -11
  130. package/dist/{traffic-47L2EV6Q.js → traffic-LA3FZKID.js} +9 -9
  131. package/dist/{trends-CCWE7MF6.js → trends-GUUBTBCO.js} +3 -3
  132. package/dist/{users-BGIGSKB2.js → users-W44TRUSL.js} +3 -3
  133. package/dist/{visibility-stats-XVF64BMX.js → visibility-stats-KMKV4TF5.js} +3 -3
  134. package/dist/{wordpress-DQ6DAQPS.js → wordpress-FNLXVJDO.js} +3 -3
  135. package/package.json +1 -1
  136. package/assets/assets/ProjectPage-BqS0rfDT.js +0 -11
  137. package/assets/assets/ToneBadge-CTZ_JZLD.js +0 -60
  138. package/dist/intelligence-service-GSQ7YAYU.js +0 -16
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  pickProjectCitedDomain
3
- } from "./chunk-YTRXALSU.js";
3
+ } from "./chunk-3FHPUOMW.js";
4
4
  import {
5
5
  createLogger
6
- } from "./chunk-T3FYWUQL.js";
6
+ } from "./chunk-IO4BZLM7.js";
7
7
  import {
8
8
  GBP_INSIGHT_PROVIDER,
9
9
  PERSISTENT_GAP_THRESHOLD,
@@ -17,7 +17,7 @@ import {
17
17
  mergeGscQueryTotalsWithFallback,
18
18
  readGscQueryDailyRows,
19
19
  readLatestGscDataDate
20
- } from "./chunk-7PUUMJXY.js";
20
+ } from "./chunk-PSCXQGJE.js";
21
21
  import {
22
22
  competitors,
23
23
  gbpDailyMetrics,
@@ -35,14 +35,14 @@ import {
35
35
  queries,
36
36
  querySnapshots,
37
37
  runs
38
- } from "./chunk-J3NS6NGM.js";
38
+ } from "./chunk-JGLX5WU6.js";
39
39
  import {
40
40
  CitationStates,
41
41
  RunKinds,
42
42
  RunStatuses,
43
43
  RunTriggers,
44
44
  effectiveDomains
45
- } from "./chunk-CVCUADCM.js";
45
+ } from "./chunk-W4ZRFNOX.js";
46
46
 
47
47
  // src/intelligence-service.ts
48
48
  import { eq, desc, asc, and, ne, or, inArray, gte, lte, isNull, sql, exists } from "drizzle-orm";
@@ -15,7 +15,7 @@ import {
15
15
  hostMatchesDomain,
16
16
  hostOf,
17
17
  isLocationRedirectStatus
18
- } from "./chunk-CVCUADCM.js";
18
+ } from "./chunk-W4ZRFNOX.js";
19
19
 
20
20
  // ../integration-traffic/src/agent-trust.ts
21
21
  var AGENT_TRUST_RULES_VERSION = 2;
@@ -5,7 +5,7 @@ import {
5
5
  siteCrawlGraphNodes,
6
6
  siteCrawlPages,
7
7
  siteCrawlSnapshots
8
- } from "./chunk-J3NS6NGM.js";
8
+ } from "./chunk-JGLX5WU6.js";
9
9
  import {
10
10
  SITE_CRAWL_GRAPH_MAX_EDGES,
11
11
  SITE_CRAWL_GRAPH_MAX_NODES,
@@ -15,7 +15,7 @@ import {
15
15
  factorStatusFromDistribution,
16
16
  factorStatusFromScore,
17
17
  roundPreservingTotal
18
- } from "./chunk-CVCUADCM.js";
18
+ } from "./chunk-W4ZRFNOX.js";
19
19
 
20
20
  // src/site-audit-factors.ts
21
21
  function recordedSharePct(factor) {
@@ -17,14 +17,14 @@ import {
17
17
  normalizeTrafficPathPattern,
18
18
  sessionizeJourneyRequests,
19
19
  verifyRequestSignature
20
- } from "./chunk-MKLAX5KY.js";
20
+ } from "./chunk-OCXDY2DG.js";
21
21
  import {
22
22
  currentOutcomeAttribution,
23
23
  getRequestContext,
24
24
  registerRequestContext,
25
25
  requestAuditIdentity,
26
26
  sanitizeRequestContext
27
- } from "./chunk-EWSVH5TU.js";
27
+ } from "./chunk-SDCH5EBA.js";
28
28
  import {
29
29
  RawEventSampleEventTypes,
30
30
  SentimentIdempotencyConflict,
@@ -184,7 +184,7 @@ import {
184
184
  userSessions,
185
185
  users,
186
186
  writeNegativeReviewMaxStars
187
- } from "./chunk-J3NS6NGM.js";
187
+ } from "./chunk-JGLX5WU6.js";
188
188
  import {
189
189
  ADS_ACTIVATE_SCOPE,
190
190
  ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
@@ -1240,6 +1240,7 @@ import {
1240
1240
  trafficSampleClassifierSchema,
1241
1241
  trafficSamplesQuerySchema,
1242
1242
  trafficSamplesResponseSchema,
1243
+ trafficSeriesGranularitySchema,
1243
1244
  trafficSourceDetailDtoSchema,
1244
1245
  trafficSourceDtoSchema,
1245
1246
  trafficSourceListResponseSchema,
@@ -1283,7 +1284,7 @@ import {
1283
1284
  wordpressSchemaDeployResultDtoSchema,
1284
1285
  wordpressSchemaStatusResultDtoSchema,
1285
1286
  wordpressStatusDtoSchema
1286
- } from "./chunk-CVCUADCM.js";
1287
+ } from "./chunk-W4ZRFNOX.js";
1287
1288
 
1288
1289
  // ../api-routes/src/user-session.ts
1289
1290
  import crypto2 from "crypto";
@@ -31585,17 +31586,7 @@ function emptyAiCounts() {
31585
31586
  }
31586
31587
  var AI_ATTRIBUTION_PREFERRED_DIMENSION = "session";
31587
31588
  function pickWinningAttributionDimension(sessionsByDimension) {
31588
- let winner;
31589
- let winnerSessions = -1;
31590
- for (const [dimension, sessions] of sessionsByDimension) {
31591
- const beatsOnCount = sessions > winnerSessions;
31592
- const winsTie = sessions === winnerSessions && dimension === AI_ATTRIBUTION_PREFERRED_DIMENSION;
31593
- if (beatsOnCount || winsTie) {
31594
- winnerSessions = sessions;
31595
- winner = dimension;
31596
- }
31597
- }
31598
- return winner;
31589
+ return sessionsByDimension.has(AI_ATTRIBUTION_PREFERRED_DIMENSION) ? AI_ATTRIBUTION_PREFERRED_DIMENSION : void 0;
31599
31590
  }
31600
31591
  function resolveWinningDimensions(rows) {
31601
31592
  const groups = /* @__PURE__ */ new Map();
@@ -44010,7 +44001,7 @@ var routeCatalog = [
44010
44001
  { name: "kind", in: "query", description: 'Filter to "crawler", "ai-user-fetch", "ai-referral", a comma-separated list of them (e.g. "ai-user-fetch,ai-referral"), or "all" (default). Rows, totals and series cover only the kinds asked for.', schema: stringSchema },
44011
44002
  { name: "limit", in: "query", description: "Max newest combined detail rows in the events array (default 500, max 5000).", schema: stringSchema },
44012
44003
  { name: "sourceId", in: "query", description: "Restrict to a single traffic source.", schema: stringSchema },
44013
- { name: "granularity", in: "query", description: 'Full-window series bucket size: "hour" (default) or "day".', schema: { type: "string", enum: ["hour", "day"] } }
44004
+ { name: "granularity", in: "query", description: 'Full-window series bucket size: "hour" (default), "day" or "month" (UTC calendar months).', schema: { type: "string", enum: ["hour", "day", "month"] } }
44014
44005
  ],
44015
44006
  responses: {
44016
44007
  200: jsonResponse("Events returned with windowed totals.", "TrafficEventsResponse"),
@@ -60049,9 +60040,10 @@ function withSessionShares(rows) {
60049
60040
  }
60050
60041
  var DAY_MS4 = 864e5;
60051
60042
  var GA_TREND_FILLING_DAYS = 2;
60043
+ var gaFillingFrom = (now) => new Date(Date.parse(now.toISOString().slice(0, 10)) - (GA_TREND_FILLING_DAYS - 1) * DAY_MS4).toISOString().slice(0, 10);
60052
60044
  function aiReferralDailyTrend(days, measuredDates, firstAiDate, now) {
60053
60045
  if (!firstAiDate) return null;
60054
- const fillingFrom = new Date(Date.parse(now.toISOString().slice(0, 10)) - (GA_TREND_FILLING_DAYS - 1) * DAY_MS4).toISOString().slice(0, 10);
60046
+ const fillingFrom = gaFillingFrom(now);
60055
60047
  const usable = [...new Set(measuredDates)].filter((date) => date >= firstAiDate && date < fillingFrom).sort();
60056
60048
  if (usable.length < 2) return null;
60057
60049
  const sessionsByDate = new Map(days.map((day) => [day.date, day.sessions]));
@@ -60062,6 +60054,49 @@ function aiReferralDailyTrend(days, measuredDates, firstAiDate, now) {
60062
60054
  const fit = linearTrend(axis.map((date) => measured.has(date) ? sessionsByDate.get(date) ?? 0 : null));
60063
60055
  return fit ? { ...fit, startDate: axis[fit.startIndex], endDate: axis[fit.endIndex] } : null;
60064
60056
  }
60057
+ var addMonths = (month, n) => {
60058
+ const [y, m] = month.split("-").map(Number);
60059
+ return new Date(Date.UTC(y, m - 1 + n, 1)).toISOString().slice(0, 7);
60060
+ };
60061
+ var lastDayOf = (month) => new Date(Date.parse(`${addMonths(month, 1)}-01T00:00:00.000Z`) - DAY_MS4).toISOString().slice(0, 10);
60062
+ function aiReferralMonthlySeries(input) {
60063
+ const today = input.now.toISOString().slice(0, 10);
60064
+ const fillingFrom = gaFillingFrom(input.now);
60065
+ const firstDay = input.range.startDate ?? [...input.days.map((day) => day.date), ...input.measuredDates].sort()[0];
60066
+ if (!firstDay) return { months: [], monthlyTrend: null };
60067
+ const lastDay = input.range.endDate && input.range.endDate < today ? input.range.endDate : today;
60068
+ const labels = new Map(input.byEngine.series.map((s) => [s.key, s.label]));
60069
+ const measured = new Set(input.measuredDates);
60070
+ const months = [];
60071
+ for (let month = firstDay.slice(0, 7); month <= lastDay.slice(0, 7); month = addMonths(month, 1)) {
60072
+ const inMonth = (date) => date.startsWith(month);
60073
+ const monthStart = `${month}-01`;
60074
+ const monthEnd = lastDayOf(month);
60075
+ const monthDates = calendarDateRange(monthStart, monthEnd);
60076
+ const days = input.days.filter((day) => inMonth(day.date));
60077
+ const reported = days.length > 0 || monthDates.some((date) => measured.has(date));
60078
+ const engines = /* @__PURE__ */ new Map();
60079
+ for (const day of input.byEngine.days) {
60080
+ if (!inMonth(day.date)) continue;
60081
+ for (const [key, value] of Object.entries(day.values)) engines.set(key, (engines.get(key) ?? 0) + value);
60082
+ }
60083
+ months.push({
60084
+ month,
60085
+ sessions: reported ? days.reduce((sum, day) => sum + day.sessions, 0) : null,
60086
+ complete: reported && monthStart >= firstDay && monthEnd <= lastDay && monthEnd < fillingFrom && monthDates.every((date) => measured.has(date)),
60087
+ engines: [...engines].filter(([, v]) => v > 0).sort((a, b) => b[1] - a[1]).map(([key, sessions]) => ({ key, label: labels.get(key) ?? key, sessions }))
60088
+ });
60089
+ }
60090
+ const fromMonth = input.firstAiDate?.slice(0, 7);
60091
+ const fit = fromMonth ? linearTrend(months.map((m) => m.complete && m.month >= fromMonth ? m.sessions : null)) : null;
60092
+ const monthlyTrend = fit ? {
60093
+ ...fit,
60094
+ startMonth: months[fit.startIndex].month,
60095
+ endMonth: months[fit.endIndex].month,
60096
+ changePct: fit.start > 0 ? deltaPercent(fit.end, fit.start) : null
60097
+ } : null;
60098
+ return { months, monthlyTrend };
60099
+ }
60065
60100
  function dateRangeConditions(column, range) {
60066
60101
  const conditions = [];
60067
60102
  if (range.startDate) conditions.push(sql37`${column} >= ${range.startDate}`);
@@ -61207,9 +61242,8 @@ async function ga4Routes(app, opts) {
61207
61242
  // every date in the window) counted one visitor once per row. Withdrawn
61208
61243
  // in 4.135.0; see `ga-ai-referral-aggregation.ts` for why no correct
61209
61244
  // figure can be computed or fetched.
61210
- // Shares of the rows' own sum, not of `aiSessionsDeduped`: that total
61211
- // keeps the winning lens per day, these rows one lens per source over the
61212
- // window, so it can exceed what the rows add up to.
61245
+ // Shares of the rows' own sum, which agrees with `aiSessionsDeduped`:
61246
+ // both count only the session lens across the same window.
61213
61247
  aiReferrals: aiReferralDtos,
61214
61248
  aiEngines: groupAiEngines(aiReferralDtos),
61215
61249
  aiReferralLandingPages: aiReferralLandingPages.map((r) => ({
@@ -61319,7 +61353,12 @@ async function ga4Routes(app, opts) {
61319
61353
  const series = buildAiReferralDailySeries(rows);
61320
61354
  const measuredDates = app.db.selectDistinct({ date: gaDailyTotals.date }).from(gaDailyTotals).where(and74(eq86(gaDailyTotals.projectId, project.id), ...dateRangeConditions(gaDailyTotals.date, range))).all().map((row) => row.date);
61321
61355
  const firstAiDate = app.db.select({ date: sql37`MIN(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq86(gaAiReferrals.projectId, project.id)).get()?.date ?? null;
61322
- return { ...series, trend: aiReferralDailyTrend(series.days, measuredDates, firstAiDate, /* @__PURE__ */ new Date()) };
61356
+ const now = /* @__PURE__ */ new Date();
61357
+ return {
61358
+ ...series,
61359
+ trend: aiReferralDailyTrend(series.days, measuredDates, firstAiDate, now),
61360
+ ...aiReferralMonthlySeries({ days: series.days, byEngine: series.byEngine, measuredDates, range, firstAiDate, now })
61361
+ };
61323
61362
  });
61324
61363
  app.get("/projects/:name/ga/social-referral-history", async (request, _reply) => {
61325
61364
  const project = resolveProject(app.db, request.params.name);
@@ -65733,22 +65772,39 @@ function assertCloudflareIngestUrlOutsideWorkerRoute(ingestUrl, workerRouteHost)
65733
65772
  function emptyTrafficSeriesPoint(bucket) {
65734
65773
  return { bucket, crawlerHits: 0, aiUserFetchHits: 0, aiReferralHits: 0, aiReferralLandedHits: 0, crawlerContentHits: 0, measured: true };
65735
65774
  }
65736
- function completeTrafficSeries(windowStart, windowEnd, granularity, pointsByBucket) {
65737
- const cursor = new Date(windowStart);
65738
- const last = new Date(windowEnd);
65739
- if (granularity === TrafficSeriesGranularities.day) {
65740
- cursor.setUTCHours(0, 0, 0, 0);
65741
- last.setUTCHours(0, 0, 0, 0);
65742
- } else {
65743
- cursor.setUTCMinutes(0, 0, 0);
65744
- last.setUTCMinutes(0, 0, 0);
65775
+ var SERIES_BUCKET_LENGTH = {
65776
+ [TrafficSeriesGranularities.hour]: null,
65777
+ [TrafficSeriesGranularities.day]: 10,
65778
+ [TrafficSeriesGranularities.month]: 7
65779
+ };
65780
+ function seriesBucketSql(tsHour, granularity) {
65781
+ const length = SERIES_BUCKET_LENGTH[granularity];
65782
+ return length === null ? tsHour : sql40`substr(${tsHour}, 1, ${sql40.raw(String(length))})`;
65783
+ }
65784
+ function seriesBucketOf(iso2, granularity) {
65785
+ const length = SERIES_BUCKET_LENGTH[granularity];
65786
+ return length === null ? iso2.slice(0, 13) : iso2.slice(0, length);
65787
+ }
65788
+ function seriesBucketStartMs(bucket, granularity) {
65789
+ if (granularity === TrafficSeriesGranularities.month) return Date.parse(`${bucket}-01T00:00:00.000Z`);
65790
+ if (granularity === TrafficSeriesGranularities.day) return Date.parse(`${bucket}T00:00:00.000Z`);
65791
+ return Date.parse(bucket.length === 13 ? `${bucket}:00:00.000Z` : bucket);
65792
+ }
65793
+ function nextSeriesBucketStartMs(startMs, granularity) {
65794
+ if (granularity === TrafficSeriesGranularities.month) {
65795
+ const start = new Date(startMs);
65796
+ return Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + 1, 1);
65745
65797
  }
65798
+ return startMs + (granularity === TrafficSeriesGranularities.day ? 864e5 : 36e5);
65799
+ }
65800
+ function completeTrafficSeries(windowStart, windowEnd, granularity, pointsByBucket) {
65801
+ const firstBucket = seriesBucketOf(windowStart.toISOString(), granularity);
65802
+ const lastBucketStartMs = seriesBucketStartMs(seriesBucketOf(windowEnd.toISOString(), granularity), granularity);
65746
65803
  const points = [];
65747
- while (cursor <= last) {
65748
- const bucket = granularity === TrafficSeriesGranularities.day ? cursor.toISOString().slice(0, 10) : cursor.toISOString();
65804
+ for (let ms = seriesBucketStartMs(firstBucket, granularity); ms <= lastBucketStartMs; ms = nextSeriesBucketStartMs(ms, granularity)) {
65805
+ const iso2 = new Date(ms).toISOString();
65806
+ const bucket = granularity === TrafficSeriesGranularities.hour ? iso2 : seriesBucketOf(iso2, granularity);
65749
65807
  points.push(pointsByBucket.get(bucket) ?? emptyTrafficSeriesPoint(bucket));
65750
- if (granularity === TrafficSeriesGranularities.day) cursor.setUTCDate(cursor.getUTCDate() + 1);
65751
- else cursor.setUTCHours(cursor.getUTCHours() + 1);
65752
65808
  }
65753
65809
  return points;
65754
65810
  }
@@ -68794,11 +68850,12 @@ async function trafficRoutes(app, opts) {
68794
68850
  const granularityParam = request.query?.granularity;
68795
68851
  let granularity = TrafficSeriesGranularities.hour;
68796
68852
  if (granularityParam !== void 0) {
68797
- if (granularityParam === TrafficSeriesGranularities.hour || granularityParam === TrafficSeriesGranularities.day) {
68798
- granularity = granularityParam;
68853
+ const parsed = trafficSeriesGranularitySchema.safeParse(granularityParam);
68854
+ if (parsed.success) {
68855
+ granularity = parsed.data;
68799
68856
  } else {
68800
68857
  throw validationError(
68801
- `"granularity" must be one of: ${TrafficSeriesGranularities.hour}, ${TrafficSeriesGranularities.day}`
68858
+ `"granularity" must be one of: ${trafficSeriesGranularitySchema.options.join(", ")}`
68802
68859
  );
68803
68860
  }
68804
68861
  }
@@ -68833,7 +68890,7 @@ async function trafficRoutes(app, opts) {
68833
68890
  );
68834
68891
  crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
68835
68892
  totalEventRows += pathTotals.reduce((sum, row) => sum + Number(row.rows), 0);
68836
- const crawlerSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql40`substr(${crawlerEventsHourly.tsHour}, 1, 10)` : crawlerEventsHourly.tsHour;
68893
+ const crawlerSeriesBucket = seriesBucketSql(crawlerEventsHourly.tsHour, granularity);
68837
68894
  const distinctPaths = pathTotals.map((r) => ({ pathNormalized: r.pathNormalized }));
68838
68895
  const contentPaths = [];
68839
68896
  const infraPaths = [];
@@ -68904,7 +68961,7 @@ async function trafficRoutes(app, opts) {
68904
68961
  }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
68905
68962
  aiUserFetchTotal = Number(total?.total ?? 0);
68906
68963
  totalEventRows += Number(total?.rows ?? 0);
68907
- const userFetchSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql40`substr(${aiUserFetchEventsHourly.tsHour}, 1, 10)` : aiUserFetchEventsHourly.tsHour;
68964
+ const userFetchSeriesBucket = seriesBucketSql(aiUserFetchEventsHourly.tsHour, granularity);
68908
68965
  const userFetchSeries = app.db.select({
68909
68966
  bucket: userFetchSeriesBucket,
68910
68967
  hits: sql40`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
@@ -68954,7 +69011,7 @@ async function trafficRoutes(app, opts) {
68954
69011
  Number(total?.organic ?? 0)
68955
69012
  );
68956
69013
  totalEventRows += Number(total?.rows ?? 0);
68957
- const referralSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql40`substr(${aiReferralEventsHourly.tsHour}, 1, 10)` : aiReferralEventsHourly.tsHour;
69014
+ const referralSeriesBucket = seriesBucketSql(aiReferralEventsHourly.tsHour, granularity);
68958
69015
  const referralSeries = app.db.select({
68959
69016
  bucket: referralSeriesBucket,
68960
69017
  hits: sql40`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
@@ -69020,32 +69077,34 @@ async function trafficRoutes(app, opts) {
69020
69077
  const referralCountingChangedAt = aiReferralOlderCountUntil(app.db, { projectId: project.id, sourceId: sourceIdParam });
69021
69078
  const referralCountingChangedMs = referralCountingChangedAt === null ? null : Date.parse(referralCountingChangedAt);
69022
69079
  const coverageStart = firsts.length ? firsts.slice().sort()[0] : null;
69023
- const bucketStartMs = (bucket) => Date.parse(
69024
- granularity === TrafficSeriesGranularities.day ? `${bucket}T00:00:00.000Z` : bucket
69025
- );
69026
- const bucketMs = granularity === TrafficSeriesGranularities.day ? 864e5 : 36e5;
69080
+ const bucketStartMs = (bucket) => seriesBucketStartMs(bucket, granularity);
69027
69081
  const lastBucket = points.length ? points[points.length - 1].bucket : null;
69028
69082
  const lastBucketStartMs = lastBucket === null ? null : bucketStartMs(lastBucket);
69029
- const trailingBucketIsPartial = lastBucketStartMs !== null && Number.isFinite(lastBucketStartMs) && until.getTime() < lastBucketStartMs + bucketMs;
69030
- const coverageKey = coverageStart === null ? null : granularity === TrafficSeriesGranularities.day ? coverageStart.slice(0, 10) : coverageStart.slice(0, 13);
69083
+ const trailingBucketIsPartial = lastBucketStartMs !== null && Number.isFinite(lastBucketStartMs) && until.getTime() < nextSeriesBucketStartMs(lastBucketStartMs, granularity);
69084
+ const coverageKey = coverageStart === null ? null : seriesBucketOf(coverageStart, granularity);
69031
69085
  for (const pt of points) {
69032
- pt.measured = coverageKey === null ? false : (granularity === TrafficSeriesGranularities.day ? pt.bucket.slice(0, 10) : pt.bucket.slice(0, 13)) >= coverageKey;
69086
+ pt.measured = coverageKey === null ? false : seriesBucketOf(pt.bucket, granularity) >= coverageKey;
69033
69087
  }
69034
69088
  const firstMeasuredIdx = points.findIndex((pt) => pt.measured);
69035
69089
  const coverageStartMs = coverageStart === null ? null : Date.parse(coverageStart);
69036
- const leadingBucketIsPartial = firstMeasuredIdx >= 0 && coverageStartMs !== null && Number.isFinite(coverageStartMs) && coverageStartMs > bucketStartMs(points[firstMeasuredIdx].bucket);
69090
+ const leadingBucketIsPartial = firstMeasuredIdx >= 0 && coverageStartMs !== null && Number.isFinite(coverageStartMs) && Math.max(coverageStartMs, since.getTime()) > bucketStartMs(points[firstMeasuredIdx].bucket);
69037
69091
  return {
69038
69092
  granularity,
69039
69093
  points,
69040
69094
  coverageStart,
69041
69095
  aiReferralCountingChangedAt: referralCountingChangedAt,
69096
+ // The same two edges the fits leave out, named for a chart to draw as partial.
69097
+ partialBuckets: [
69098
+ ...firstMeasuredIdx >= 0 && leadingBucketIsPartial ? [points[firstMeasuredIdx].bucket] : [],
69099
+ ...trailingBucketIsPartial && lastBucket !== null && !(points.length - 1 === firstMeasuredIdx && leadingBucketIsPartial) ? [lastBucket] : []
69100
+ ],
69042
69101
  // Two edges are excluded from the fit, both for the same reason: a
69043
69102
  // bucket that is not a full reading must not be treated as one.
69044
69103
  //
69045
69104
  // Leading: unmeasured buckets predate recording, so they are the
69046
69105
  // ABSENCE of a reading, not a reading of zero. The first MEASURED
69047
- // bucket goes too when recording began inside it, since a fraction of
69048
- // a bucket read as a whole one slopes the fit up out of nothing.
69106
+ // bucket goes too when recording or the range began inside it, since
69107
+ // a fraction of a bucket read as a whole slopes the fit up out of nothing.
69049
69108
  //
69050
69109
  // Trailing: `until` defaults to now, so the newest bucket holds only
69051
69110
  // the elapsed fraction of the current period. A site flat at 500/day
@@ -15,7 +15,7 @@ import {
15
15
  formatCanonryPluginUpdateHint,
16
16
  formatCanonryPluginVersionMismatch,
17
17
  skillsClientSchema
18
- } from "./chunk-CVCUADCM.js";
18
+ } from "./chunk-W4ZRFNOX.js";
19
19
 
20
20
  // src/commands/skills.ts
21
21
  import crypto from "crypto";
@@ -2,18 +2,18 @@ import {
2
2
  configExists,
3
3
  loadConfigRaw,
4
4
  saveConfigPatch
5
- } from "./chunk-5TPLZD4I.js";
5
+ } from "./chunk-ZZVRDHSK.js";
6
6
  import {
7
7
  BUNDLED_SKILL_NAMES,
8
8
  installSkills
9
- } from "./chunk-NJG6WFRK.js";
9
+ } from "./chunk-QBIIQF3J.js";
10
10
  import {
11
11
  PACKAGE_VERSION
12
12
  } from "./chunk-AOJU5LKX.js";
13
13
  import {
14
14
  SKILL_MANIFEST_FILENAME,
15
15
  SkillsClients
16
- } from "./chunk-CVCUADCM.js";
16
+ } from "./chunk-W4ZRFNOX.js";
17
17
 
18
18
  // src/skills-autosync.ts
19
19
  import fs from "fs";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  USAGE_TELEMETRY_HEADERS,
3
3
  redactLogString
4
- } from "./chunk-CVCUADCM.js";
4
+ } from "./chunk-W4ZRFNOX.js";
5
5
 
6
6
  // ../api-routes/src/request-context.ts
7
7
  import { AsyncLocalStorage } from "async_hooks";