@canonry/canonry 7.16.0 → 7.17.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 +22 -6
  2. package/assets/assets/{AuditHistoryPanel-Cl9tUvSZ.js → AuditHistoryPanel-CUde9U_6.js} +1 -1
  3. package/assets/assets/{BacklinksPage-CTeTJ9dh.js → BacklinksPage-DMQPw8ks.js} +1 -1
  4. package/assets/assets/{HistoryPage-DpOoXr2P.js → HistoryPage-DZSa1gYf.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-DxXsgM25.js → MeasurementPropertyPage-DgW17Jpd.js} +1 -1
  6. package/assets/assets/ProjectPage-C8vdtqtA.js +8 -0
  7. package/assets/assets/{RunsPage-CfDi7I1z.js → RunsPage-DRfNd-Jp.js} +1 -1
  8. package/assets/assets/{SettingsPage-BHDb1apK.js → SettingsPage-D38Gl1Nv.js} +1 -1
  9. package/assets/assets/{SiteHealthSection-DQZyy4P_.js → SiteHealthSection-CR3zB84D.js} +3 -3
  10. package/assets/assets/{TrafficPage-DX_vN2RL.js → TrafficPage-DX3qRPgQ.js} +2 -2
  11. package/assets/assets/{TrafficSourceDetailPage-Cg92zrtZ.js → TrafficSourceDetailPage-CigB82vX.js} +1 -1
  12. package/assets/assets/{extract-error-message-BKxVCbov.js → extract-error-message-DsMnnkxT.js} +1 -1
  13. package/assets/assets/{index-DoQJETc6.js → index-DBr6ISbx.js} +25 -25
  14. package/assets/assets/{react-sigma_core.esm.min-BhqZcSgh.js → react-sigma_core.esm.min-CkYxzGpi.js} +1 -1
  15. package/assets/assets/{v2-overview-adapter-DZ5YsE_G.js → v2-overview-adapter-5x8UiA07.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-O6XPXEN6.js → chunk-2MBI5SYB.js} +3 -3
  18. package/dist/{chunk-SPZRSKCF.js → chunk-3NAZXITW.js} +1381 -797
  19. package/dist/{chunk-6LS2R477.js → chunk-PVWGKDY6.js} +98 -2
  20. package/dist/{chunk-2S7U2YHI.js → chunk-QPOXDS43.js} +2 -2
  21. package/dist/{chunk-KDWTNI4Z.js → chunk-RBQUGZM4.js} +23 -1
  22. package/dist/{chunk-VWSUFTW5.js → chunk-WEKVQK3O.js} +6 -6
  23. package/dist/{chunk-3ZGBSHZ2.js → chunk-WVBLFG7H.js} +30 -3
  24. package/dist/cli.js +123 -12
  25. package/dist/{demo-server-6OE3H2KL.js → demo-server-BVJFYR4V.js} +68 -15
  26. package/dist/index.js +6 -6
  27. package/dist/{intelligence-service-USSTKAKC.js → intelligence-service-FK2W72X7.js} +2 -2
  28. package/dist/mcp.js +4 -4
  29. package/package.json +14 -14
  30. package/assets/assets/ProjectPage-By9arjLJ.js +0 -8
