@ainyc/canonry 4.139.1 → 4.142.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 (30) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +33 -5
  2. package/assets/agent-workspace/skills/canonry/references/indexing.md +2 -1
  3. package/assets/assets/{AuditHistoryPanel-FiMbN4_a.js → AuditHistoryPanel-CfaF5B_e.js} +1 -1
  4. package/assets/assets/{BacklinksPage-CLRIsKHN.js → BacklinksPage-BIytWBpL.js} +1 -1
  5. package/assets/assets/{ChartPrimitives-BuTqG5NR.js → ChartPrimitives-0tQT6GE7.js} +1 -1
  6. package/assets/assets/{HistoryPage-BWRbsFAT.js → HistoryPage-Bn6IH8WV.js} +1 -1
  7. package/assets/assets/{ProjectPage-DQ2kIj-L.js → ProjectPage-DsQic9wX.js} +8 -8
  8. package/assets/assets/{RunRow-BZobaeDq.js → RunRow-RHd8Bd52.js} +1 -1
  9. package/assets/assets/{RunsPage-DhrY4Ouc.js → RunsPage-BsDqqQFX.js} +1 -1
  10. package/assets/assets/{SettingsPage-C6VcJEgB.js → SettingsPage-CX2LkFDB.js} +1 -1
  11. package/assets/assets/{TrafficPage-KqYl5HUH.js → TrafficPage-I19b7i9m.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-mMJi-QZr.js → TrafficSourceDetailPage-DOLEAPsc.js} +1 -1
  13. package/assets/assets/{arrow-left-CpLow2Kp.js → arrow-left-BG9o86__.js} +1 -1
  14. package/assets/assets/{extract-error-message-dHq11Y4U.js → extract-error-message-D0cgM2XG.js} +1 -1
  15. package/assets/assets/index-BDo9nl4O.css +1 -0
  16. package/assets/assets/index-CYGBt2f0.js +213 -0
  17. package/assets/assets/{trash-2-CnEPNBqu.js → trash-2-C_THWxO_.js} +1 -1
  18. package/assets/index.html +2 -2
  19. package/dist/{chunk-IYTHIAAE.js → chunk-D7NJUGNQ.js} +55 -10
  20. package/dist/{chunk-OA2EGCUS.js → chunk-LS6SEZVF.js} +120 -4
  21. package/dist/{chunk-DJHSOPMU.js → chunk-NR6OGEV3.js} +326 -67
  22. package/dist/{chunk-YLHZQ7HQ.js → chunk-UFPP5CSN.js} +16 -5
  23. package/dist/cli.js +203 -63
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.js +4 -4
  26. package/dist/{intelligence-service-BGH2MGHB.js → intelligence-service-EOUP37XN.js} +2 -2
  27. package/dist/mcp.js +2 -2
  28. package/package.json +8 -8
  29. package/assets/assets/index-CntuM6Th.css +0 -1
  30. package/assets/assets/index-iRW7X23W.js +0 -213
@@ -1 +1 @@
1
- import{c as a}from"./index-iRW7X23W.js";const t=[["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"}]],h=a("circle-question-mark",t);const e=[["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"}]],d=a("download",e);const o=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],p=a("external-link",o);const c=[["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"}]],s=a("trash-2",c);export{h as C,d as D,p as E,s as T};
1
+ import{c as a}from"./index-CYGBt2f0.js";const t=[["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"}]],h=a("circle-question-mark",t);const e=[["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"}]],d=a("download",e);const o=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],p=a("external-link",o);const c=[["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"}]],s=a("trash-2",c);export{h as C,d as D,p as E,s as T};
package/assets/index.html CHANGED
@@ -12,12 +12,12 @@
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-iRW7X23W.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-CYGBt2f0.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">
19
19
  <link rel="modulepreload" crossorigin href="./assets/vendor-markdown-6dwjPOKK.js">
20
- <link rel="stylesheet" crossorigin href="./assets/index-CntuM6Th.css">
20
+ <link rel="stylesheet" crossorigin href="./assets/index-BDo9nl4O.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
@@ -23,6 +23,7 @@ import {
23
23
  discoveryRunRequestSchema,
24
24
  gaMeasurementAnalysisWindowSchema,
25
25
  gaMeasurementHostScopeSchema,
26
+ gscPerformanceOrderBySchema,
26
27
  keywordBatchRequestSchema,
27
28
  keywordGenerateRequestSchema,
28
29
  notificationCreateRequestSchema,
@@ -42,7 +43,7 @@ import {
42
43
  trafficConnectWordpressRequestSchema,
43
44
  trafficEventKindSchema,
44
45
  trafficSeriesGranularitySchema
45
- } from "./chunk-OA2EGCUS.js";
46
+ } from "./chunk-LS6SEZVF.js";
46
47
 
47
48
  // src/config.ts
48
49
  import fs from "fs";
@@ -2067,6 +2068,18 @@ var getApiV1ProjectsByNameGoogleGscPerformanceDaily = (options) => {
2067
2068
  ...options
2068
2069
  });
2069
2070
  };
2071
+ var getApiV1ProjectsByNameGoogleGscTopPages = (options) => {
2072
+ return (options.client ?? client).get({
2073
+ security: [
2074
+ {
2075
+ scheme: "bearer",
2076
+ type: "http"
2077
+ }
2078
+ ],
2079
+ url: "/api/v1/projects/{name}/google/gsc/top-pages",
2080
+ ...options
2081
+ });
2082
+ };
2070
2083
  var postApiV1ProjectsByNameGoogleGscInspect = (options) => {
2071
2084
  return (options.client ?? client).post({
2072
2085
  security: [
@@ -5139,6 +5152,15 @@ var ApiClient = class {
5139
5152
  })
5140
5153
  );
5141
5154
  }
5155
+ async gscTopPages(project, params) {
5156
+ return this.invoke(
5157
+ () => getApiV1ProjectsByNameGoogleGscTopPages({
5158
+ client: this.heyClient,
5159
+ path: { name: project },
5160
+ query: params
5161
+ })
5162
+ );
5163
+ }
5142
5164
  async gscInspect(project, url) {
5143
5165
  return this.invoke(
5144
5166
  () => postApiV1ProjectsByNameGoogleGscInspect({
@@ -6140,7 +6162,9 @@ var gscPerformanceInputSchema = z2.object({
6140
6162
  endDate: z2.string().optional(),
6141
6163
  query: z2.string().optional(),
6142
6164
  page: z2.string().optional(),
6143
- limit: z2.number().int().positive().max(500).optional(),
6165
+ limit: z2.number().int().positive().max(5e3).optional(),
6166
+ offset: z2.number().int().nonnegative().optional(),
6167
+ orderBy: gscPerformanceOrderBySchema.optional(),
6144
6168
  window: analyticsWindowSchema.optional()
6145
6169
  });
6146
6170
  var gscPerformanceDailyInputSchema = z2.object({
@@ -6149,6 +6173,13 @@ var gscPerformanceDailyInputSchema = z2.object({
6149
6173
  endDate: z2.string().optional(),
6150
6174
  window: analyticsWindowSchema.optional()
6151
6175
  });
6176
+ var gscTopPagesInputSchema = z2.object({
6177
+ project: projectNameSchema,
6178
+ startDate: z2.string().optional(),
6179
+ endDate: z2.string().optional(),
6180
+ limit: z2.number().int().positive().max(500).optional(),
6181
+ window: analyticsWindowSchema.optional()
6182
+ });
6152
6183
  var gscInspectionsInputSchema = z2.object({
6153
6184
  project: projectNameSchema,
6154
6185
  url: z2.string().optional(),
@@ -6246,8 +6277,11 @@ async function submitGscSitemapsFromMcp(client2, input) {
6246
6277
  }
6247
6278
  var gaWindowInputSchema = z2.object({
6248
6279
  project: projectNameSchema,
6249
- window: analyticsWindowSchema.optional()
6280
+ window: analyticsWindowSchema.optional(),
6281
+ startDate: z2.string().optional(),
6282
+ endDate: z2.string().optional()
6250
6283
  });
6284
+ var GA_RANGE_PARAMS = ["window", "startDate", "endDate"];
6251
6285
  var gaTrafficInputSchema = gaWindowInputSchema.extend({
6252
6286
  limit: z2.number().int().positive().max(500).optional()
6253
6287
  });
@@ -7074,13 +7108,13 @@ var canonryMcpTools = [
7074
7108
  defineTool({
7075
7109
  name: "canonry_gsc_performance",
7076
7110
  title: "Get GSC performance",
7077
- description: "Get stored Google Search Console performance rows for a Canonry project.",
7111
+ description: "Get stored Google Search Console performance rows for a Canonry project. Rows are ordered by clicks descending unless orderBy says otherwise, and the response reports totalMatching / truncated / latestAvailableDate. Never sum these rows for a property total, use canonry_gsc_performance_daily.",
7078
7112
  access: "read",
7079
7113
  tier: "gsc",
7080
7114
  inputSchema: gscPerformanceInputSchema,
7081
7115
  annotations: readAnnotations(),
7082
7116
  openApiOperations: ["GET /api/v1/projects/{name}/google/gsc/performance"],
7083
- handler: (client2, input) => client2.gscPerformance(input.project, compactStringParams(input, ["startDate", "endDate", "query", "page", "limit", "window"]))
7117
+ handler: (client2, input) => client2.gscPerformance(input.project, compactStringParams(input, ["startDate", "endDate", "query", "page", "limit", "offset", "orderBy", "window"]))
7084
7118
  }),
7085
7119
  defineTool({
7086
7120
  name: "canonry_gsc_performance_daily",
@@ -7093,6 +7127,17 @@ var canonryMcpTools = [
7093
7127
  openApiOperations: ["GET /api/v1/projects/{name}/google/gsc/performance/daily"],
7094
7128
  handler: (client2, input) => client2.gscPerformanceDaily(input.project, compactStringParams(input, ["startDate", "endDate", "window"]))
7095
7129
  }),
7130
+ defineTool({
7131
+ name: "canonry_gsc_top_pages",
7132
+ title: "Get top GSC pages",
7133
+ description: 'Get the project\'s pages ranked by summed GSC clicks, aggregated in SQL. The rows are a RANKING and their clicks/impressions do NOT add up to the site total: Google withholds rare queries and repeats one impression per ranking page. Read the window total from the returned `totals` block (labelled totalsSource "property-daily"), which is null when no property-level figure covers the window. Never sum the rows.',
7134
+ access: "read",
7135
+ tier: "gsc",
7136
+ inputSchema: gscTopPagesInputSchema,
7137
+ annotations: readAnnotations(),
7138
+ openApiOperations: ["GET /api/v1/projects/{name}/google/gsc/top-pages"],
7139
+ handler: (client2, input) => client2.gscTopPages(input.project, compactStringParams(input, ["startDate", "endDate", "limit", "window"]))
7140
+ }),
7096
7141
  defineTool({
7097
7142
  name: "canonry_gsc_inspections",
7098
7143
  title: "List GSC inspections",
@@ -7193,7 +7238,7 @@ var canonryMcpTools = [
7193
7238
  inputSchema: gaTrafficInputSchema,
7194
7239
  annotations: readAnnotations(),
7195
7240
  openApiOperations: ["GET /api/v1/projects/{name}/ga/traffic"],
7196
- handler: (client2, input) => client2.gaTraffic(input.project, compactStringParams(input, ["limit", "window"]))
7241
+ handler: (client2, input) => client2.gaTraffic(input.project, compactStringParams(input, ["limit", ...GA_RANGE_PARAMS]))
7197
7242
  }),
7198
7243
  defineTool({
7199
7244
  name: "canonry_ga_coverage",
@@ -7215,7 +7260,7 @@ var canonryMcpTools = [
7215
7260
  inputSchema: gaWindowInputSchema,
7216
7261
  annotations: readAnnotations(),
7217
7262
  openApiOperations: ["GET /api/v1/projects/{name}/ga/ai-referral-history"],
7218
- handler: (client2, input) => client2.gaAiReferralHistory(input.project, compactStringParams(input, ["window"]))
7263
+ handler: (client2, input) => client2.gaAiReferralHistory(input.project, compactStringParams(input, GA_RANGE_PARAMS))
7219
7264
  }),
7220
7265
  defineTool({
7221
7266
  name: "canonry_ga_ai_referral_daily",
@@ -7226,7 +7271,7 @@ var canonryMcpTools = [
7226
7271
  inputSchema: gaWindowInputSchema,
7227
7272
  annotations: readAnnotations(),
7228
7273
  openApiOperations: ["GET /api/v1/projects/{name}/ga/ai-referral-daily"],
7229
- handler: (client2, input) => client2.gaAiReferralDaily(input.project, compactStringParams(input, ["window"]))
7274
+ handler: (client2, input) => client2.gaAiReferralDaily(input.project, compactStringParams(input, GA_RANGE_PARAMS))
7230
7275
  }),
7231
7276
  defineTool({
7232
7277
  name: "canonry_ga_social_referral_history",
@@ -7237,7 +7282,7 @@ var canonryMcpTools = [
7237
7282
  inputSchema: gaWindowInputSchema,
7238
7283
  annotations: readAnnotations(),
7239
7284
  openApiOperations: ["GET /api/v1/projects/{name}/ga/social-referral-history"],
7240
- handler: (client2, input) => client2.gaSocialReferralHistory(input.project, compactStringParams(input, ["window"]))
7285
+ handler: (client2, input) => client2.gaSocialReferralHistory(input.project, compactStringParams(input, GA_RANGE_PARAMS))
7241
7286
  }),
7242
7287
  defineTool({
7243
7288
  name: "canonry_ga_social_referral_trend",
@@ -7270,7 +7315,7 @@ var canonryMcpTools = [
7270
7315
  inputSchema: gaWindowInputSchema,
7271
7316
  annotations: readAnnotations(),
7272
7317
  openApiOperations: ["GET /api/v1/projects/{name}/ga/session-history"],
7273
- handler: (client2, input) => client2.gaSessionHistory(input.project, compactStringParams(input, ["window"]))
7318
+ handler: (client2, input) => client2.gaSessionHistory(input.project, compactStringParams(input, GA_RANGE_PARAMS))
7274
7319
  }),
7275
7320
  // ----- Google Business Profile (Phase 1: auth + discovery) -----
7276
7321
  defineTool({
@@ -262,6 +262,24 @@ var analysisDemandPeriodSchema = analysisPeriodSchema.extend({
262
262
  unreportedClicks: z2.number().int().nonnegative(),
263
263
  unreportedImpressions: z2.number().int().nonnegative()
264
264
  });
265
+ var analysisEngagementPeriodSchema = analysisPeriodSchema.extend({
266
+ /** Sessions over the bucket. Additive, so this is a plain sum. */
267
+ sessions: z2.number().int().nonnegative(),
268
+ /**
269
+ * Sessions-weighted engagement rate over the days that carry a reading.
270
+ * A rate is not additive; sessions are, and GA4's engagementRate is
271
+ * engagedSessions / sessions, so the weighted mean reconstructs the bucket
272
+ * rate exactly. `null` when no day in the bucket carries a reading.
273
+ */
274
+ engagementRate: z2.number().min(0).max(1).nullable(),
275
+ dailyTotalUsers: z2.number().int().nonnegative().nullable(),
276
+ dailyNewUsers: z2.number().int().nonnegative().nullable(),
277
+ /** False when the bucket has no engagement reading at all. */
278
+ metricsAvailable: z2.boolean(),
279
+ daysInPeriod: z2.number().int().nonnegative(),
280
+ daysWithEngagementRate: z2.number().int().nonnegative(),
281
+ daysWithUserSplit: z2.number().int().nonnegative()
282
+ });
265
283
  var gaMeasurementAnalysisDtoSchema = z2.object({
266
284
  window: gaMeasurementAnalysisWindowSchema,
267
285
  bucketDays: z2.literal(30),
@@ -299,6 +317,18 @@ var gaMeasurementAnalysisDtoSchema = z2.object({
299
317
  periods: z2.array(analysisEventPeriodSchema)
300
318
  }))
301
319
  }),
320
+ engagement: z2.object({
321
+ status: z2.enum(["ready", "unavailable"]),
322
+ /**
323
+ * Earliest date the project holds ANY engagement reading for, ignoring the
324
+ * requested window. Everything before it is unmeasured, not zero — this is
325
+ * the field a client report reads to label (or truncate) the pre-migration
326
+ * span instead of drawing a flat zero line across it.
327
+ */
328
+ availableFromDate: analysisDateSchema.nullable(),
329
+ latestDate: analysisDateSchema.nullable(),
330
+ periods: z2.array(analysisEngagementPeriodSchema)
331
+ }),
302
332
  searchDemand: z2.object({
303
333
  status: z2.enum(["ready", "unavailable"]),
304
334
  periods: z2.array(analysisDemandPeriodSchema),
@@ -4129,6 +4159,10 @@ function compactDateToIso(value) {
4129
4159
  if (value.length !== 8 || !/^\d{8}$/.test(value)) return value;
4130
4160
  return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`;
4131
4161
  }
4162
+ function parseBoundedRate(value) {
4163
+ if (value === null) return null;
4164
+ return value >= 0 && value <= 1 ? value : null;
4165
+ }
4132
4166
 
4133
4167
  // ../contracts/src/model-pointers.ts
4134
4168
  var modelPointerEventKindSchema = z10.enum(["introduced", "repointed", "retired"]);
@@ -4408,6 +4442,13 @@ var gscSearchDataDtoSchema = z11.object({
4408
4442
  ctr: z11.number(),
4409
4443
  position: z11.number()
4410
4444
  });
4445
+ var gscPerformanceOrderBySchema = z11.enum(["clicks", "impressions", "date"]);
4446
+ var gscPerformanceResponseDtoSchema = z11.object({
4447
+ rows: z11.array(gscSearchDataDtoSchema),
4448
+ totalMatching: z11.number(),
4449
+ truncated: z11.boolean(),
4450
+ latestAvailableDate: z11.string().nullable()
4451
+ });
4411
4452
  var gscPerformanceDailyPointSchema = z11.object({
4412
4453
  date: z11.string(),
4413
4454
  clicks: z11.number(),
@@ -4423,6 +4464,38 @@ var gscPerformanceDailyDtoSchema = z11.object({
4423
4464
  }),
4424
4465
  daily: z11.array(gscPerformanceDailyPointSchema)
4425
4466
  });
4467
+ var gscTopPageRowSchema = z11.object({
4468
+ page: z11.string(),
4469
+ clicks: z11.number(),
4470
+ impressions: z11.number(),
4471
+ ctr: z11.number()
4472
+ });
4473
+ var gscTopPagesDtoSchema = z11.object({
4474
+ rows: z11.array(gscTopPageRowSchema),
4475
+ totals: z11.object({
4476
+ clicks: z11.number(),
4477
+ impressions: z11.number(),
4478
+ ctr: z11.number(),
4479
+ days: z11.number(),
4480
+ /** First date the property-level totals actually cover. */
4481
+ coveredFrom: z11.string().nullable(),
4482
+ /** Last date the property-level totals actually cover. */
4483
+ coveredThrough: z11.string().nullable(),
4484
+ /**
4485
+ * False when the totals span less than the ranked rows above.
4486
+ *
4487
+ * The dimensioned and property-level tables sync independently, so a
4488
+ * 30-day totals sync can sit next to months of dimensioned rows. Printing
4489
+ * both as one period would misstate the window the totals belong to.
4490
+ */
4491
+ complete: z11.boolean()
4492
+ }).nullable(),
4493
+ totalsSource: z11.literal("property-daily"),
4494
+ /** First date the ranked rows cover, so a caller can compare spans. */
4495
+ rankedFrom: z11.string().nullable(),
4496
+ /** Last date the ranked rows cover. */
4497
+ rankedThrough: z11.string().nullable()
4498
+ });
4426
4499
  var gscUrlInspectionDtoSchema = z11.object({
4427
4500
  id: z11.string(),
4428
4501
  url: z11.string(),
@@ -6574,8 +6647,14 @@ var sourceBreakdownDtoSchema = z23.object({
6574
6647
  limit: z23.number().int().nullable()
6575
6648
  });
6576
6649
  function parseWindow(value) {
6577
- if (value === "7d" || value === "30d" || value === "90d" || value === "all") return value;
6578
- return "all";
6650
+ if (value === void 0 || value === "") return "all";
6651
+ const parsed = metricsWindowSchema.safeParse(value);
6652
+ if (!parsed.success) {
6653
+ throw validationError(
6654
+ `Invalid window "${value}". Must be one of: ${metricsWindowSchema.options.join(", ")}.`
6655
+ );
6656
+ }
6657
+ return parsed.data;
6579
6658
  }
6580
6659
  function windowCutoff(window) {
6581
6660
  if (window === "all") return null;
@@ -6584,6 +6663,31 @@ function windowCutoff(window) {
6584
6663
  d.setDate(d.getDate() - days);
6585
6664
  return d.toISOString();
6586
6665
  }
6666
+ var CALENDAR_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
6667
+ function parseBoundaryDate(value, field) {
6668
+ if (value === void 0 || value === "") return null;
6669
+ const trimmed = value.trim();
6670
+ const invalid = () => validationError(`Invalid ${field} "${value}". Expected a calendar date as YYYY-MM-DD.`);
6671
+ if (!CALENDAR_DATE_PATTERN.test(trimmed)) throw invalid();
6672
+ const parsed = /* @__PURE__ */ new Date(`${trimmed}T00:00:00Z`);
6673
+ if (Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== trimmed) throw invalid();
6674
+ return trimmed;
6675
+ }
6676
+ function resolveDateRange(input) {
6677
+ const window = parseWindow(input.window);
6678
+ const startDate = parseBoundaryDate(input.startDate, "startDate");
6679
+ const endDate = parseBoundaryDate(input.endDate, "endDate");
6680
+ if (startDate && endDate && startDate > endDate) {
6681
+ throw validationError(`Invalid date range: startDate "${startDate}" must be on or before endDate "${endDate}".`);
6682
+ }
6683
+ const cutoff = startDate ? null : windowCutoff(window)?.slice(0, 10) ?? null;
6684
+ return {
6685
+ startDate: startDate ?? cutoff,
6686
+ endDate,
6687
+ window,
6688
+ explicitDates: startDate !== null || endDate !== null
6689
+ };
6690
+ }
6587
6691
 
6588
6692
  // ../contracts/src/visibility-stats.ts
6589
6693
  import { z as z24 } from "zod";
@@ -7036,7 +7140,15 @@ var ga4TrafficSummaryDtoSchema = z27.object({
7036
7140
  totalOrganicSessions: z27.number(),
7037
7141
  /** Direct-channel sessions (sessions with no source — bookmarks, typed URLs, AI-driven traffic with stripped referrer). 0 for legacy rows from before the column was added. */
7038
7142
  totalDirectSessions: z27.number(),
7039
- totalUsers: z27.number(),
7143
+ /**
7144
+ * Null when the range has no un-dimensioned aggregate behind it.
7145
+ *
7146
+ * GA counts users as a COUNT DISTINCT at the grain requested, so users cannot
7147
+ * be recovered by summing the landing-page dimensioned table. The rolling
7148
+ * windows have a precomputed deduplicated row; an explicit calendar range does
7149
+ * not, and reports the figure as unavailable rather than as an inflated sum.
7150
+ */
7151
+ totalUsers: z27.number().nullable(),
7040
7152
  topPages: z27.array(z27.object({
7041
7153
  landingPage: z27.string(),
7042
7154
  sessions: z27.number(),
@@ -10783,11 +10895,14 @@ export {
10783
10895
  formatAverageDelta,
10784
10896
  formatWindowCountDelta,
10785
10897
  compactDateToIso,
10898
+ parseBoundedRate,
10786
10899
  evaluateModelPointerExposure,
10787
10900
  buildModelChangeNotice,
10788
10901
  googleConnectionDtoSchema,
10789
- gscSearchDataDtoSchema,
10902
+ gscPerformanceOrderBySchema,
10903
+ gscPerformanceResponseDtoSchema,
10790
10904
  gscPerformanceDailyDtoSchema,
10905
+ gscTopPagesDtoSchema,
10791
10906
  gscUrlInspectionDtoSchema,
10792
10907
  gscDeindexedRowSchema,
10793
10908
  gscCoverageSummaryDtoSchema,
@@ -10932,6 +11047,7 @@ export {
10932
11047
  sourceBreakdownDtoSchema,
10933
11048
  parseWindow,
10934
11049
  windowCutoff,
11050
+ resolveDateRange,
10935
11051
  visibilityStatsDtoSchema,
10936
11052
  visibilityCompareDtoSchema,
10937
11053
  calendarMonthBounds,