@@ -17416,6 +17416,84 @@ var ga4PropertySummaryDtoSchema = z51.object({
17416
17416
  var ga4PropertiesDtoSchema = z51.object({
17417
17417
  properties: z51.array(ga4PropertySummaryDtoSchema)
17418
17418
  });
17419
+ var gaSearchLandingWindowSchema = z51.enum(["7d", "28d", "90d"]);
17420
+ var GA_SEARCH_LANDING_WINDOWS = gaSearchLandingWindowSchema.options;
17421
+ var GA_SEARCH_LANDING_WINDOW_DAYS = {
17422
+ "7d": 7,
17423
+ "28d": 28,
17424
+ "90d": 90
17425
+ };
17426
+ var GA_SEARCH_LANDING_DEFAULT_WINDOW = "28d";
17427
+ var GA_SEARCH_LANDING_DEFAULT_LIMIT = 50;
17428
+ var GA_SEARCH_LANDING_MAX_LIMIT = 1e3;
17429
+ var gaSearchLandingStatusSchema = z51.enum(["never-synced", "ready", "unavailable", "error"]);
17430
+ var gaSearchLandingMetricsSchema = z51.object({
17431
+ organicGoogleSearchClicks: z51.number().int().nonnegative(),
17432
+ organicGoogleSearchImpressions: z51.number().int().nonnegative(),
17433
+ /** GA4's own click-through rate (0..1). Null when there were no impressions. */
17434
+ organicGoogleSearchClickThroughRate: fraction(z51.number().min(0).max(1)).nullable(),
17435
+ /** GA4's own average position. Null when there were no impressions. */
17436
+ organicGoogleSearchAveragePosition: z51.number().nonnegative().nullable(),
17437
+ /**
17438
+ * GA4's plain `activeUsers` from the same request, which is what GA4's own
17439
+ * report shows in this column (verified against the GA4 UI). Not limited to
17440
+ * Google organic sessions.
17441
+ */
17442
+ activeUsers: z51.number().int().nonnegative()
17443
+ });
17444
+ var gaSearchLandingPageRowSchema = gaSearchLandingMetricsSchema.extend({
17445
+ /** GA4's `landingPagePlusQueryString`, exactly as reported (never normalized). */
17446
+ landingPage: z51.string()
17447
+ });
17448
+ var gaSearchLandingPagesResponseSchema = z51.object({
17449
+ source: z51.literal("ga4-search-console-link"),
17450
+ status: gaSearchLandingStatusSchema,
17451
+ /** The last failed attempt's message (Google's own text for `unavailable`). */
17452
+ error: z51.string().nullable(),
17453
+ /** When the stored snapshot was written. Null when none exists. */
17454
+ syncedAt: z51.string().datetime().nullable(),
17455
+ /** When a sync last tried to refresh the snapshot, successful or not. */
17456
+ attemptedAt: z51.string().datetime().nullable(),
17457
+ window: gaSearchLandingWindowSchema,
17458
+ /** Inclusive first day of the stored window, in the property's time zone. */
17459
+ windowStart: z51.iso.date().nullable(),
17460
+ /** Inclusive last day of the stored window (yesterday at sync time). */
17461
+ windowEnd: z51.iso.date().nullable(),
17462
+ windowDays: z51.number().int().positive().nullable(),
17463
+ /** GA4 property reporting time zone the window dates are in. */
17464
+ timeZone: z51.string().nullable(),
17465
+ subjectToThresholding: z51.boolean(),
17466
+ dataLossFromOtherRow: z51.boolean(),
17467
+ /** GA4's own TOTAL row for the window. Never a sum of `rows`. Null when no snapshot exists. */
17468
+ total: gaSearchLandingMetricsSchema.nullable(),
17469
+ /** Rows GA4 reported for the window. Can exceed `totalRows` when `rowsCapped`. */
17470
+ reportRowCount: z51.number().int().nonnegative().nullable(),
17471
+ /** True when GA4 reported more rows than were stored (the per-window row cap). */
17472
+ rowsCapped: z51.boolean(),
17473
+ /** Stored rows for the window, the population `limit` / `offset` page over. */
17474
+ totalRows: z51.number().int().nonnegative(),
17475
+ limit: z51.number().int().positive(),
17476
+ offset: z51.number().int().nonnegative(),
17477
+ /** Ordered by clicks, then impressions (both descending), then landing page. */
17478
+ rows: z51.array(gaSearchLandingPageRowSchema)
17479
+ });
17480
+ function gaSearchLandingHasSearchData(total) {
17481
+ return total.organicGoogleSearchClicks > 0 || total.organicGoogleSearchImpressions > 0;
17482
+ }
17483
+ var gaSearchLandingSyncResultSchema = z51.object({
17484
+ status: z51.enum(["ready", "unavailable", "error"]),
17485
+ /** Windows written by this sync, empty unless `ready`. */
17486
+ windows: z51.array(z51.object({
17487
+ window: gaSearchLandingWindowSchema,
17488
+ rowCount: z51.number().int().nonnegative(),
17489
+ reportRowCount: z51.number().int().nonnegative(),
17490
+ rowsCapped: z51.boolean()
17491
+ })),
17492
+ /** Landing-page rows written across every window. */
17493
+ rowCount: z51.number().int().nonnegative(),
17494
+ error: z51.string().optional()
17495
+ });
17496
+ var gaSyncOnlySchema = z51.enum(["traffic", "ai", "social", "search-landing"]);
17419
17497
  var ga4SyncResponseDtoSchema = z51.object({
17420
17498
  synced: z51.boolean(),
17421
17499
  rowCount: z51.number().int().nonnegative(),
@@ -17438,12 +17516,21 @@ var ga4SyncResponseDtoSchema = z51.object({
17438
17516
  syncedAt: z51.string(),
17439
17517
  measurement: z51.object({
17440
17518
  acquisition: z51.object({ days: z51.number().int().nonnegative(), status: z51.enum(["ready", "error"]), rowCount: z51.number().int().nonnegative(), error: z51.string().optional() }),
17441
- leads: z51.object({ days: z51.number().int().nonnegative(), status: z51.enum(["ready", "error", "not-configured"]), rowCount: z51.number().int().nonnegative(), attributionScope: z51.enum(["landing-page", "channel"]).optional(), error: z51.string().optional() })
17519
+ leads: z51.object({ days: z51.number().int().nonnegative(), status: z51.enum(["ready", "error", "not-configured"]), rowCount: z51.number().int().nonnegative(), attributionScope: z51.enum(["landing-page", "channel"]).optional(), error: z51.string().optional() }),
17520
+ /**
17521
+ * GA4's Search Console landing-page snapshot. Absent when `only` names
17522
+ * another slice, since this sync did not attempt it. Its failure never
17523
+ * fails the sync: the previous snapshot is kept.
17524
+ */
17525
+ searchLandingPages: gaSearchLandingSyncResultSchema.optional()
17442
17526
  }),
17443
17527
  /**
17444
17528
  * Components that were written this run. Present when `only` is set.
17445
17529
  * Always includes `traffic` and `summary` (the share denominator) plus
17446
- * the requested channel breakdown — `ai` and/or `social`.
17530
+ * the requested slice: `ai`, `social` or `search-landing`. `search-landing`
17531
+ * fails soft, so it is listed only when its snapshot was stored; a failed
17532
+ * or `unavailable` attempt is left out and reported in
17533
+ * `measurement.searchLandingPages`.
17447
17534
  */
17448
17535
  syncedComponents: z51.array(z51.string()).optional()
17449
17536
  });
@@ -23063,6 +23150,15 @@ export {
23063
23150
  formatAiReferralClassSummary,
23064
23151
  ga4StatusDtoSchema,
23065
23152
  ga4PropertiesDtoSchema,
23153
+ gaSearchLandingWindowSchema,
23154
+ GA_SEARCH_LANDING_WINDOWS,
23155
+ GA_SEARCH_LANDING_WINDOW_DAYS,
23156
+ GA_SEARCH_LANDING_DEFAULT_WINDOW,
23157
+ GA_SEARCH_LANDING_DEFAULT_LIMIT,
23158
+ GA_SEARCH_LANDING_MAX_LIMIT,
23159
+ gaSearchLandingPagesResponseSchema,
23160
+ gaSearchLandingHasSearchData,
23161
+ gaSyncOnlySchema,
23066
23162
  ga4SyncResponseDtoSchema,
23067
23163
  GaMoverChangeBases,
23068
23164
  gaSocialReferralTrendResponseSchema,
@@ -5,7 +5,7 @@ import {
5
5
  siteCrawlGraphNodes,
6
6
  siteCrawlPages,
7
7
  siteCrawlSnapshots
8
- } from "./chunk-SPZRSKCF.js";
8
+ } from "./chunk-3NAZXITW.js";
9
9
  import {
10
10
  SITE_CRAWL_GRAPH_MAX_EDGES,
11
11
  SITE_CRAWL_GRAPH_MAX_NODES,
@@ -14,7 +14,7 @@ import {
14
14
  factorStatusFromDistribution,
15
15
  factorStatusFromScore,
16
16
  roundPreservingTotal
17
- } from "./chunk-6LS2R477.js";
17
+ } from "./chunk-PVWGKDY6.js";
18
18
 
19
19
  // src/site-audit-factors.ts
20
20
  function recordedSharePct(factor) {
@@ -3,6 +3,7 @@ import {
3
3
  AGENT_MEMORY_VALUE_MAX_BYTES,
4
4
  DISCOVERY_MAX_PROBES_CAP,
5
5
  DISCOVERY_PROBE_CONCURRENCY_CAP,
6
+ GA_SEARCH_LANDING_MAX_LIMIT,
6
7
  GOOGLE_MARKETING_STORED_SNAPSHOT_PAGE_MAX,
7
8
  LATEST_RUN_ID,
8
9
  PROVIDER_ACCOUNT_FAILURE_STREAK,
@@ -44,6 +45,7 @@ import {
44
45
  feedbackSubmissionSchema,
45
46
  gaMeasurementAnalysisWindowSchema,
46
47
  gaMeasurementHostScopeSchema,
48
+ gaSearchLandingWindowSchema,
47
49
  googleAdsMetricsWindowSchema,
48
50
  gscPerformanceOrderBySchema,
49
51
  keywordBatchRequestSchema,
@@ -139,7 +141,7 @@ import {
139
141
  trafficSeriesGranularitySchema,
140
142
  visibilityCompareSelectionSchema,
141
143
  visibilityReportRequestSchema
142
- } from "./chunk-6LS2R477.js";
144
+ } from "./chunk-PVWGKDY6.js";
143
145
 
144
146
  // src/cli-error.ts
145
147
  function isMachineFormat(format) {
@@ -719,6 +721,12 @@ var gaMeasurementAnalysisInputSchema = z3.object({
719
721
  pathPrefix: z3.string().min(1).optional(),
720
722
  limit: z3.number().int().positive().max(100).optional()
721
723
  });
724
+ var gaSearchLandingPagesInputSchema = z3.object({
725
+ project: projectNameSchema,
726
+ window: gaSearchLandingWindowSchema.optional(),
727
+ limit: z3.number().int().positive().max(GA_SEARCH_LANDING_MAX_LIMIT).optional(),
728
+ offset: z3.number().int().nonnegative().optional()
729
+ });
722
730
  var queriesInputSchema = z3.object({
723
731
  project: projectNameSchema,
724
732
  request: queryBatchRequestSchema
@@ -2291,6 +2299,20 @@ var canonryMcpTools = [
2291
2299
  compactStringParams(input, ["window", "hostScope", "pathPrefix", "limit"])
2292
2300
  )
2293
2301
  }),
2302
+ defineTool({
2303
+ name: "canonry_ga_search_landing_pages",
2304
+ title: "Get GA4 Google organic search landing pages",
2305
+ description: "Read GA4's \"Google organic search traffic: Landing page + query string\" report (Search Console link) as the last GA sync stored it: clicks, impressions, CTR, average position and active users per landing page for the 7, 28 (default) or 90 days ending yesterday in the property time zone. `total` is GA4's own Total row; never sum `rows`. Status `unavailable` means GA4 refused the Search Console metrics (link Search Console in GA4); the previous snapshot is still returned. Stored data only.",
2306
+ access: "read",
2307
+ tier: "ga",
2308
+ inputSchema: gaSearchLandingPagesInputSchema,
2309
+ annotations: readAnnotations(),
2310
+ openApiOperations: ["GET /api/v1/projects/{name}/ga/search-landing-pages"],
2311
+ handler: (client, input) => client.gaSearchLandingPages(
2312
+ input.project,
2313
+ compactStringParams(input, ["window", "limit", "offset"])
2314
+ )
2315
+ }),
2294
2316
  defineTool({
2295
2317
  name: "canonry_ga_traffic",
2296
2318
  title: "Get GA traffic",
@@ -20,20 +20,20 @@ import {
20
20
  saveConfig,
21
21
  saveConfigPatch,
22
22
  sentimentInstallReadiness
23
- } from "./chunk-3ZGBSHZ2.js";
23
+ } from "./chunk-WVBLFG7H.js";
24
24
  import {
25
25
  computeFactorAverages,
26
26
  deleteSiteCrawlGraphLayout,
27
27
  persistSiteCrawlGraphLayout,
28
28
  prepareSiteCrawlGraphLayout,
29
29
  recordedSharePct
30
- } from "./chunk-2S7U2YHI.js";
30
+ } from "./chunk-QPOXDS43.js";
31
31
  import {
32
32
  CanonryMcpToolNames,
33
33
  CliError,
34
34
  PACKAGE_VERSION,
35
35
  canonryMcpTools
36
- } from "./chunk-KDWTNI4Z.js";
36
+ } from "./chunk-RBQUGZM4.js";
37
37
  import {
38
38
  CC_CACHE_DIR,
39
39
  DUCKDB_SPEC,
@@ -217,7 +217,7 @@ import {
217
217
  siteLivenessState,
218
218
  toAlertView,
219
219
  usageCounters
220
- } from "./chunk-SPZRSKCF.js";
220
+ } from "./chunk-3NAZXITW.js";
221
221
  import {
222
222
  AGENT_MEMORY_VALUE_MAX_BYTES,
223
223
  AGENT_NONE,
@@ -432,7 +432,7 @@ import {
432
432
  winnabilityClassLabel,
433
433
  withRetry,
434
434
  withSkippedProviders
435
- } from "./chunk-6LS2R477.js";
435
+ } from "./chunk-PVWGKDY6.js";
436
436
 
437
437
  // src/runtime-context.ts
438
438
  function cliRuntimeContext(env = process.env, stdio = process) {
@@ -8045,7 +8045,7 @@ function nonBlank(value) {
8045
8045
  return trimmed ? trimmed : void 0;
8046
8046
  }
8047
8047
  function resolveBuildCommit(env = process.env) {
8048
- const embedded = true ? "27c13aa5394b95a729a483c1d7062fc4be42361f" : void 0;
8048
+ const embedded = true ? "292663bc656bcd51a4a32fa879fbdcd7f7935398" : void 0;
8049
8049
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
8050
8050
  }
8051
8051
  function resolveInstanceIdentity(env = process.env) {
@@ -5,7 +5,7 @@ import {
5
5
  PACKAGE_VERSION,
6
6
  canonryMcpTools,
7
7
  isMachineFormat
8
- } from "./chunk-KDWTNI4Z.js";
8
+ } from "./chunk-RBQUGZM4.js";
9
9
  import {
10
10
  AGENT_PROVIDER_IDS,
11
11
  CANONRY_NPM_PACKAGE_URL,
@@ -50,7 +50,7 @@ import {
50
50
  skillsClientSchema,
51
51
  upgradeCaveatFor,
52
52
  upgradeCommandFor
53
- } from "./chunk-6LS2R477.js";
53
+ } from "./chunk-PVWGKDY6.js";
54
54
 
55
55
  // src/config.ts
56
56
  import fs from "fs";
@@ -5212,6 +5212,18 @@ var getApiV1ProjectsByNameGaMeasurementAnalysis = (options) => {
5212
5212
  ...options
5213
5213
  });
5214
5214
  };
5215
+ var getApiV1ProjectsByNameGaSearchLandingPages = (options) => {
5216
+ return (options.client ?? client).get({
5217
+ security: [
5218
+ {
5219
+ scheme: "bearer",
5220
+ type: "http"
5221
+ }
5222
+ ],
5223
+ url: "/api/v1/projects/{name}/ga/search-landing-pages",
5224
+ ...options
5225
+ });
5226
+ };
5215
5227
  var postApiV1ProjectsByNameGaSync = (options) => {
5216
5228
  return (options.client ?? client).post({
5217
5229
  security: [
@@ -8598,11 +8610,26 @@ var ApiClient = class {
8598
8610
  })
8599
8611
  );
8600
8612
  }
8613
+ /**
8614
+ * One stored window of GA4's Search Console landing-page report
8615
+ * (`window`, `limit`, `offset`). Its `total` is GA4's own Total row.
8616
+ */
8617
+ async gaSearchLandingPages(project, params) {
8618
+ return this.invoke(
8619
+ () => getApiV1ProjectsByNameGaSearchLandingPages({
8620
+ client: this.heyClient,
8621
+ path: { name: project },
8622
+ query: params
8623
+ })
8624
+ );
8625
+ }
8601
8626
  async gaSync(project, body) {
8602
8627
  return this.invoke(
8603
8628
  () => postApiV1ProjectsByNameGaSync({
8604
8629
  client: this.heyClient,
8605
8630
  path: { name: project },
8631
+ // `--only` is forwarded verbatim: the server validates it against
8632
+ // `gaSyncOnlySchema` and names the accepted values in its 400.
8606
8633
  body: body ?? {}
8607
8634
  })
8608
8635
  );
@@ -9694,7 +9721,7 @@ var CANONRY_MCP_TOOLKITS = [
9694
9721
  {
9695
9722
  name: "ga",
9696
9723
  title: "Google Analytics 4",
9697
- description: "Read GA traffic, AI/social referral history, attribution trend, and session history.",
9724
+ description: "Read GA traffic, AI/social referral history, attribution trend, session history, and GA4's Search Console landing-page report (Google organic clicks, impressions, CTR, position and active users per landing page).",
9698
9725
  whenToLoad: "Load when you need traffic, referral, or attribution data from Google Analytics 4."
9699
9726
  },
9700
9727
  {
package/dist/cli.js CHANGED
@@ -33,11 +33,11 @@ import {
33
33
  trackCliCommandFinished,
34
34
  trackEvent,
35
35
  waitForServerRuntimeStartup
36
- } from "./chunk-VWSUFTW5.js";
36
+ } from "./chunk-WEKVQK3O.js";
37
37
  import {
38
38
  autoSyncSkills,
39
39
  formatAutoSyncNotice
40
- } from "./chunk-O6XPXEN6.js";
40
+ } from "./chunk-2MBI5SYB.js";
41
41
  import {
42
42
  configExists,
43
43
  createApiClient,
@@ -54,13 +54,13 @@ import {
54
54
  saveConfig,
55
55
  saveConfigPatch,
56
56
  shouldPrintServeSkillsNudge
57
- } from "./chunk-3ZGBSHZ2.js";
57
+ } from "./chunk-WVBLFG7H.js";
58
58
  import {
59
59
  DEFAULT_DEMO_TRUSTED_PROXIES,
60
60
  trustedProxiesProblem,
61
61
  trustedProxyProblem
62
62
  } from "./chunk-RB2F4D3Z.js";
63
- import "./chunk-2S7U2YHI.js";
63
+ import "./chunk-QPOXDS43.js";
64
64
  import {
65
65
  CliError,
66
66
  EXIT_SYSTEM_ERROR,
@@ -72,7 +72,7 @@ import {
72
72
  runMeasurementDraftAction,
73
73
  systemError,
74
74
  usageError
75
- } from "./chunk-KDWTNI4Z.js";
75
+ } from "./chunk-RBQUGZM4.js";
76
76
  import {
77
77
  CLOUDFLARE_WORKER_BINDINGS,
78
78
  CLOUDFLARE_WORKER_GENERATED_MARKER,
@@ -104,7 +104,7 @@ import {
104
104
  rawEventSamples,
105
105
  readMarketCompetitorNames,
106
106
  runs
107
- } from "./chunk-SPZRSKCF.js";
107
+ } from "./chunk-3NAZXITW.js";
108
108
  import {
109
109
  AdsDeliverySnapshotStatuses,
110
110
  AdsHistoricalCampaignRollupStatuses,
@@ -119,6 +119,7 @@ import {
119
119
  CitationStates,
120
120
  DiscoveryPromoteCompetitorSkipReasons,
121
121
  FEEDBACK_KINDS,
122
+ GA_SEARCH_LANDING_WINDOW_DAYS,
122
123
  GaMoverChangeBases,
123
124
  MeasurementEvidenceShapes,
124
125
  OUTSTANDING_PROVIDER_BATCH_STATUSES,
@@ -181,6 +182,7 @@ import {
181
182
  formatRunErrorOneLine,
182
183
  formatSignedPointDelta,
183
184
  formatSiteAuditPageBudget,
185
+ gaSearchLandingHasSearchData,
184
186
  gbpNegativeReviewMaxStarsSchema,
185
187
  googleAdsMetricsWindowSchema,
186
188
  isRetiredAgentProviderId,
@@ -234,7 +236,7 @@ import {
234
236
  visibilityCompareSelectionSchema,
235
237
  visibilityReportRequestSchema,
236
238
  winnabilityClassSchema
237
- } from "./chunk-6LS2R477.js";
239
+ } from "./chunk-PVWGKDY6.js";
238
240
 
239
241
  // src/cli.ts
240
242
  import { pathToFileURL } from "url";
@@ -784,7 +786,7 @@ function parseDemoTrustedProxies(values) {
784
786
  async function demoCommand(options) {
785
787
  const { host, port } = parseDemoListenOptions(options);
786
788
  const trustProxy = parseDemoTrustedProxies(options.trustProxy);
787
- const { createDemoServer } = await import("./demo-server-6OE3H2KL.js");
789
+ const { createDemoServer } = await import("./demo-server-BVJFYR4V.js");
788
790
  const app = await createDemoServer({ trustProxy });
789
791
  let stopping = false;
790
792
  const stop = () => {
@@ -1198,7 +1200,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
1198
1200
  }
1199
1201
  }
1200
1202
  async function backfillInsightsCommand(project2, opts) {
1201
- const { IntelligenceService } = await import("./intelligence-service-USSTKAKC.js");
1203
+ const { IntelligenceService } = await import("./intelligence-service-FK2W72X7.js");
1202
1204
  const config = loadConfig();
1203
1205
  const db = createClient(config.database);
1204
1206
  migrate(db);
@@ -5437,8 +5439,99 @@ async function gaSync(project2, opts) {
5437
5439
  console.log(` AI rows: ${result.aiReferralCount}`);
5438
5440
  console.log(` Social rows: ${result.socialReferralCount}`);
5439
5441
  console.log(` Period: ${result.days} days${result.clamped ? ` (requested ${result.requestedDays})` : ""}`);
5442
+ const searchLanding = result.measurement.searchLandingPages;
5443
+ if (searchLanding) {
5444
+ console.log(` Search Console landing pages: ${searchLanding.status} (${searchLanding.rowCount} rows)`);
5445
+ if (searchLanding.error) console.log(` Error: ${searchLanding.error}`);
5446
+ }
5440
5447
  console.log(` Synced at: ${result.syncedAt}`);
5441
5448
  }
5449
+ var SEARCH_LANDING_NOT_AVAILABLE = "n/a";
5450
+ function searchLandingCells(metrics) {
5451
+ return [
5452
+ metrics.organicGoogleSearchClicks.toLocaleString("en-US"),
5453
+ metrics.organicGoogleSearchImpressions.toLocaleString("en-US"),
5454
+ metrics.organicGoogleSearchClickThroughRate === null ? SEARCH_LANDING_NOT_AVAILABLE : formatPercent(metrics.organicGoogleSearchClickThroughRate),
5455
+ metrics.organicGoogleSearchAveragePosition === null ? SEARCH_LANDING_NOT_AVAILABLE : metrics.organicGoogleSearchAveragePosition.toFixed(1),
5456
+ metrics.activeUsers.toLocaleString("en-US")
5457
+ ];
5458
+ }
5459
+ async function gaSearchLandingPages(project2, opts) {
5460
+ const client = getClient7();
5461
+ const params = {};
5462
+ if (opts?.window) params.window = opts.window;
5463
+ if (opts?.limit) params.limit = opts.limit;
5464
+ if (opts?.offset) params.offset = opts.offset;
5465
+ const result = await client.gaSearchLandingPages(
5466
+ project2,
5467
+ Object.keys(params).length > 0 ? params : void 0
5468
+ );
5469
+ if (opts?.format === "json") {
5470
+ console.log(JSON.stringify(result, null, 2));
5471
+ return;
5472
+ } else if (opts?.format === "jsonl") {
5473
+ emitJsonl(result.rows.map((row) => ({
5474
+ project: project2,
5475
+ window: result.window,
5476
+ windowStart: result.windowStart,
5477
+ windowEnd: result.windowEnd,
5478
+ ...row
5479
+ })));
5480
+ return;
5481
+ }
5482
+ if (result.status === "never-synced") {
5483
+ console.log(`No GA4 Search Console landing-page data for "${project2}" yet. Run "canonry ga sync ${project2}".`);
5484
+ return;
5485
+ }
5486
+ console.log(`Google organic search traffic: landing page + query string (GA4 Search Console link) for "${project2}"`);
5487
+ if (result.windowStart && result.windowEnd) {
5488
+ console.log(`Last ${result.windowDays ?? GA_SEARCH_LANDING_WINDOW_DAYS[result.window]} days: ${result.windowStart} to ${result.windowEnd}${result.timeZone ? ` (${result.timeZone})` : ""}`);
5489
+ }
5490
+ if (result.status === "unavailable") {
5491
+ console.log(`Status: unavailable. GA4 did not return Search Console data: ${result.error ?? "no reason given"}`);
5492
+ console.log(`Link Search Console to this GA4 property (GA4 Admin, Product links, Search Console links), then run "canonry ga sync ${project2}".`);
5493
+ } else if (result.status === "error") {
5494
+ console.log(`Status: the last refresh failed${result.attemptedAt ? ` (${result.attemptedAt})` : ""}: ${result.error ?? "unknown error"}`);
5495
+ }
5496
+ if (result.status !== "ready" && result.total) {
5497
+ console.log(`Showing the snapshot synced ${result.syncedAt ?? "earlier"}.`);
5498
+ }
5499
+ if (!result.total) {
5500
+ console.log("No stored snapshot.");
5501
+ return;
5502
+ }
5503
+ const headers = ["LANDING PAGE", "CLICKS", "IMPRESSIONS", "CTR", "AVG POSITION", "ACTIVE USERS"];
5504
+ const table = [
5505
+ ["Total", ...searchLandingCells(result.total)],
5506
+ ...result.rows.map((row) => [row.landingPage, ...searchLandingCells(row)])
5507
+ ];
5508
+ const pageWidth = Math.min(60, Math.max(headers[0].length, ...table.map((row) => row[0].length)));
5509
+ const widths = headers.map((header, index) => index === 0 ? pageWidth : Math.max(header.length, ...table.map((row) => row[index].length)));
5510
+ const line = (cells) => ` ${cells.map((cell2, index) => index === 0 ? (cell2.length > pageWidth ? `${cell2.slice(0, pageWidth - 3)}...` : cell2).padEnd(pageWidth) : cell2.padStart(widths[index])).join(" ")}`;
5511
+ console.log();
5512
+ console.log(line(headers));
5513
+ console.log(` ${widths.map((width) => "\u2500".repeat(width)).join(" ")}`);
5514
+ for (const row of table) console.log(line(row));
5515
+ console.log();
5516
+ if (result.rows.length > 0) {
5517
+ console.log(` Showing ${result.offset + 1}-${result.offset + result.rows.length} of ${result.totalRows} pages.`);
5518
+ } else if (result.totalRows > 0) {
5519
+ console.log(` No landing-page rows at offset ${result.offset} (${result.totalRows} stored).`);
5520
+ }
5521
+ if (!gaSearchLandingHasSearchData(result.total)) {
5522
+ console.log(" No landing pages had Google organic search traffic in this window.");
5523
+ console.log(" If the property should have some, check its Search Console link in GA4 (Admin, Product links, Search Console links).");
5524
+ }
5525
+ if (result.rowsCapped && result.reportRowCount !== null) {
5526
+ console.log(` GA4 reported ${result.reportRowCount} rows; the ${result.totalRows} with the most clicks are stored.`);
5527
+ }
5528
+ if (result.subjectToThresholding) {
5529
+ console.log(" GA4 applied thresholding to this report, so some rows may be withheld.");
5530
+ }
5531
+ if (result.status === "ready" && result.syncedAt) {
5532
+ console.log(` Synced at: ${result.syncedAt}`);
5533
+ }
5534
+ }
5442
5535
  async function gaTraffic(project2, opts) {
5443
5536
  const client = getClient7();
5444
5537
  const params = rangeParams(opts, opts?.limit ? { limit: String(opts.limit) } : void 0);
@@ -6094,13 +6187,13 @@ var GA_CLI_COMMANDS = [
6094
6187
  },
6095
6188
  {
6096
6189
  path: ["ga", "sync"],
6097
- usage: "canonry ga sync <project> [--days 30] [--only traffic|ai|social] [--format json]",
6190
+ usage: "canonry ga sync <project> [--days 30] [--only traffic|ai|social|search-landing] [--format json]",
6098
6191
  options: {
6099
6192
  days: stringOption(),
6100
6193
  only: stringOption()
6101
6194
  },
6102
6195
  run: async (input) => {
6103
- const project2 = requireProject(input, "ga.sync", "canonry ga sync <project> [--days 30] [--only traffic|ai|social] [--format json]");
6196
+ const project2 = requireProject(input, "ga.sync", "canonry ga sync <project> [--days 30] [--only traffic|ai|social|search-landing] [--format json]");
6104
6197
  const daysStr = getString(input.values, "days");
6105
6198
  const days = daysStr ? parseInt(daysStr, 10) : void 0;
6106
6199
  const only = getString(input.values, "only");
@@ -6132,6 +6225,24 @@ var GA_CLI_COMMANDS = [
6132
6225
  });
6133
6226
  }
6134
6227
  },
6228
+ {
6229
+ path: ["ga", "search-landing-pages"],
6230
+ usage: "canonry ga search-landing-pages <project> [--window 7d|28d|90d] [--limit 50] [--offset 0] [--format json|jsonl]",
6231
+ options: {
6232
+ window: stringOption(),
6233
+ limit: stringOption(),
6234
+ offset: stringOption()
6235
+ },
6236
+ run: async (input) => {
6237
+ const project2 = requireProject(input, "ga.search-landing-pages", "canonry ga search-landing-pages <project> [--window 7d|28d|90d] [--limit 50] [--offset 0] [--format json|jsonl]");
6238
+ await gaSearchLandingPages(project2, {
6239
+ window: getString(input.values, "window"),
6240
+ limit: getString(input.values, "limit"),
6241
+ offset: getString(input.values, "offset"),
6242
+ format: input.format
6243
+ });
6244
+ }
6245
+ },
6135
6246
  {
6136
6247
  path: ["ga", "traffic"],
6137
6248
  usage: `canonry ga traffic <project> [--limit 50] ${RANGE_USAGE} [--format json]`,
@@ -6241,7 +6352,7 @@ var GA_CLI_COMMANDS = [
6241
6352
  unknownSubcommand(input.positionals[0], {
6242
6353
  command: "ga",
6243
6354
  usage: "canonry ga <subcommand> <project> [args]",
6244
- available: ["connect", "disconnect", "status", "properties", "sync", "measurement-analysis", "traffic", "coverage", "ai-referral-history", "ai-referral-daily", "social-referral-history", "session-history", "social-referral-summary", "attribution"]
6355
+ available: ["connect", "disconnect", "status", "properties", "sync", "measurement-analysis", "search-landing-pages", "traffic", "coverage", "ai-referral-history", "ai-referral-daily", "social-referral-history", "session-history", "social-referral-summary", "attribution"]
6245
6356
  });
6246
6357
  }
6247
6358
  }