@canonry/canonry 7.15.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-YFCKHEKM.js → chunk-2MBI5SYB.js} +3 -3
  18. package/dist/{chunk-7HJYGDRW.js → chunk-3NAZXITW.js} +1680 -840
  19. package/dist/{chunk-KOBK4I5N.js → chunk-PVWGKDY6.js} +156 -2
  20. package/dist/{chunk-SG6T3DEP.js → chunk-QPOXDS43.js} +2 -2
  21. package/dist/{chunk-LC7GMHOL.js → chunk-RBQUGZM4.js} +23 -1
  22. package/dist/{chunk-N2MRW2DH.js → chunk-WEKVQK3O.js} +43 -20
  23. package/dist/{chunk-SGQBPTWQ.js → chunk-WVBLFG7H.js} +30 -3
  24. package/dist/cli.js +123 -12
  25. package/dist/{demo-server-YWGUGZMI.js → demo-server-BVJFYR4V.js} +68 -15
  26. package/dist/index.js +6 -6
  27. package/dist/{intelligence-service-7RKUUR7O.js → intelligence-service-FK2W72X7.js} +2 -2
  28. package/dist/mcp.js +4 -4
  29. package/package.json +9 -9
  30. package/assets/assets/ProjectPage-By9arjLJ.js +0 -8
@@ -5679,6 +5679,60 @@ function describeError(err) {
5679
5679
  return "unknown error";
5680
5680
  }
5681
5681
  }
5682
+ var NETWORK_ERROR_CODE = /^[A-Z][A-Z0-9_]{1,63}$/;
5683
+ var MAX_CAUSE_DEPTH = 5;
5684
+ var RESOLVE_CODES = /* @__PURE__ */ new Set(["ENOTFOUND", "EAI_AGAIN", "ENODATA"]);
5685
+ var CONNECT_CODES = /* @__PURE__ */ new Set(["ECONNREFUSED", "UND_ERR_CONNECT_TIMEOUT", "ETIMEDOUT", "EHOSTUNREACH", "ENETUNREACH"]);
5686
+ function stringField(value) {
5687
+ return typeof value === "string" && value !== "" ? value : null;
5688
+ }
5689
+ function findNetworkCause(err) {
5690
+ let node = err;
5691
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && node !== null && typeof node === "object"; depth++) {
5692
+ const record = node;
5693
+ if (typeof record.code === "string" && NETWORK_ERROR_CODE.test(record.code)) {
5694
+ return {
5695
+ code: record.code,
5696
+ syscall: stringField(record.syscall),
5697
+ hostname: stringField(record.hostname),
5698
+ address: stringField(record.address),
5699
+ port: typeof record.port === "number" && Number.isInteger(record.port) ? record.port : null
5700
+ };
5701
+ }
5702
+ node = record.cause ?? (Array.isArray(record.errors) ? record.errors[0] : void 0);
5703
+ }
5704
+ return null;
5705
+ }
5706
+ function requestHostname(requestUrl) {
5707
+ if (requestUrl === void 0) return null;
5708
+ try {
5709
+ return stringField(new URL(requestUrl).hostname);
5710
+ } catch {
5711
+ return null;
5712
+ }
5713
+ }
5714
+ function describeFetchError(err, requestUrl) {
5715
+ const message = describeError(err);
5716
+ let cause;
5717
+ try {
5718
+ cause = findNetworkCause(err);
5719
+ } catch {
5720
+ return message;
5721
+ }
5722
+ if (!cause) return message;
5723
+ const host = cause.hostname ?? requestHostname(requestUrl);
5724
+ const resolving = cause.syscall === "getaddrinfo" || RESOLVE_CODES.has(cause.code);
5725
+ const connecting = cause.syscall === "connect" || CONNECT_CODES.has(cause.code);
5726
+ const verb = resolving ? "resolving" : connecting ? "connecting to" : "calling";
5727
+ const address = cause.address && cause.address !== host ? `${cause.address.includes(":") ? `[${cause.address}]` : cause.address}${cause.port !== null ? `:${cause.port}` : ""}` : null;
5728
+ const parts = [cause.code, host ? `${verb} ${host}` : null, address ? `at ${address}` : null];
5729
+ return `${message} (${parts.filter((part) => part !== null).join(" ")})`;
5730
+ }
5731
+ function isFetchTransportError(err) {
5732
+ if (!(err instanceof Error)) return false;
5733
+ if (err.name === "TimeoutError" || err.name === "AbortError") return true;
5734
+ return err instanceof TypeError && err.message === "fetch failed";
5735
+ }
5682
5736
 
5683
5737
  // ../contracts/src/measurement.ts
5684
5738
  import { z as z12 } from "zod";
@@ -6127,6 +6181,7 @@ var gbpSyncResponseSchema = z13.object({
6127
6181
  runId: z13.string(),
6128
6182
  status: z13.string()
6129
6183
  });
6184
+ var GBP_NO_SELECTED_LOCATIONS_ERROR = "No selected GBP locations to sync. Discover and select locations first.";
6130
6185
  var gbpDailyMetricDtoSchema = z13.object({
6131
6186
  locationName: z13.string(),
6132
6187
  date: z13.string(),
@@ -17361,6 +17416,84 @@ var ga4PropertySummaryDtoSchema = z51.object({
17361
17416
  var ga4PropertiesDtoSchema = z51.object({
17362
17417
  properties: z51.array(ga4PropertySummaryDtoSchema)
17363
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"]);
17364
17497
  var ga4SyncResponseDtoSchema = z51.object({
17365
17498
  synced: z51.boolean(),
17366
17499
  rowCount: z51.number().int().nonnegative(),
@@ -17383,12 +17516,21 @@ var ga4SyncResponseDtoSchema = z51.object({
17383
17516
  syncedAt: z51.string(),
17384
17517
  measurement: z51.object({
17385
17518
  acquisition: z51.object({ days: z51.number().int().nonnegative(), status: z51.enum(["ready", "error"]), rowCount: z51.number().int().nonnegative(), error: z51.string().optional() }),
17386
- 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()
17387
17526
  }),
17388
17527
  /**
17389
17528
  * Components that were written this run. Present when `only` is set.
17390
17529
  * Always includes `traffic` and `summary` (the share denominator) plus
17391
- * 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`.
17392
17534
  */
17393
17535
  syncedComponents: z51.array(z51.string()).optional()
17394
17536
  });
@@ -22401,6 +22543,8 @@ export {
22401
22543
  internalError,
22402
22544
  runtimeStateMissing,
22403
22545
  describeError,
22546
+ describeFetchError,
22547
+ isFetchTransportError,
22404
22548
  AI_ENGINE_DOMAINS,
22405
22549
  AI_ENGINE_SELF_DOMAINS,
22406
22550
  VERTEX_AI_SEARCH_PROXY_DOMAIN,
@@ -22452,6 +22596,7 @@ export {
22452
22596
  gbpLocationSelectionRequestSchema,
22453
22597
  gbpSyncRequestSchema,
22454
22598
  gbpSyncResponseSchema,
22599
+ GBP_NO_SELECTED_LOCATIONS_ERROR,
22455
22600
  gbpDailyMetricListResponseSchema,
22456
22601
  gbpKeywordImpressionListResponseSchema,
22457
22602
  gbpPlaceActionListResponseSchema,
@@ -23005,6 +23150,15 @@ export {
23005
23150
  formatAiReferralClassSummary,
23006
23151
  ga4StatusDtoSchema,
23007
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,
23008
23162
  ga4SyncResponseDtoSchema,
23009
23163
  GaMoverChangeBases,
23010
23164
  gaSocialReferralTrendResponseSchema,
@@ -5,7 +5,7 @@ import {
5
5
  siteCrawlGraphNodes,
6
6
  siteCrawlPages,
7
7
  siteCrawlSnapshots
8
- } from "./chunk-7HJYGDRW.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-KOBK4I5N.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-KOBK4I5N.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-SGQBPTWQ.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-SG6T3DEP.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-LC7GMHOL.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-7HJYGDRW.js";
220
+ } from "./chunk-3NAZXITW.js";
221
221
  import {
222
222
  AGENT_MEMORY_VALUE_MAX_BYTES,
223
223
  AGENT_NONE,
@@ -238,6 +238,7 @@ import {
238
238
  DEFAULT_VIEWER_RESEARCH_DAILY_RUN_LIMIT,
239
239
  DiscoveryCompetitorTypes,
240
240
  FEEDBACK_LIMITS,
241
+ GBP_NO_SELECTED_LOCATIONS_ERROR,
241
242
  GOOGLE_ADS_CAMPAIGN_METRICS_MAX_CAMPAIGNS,
242
243
  GOOGLE_ADS_CAMPAIGN_METRICS_MAX_DAYS,
243
244
  GOOGLE_ADS_CAMPAIGN_METRICS_MAX_ROWS,
@@ -328,6 +329,7 @@ import {
328
329
  deriveSiteHealthState,
329
330
  describeBatchIneligibility,
330
331
  describeError,
332
+ describeFetchError,
331
333
  detectAgentRuntime,
332
334
  determineAnswerMentioned,
333
335
  dollarsToMicros,
@@ -430,7 +432,7 @@ import {
430
432
  winnabilityClassLabel,
431
433
  withRetry,
432
434
  withSkippedProviders
433
- } from "./chunk-KOBK4I5N.js";
435
+ } from "./chunk-PVWGKDY6.js";
434
436
 
435
437
  // src/runtime-context.ts
436
438
  function cliRuntimeContext(env = process.env, stdio = process) {
@@ -1597,15 +1599,16 @@ var GoogleAdsClient = class {
1597
1599
  if (includeLoginCustomerId && this.#loginCustomerId) {
1598
1600
  headers["login-customer-id"] = this.#loginCustomerId;
1599
1601
  }
1602
+ const url = `${GOOGLE_ADS_API_BASE}${path9}`;
1600
1603
  let response;
1601
1604
  try {
1602
- response = await this.#fetch(`${GOOGLE_ADS_API_BASE}${path9}`, {
1605
+ response = await this.#fetch(url, {
1603
1606
  ...init,
1604
1607
  headers,
1605
1608
  signal: AbortSignal.timeout(this.#requestTimeoutMs)
1606
1609
  });
1607
1610
  } catch (error) {
1608
- const detail = describeError(error);
1611
+ const detail = describeFetchError(error, url);
1609
1612
  throw new GoogleAdsApiError(
1610
1613
  `Google Ads API request failed: ${sanitizeText(detail, this.#secrets())}`
1611
1614
  );
@@ -8042,7 +8045,7 @@ function nonBlank(value) {
8042
8045
  return trimmed ? trimmed : void 0;
8043
8046
  }
8044
8047
  function resolveBuildCommit(env = process.env) {
8045
- const embedded = true ? "e5577fdc9f99f2cc008aeff477aaf284da4532f7" : void 0;
8048
+ const embedded = true ? "292663bc656bcd51a4a32fa879fbdcd7f7935398" : void 0;
8046
8049
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
8047
8050
  }
8048
8051
  function resolveInstanceIdentity(env = process.env) {
@@ -11005,7 +11008,7 @@ async function executeGbpSync(db, runId, projectId, opts) {
11005
11008
  locationRows = locationRows.filter((l) => wanted.has(l.locationName));
11006
11009
  }
11007
11010
  if (locationRows.length === 0) {
11008
- throw new Error("No selected GBP locations to sync. Discover and select locations first.");
11011
+ throw new Error(GBP_NO_SELECTED_LOCATIONS_ERROR);
11009
11012
  }
11010
11013
  locationRows = await refreshSelectedLocationProfiles(db, projectId, accessToken, locationRows);
11011
11014
  const daysOfMetrics = opts.daysOfMetrics ?? DEFAULT_DAYS_OF_METRICS;
@@ -15453,27 +15456,47 @@ function gaRefreshDays(now = /* @__PURE__ */ new Date()) {
15453
15456
  const reach = inclusiveDayCount(`${reportMonth}-01`, now.toISOString().slice(0, 10)) ?? 0;
15454
15457
  return Math.max(30, reach);
15455
15458
  }
15459
+ function queuedRun(result2) {
15460
+ const record2 = asRecord(result2);
15461
+ const runId = record2?.runId ?? record2?.id;
15462
+ const runStatus = record2?.status;
15463
+ return {
15464
+ runId: typeof runId === "string" ? runId : null,
15465
+ runStatus: typeof runStatus === "string" ? runStatus : null
15466
+ };
15467
+ }
15456
15468
  async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */ new Date()) {
15457
15469
  const integrations = [
15458
- { name: "gsc", run: () => client.gscSync(projectName, {}) },
15459
- { name: "bing", run: () => client.bingInspectSitemap(projectName, {}) },
15460
- { name: "ga", run: () => client.gaSync(projectName, { days: gaRefreshDays(now) }) },
15461
- { name: "gbp", run: () => client.triggerGbpSync(projectName, {}) },
15470
+ { name: "gsc", completion: "queued", run: () => client.gscSync(projectName, {}) },
15471
+ { name: "bing", completion: "queued", run: () => client.bingInspectSitemap(projectName, {}) },
15472
+ { name: "ga", completion: "synchronous", run: () => client.gaSync(projectName, { days: gaRefreshDays(now) }) },
15473
+ { name: "gbp", completion: "queued", run: () => client.triggerGbpSync(projectName, {}) },
15462
15474
  // `ads` remains the OpenAI / ChatGPT Ads integration. For scheduled
15463
15475
  // refreshes, Google providers have explicit names and fan out only here.
15464
- { name: "ads", run: () => client.triggerAdsSync(projectName) },
15465
- { name: "google-ads", run: () => client.triggerGoogleAdsSync(projectName) },
15466
- { name: "gtm", run: () => client.triggerGtmSync(projectName) }
15476
+ { name: "ads", completion: "queued", run: () => client.triggerAdsSync(projectName) },
15477
+ { name: "google-ads", completion: "queued", run: () => client.triggerGoogleAdsSync(projectName) },
15478
+ { name: "gtm", completion: "queued", run: () => client.triggerGtmSync(projectName) }
15467
15479
  ];
15468
15480
  const results = await Promise.allSettled(integrations.map((i) => i.run()));
15469
15481
  results.forEach((result2, idx) => {
15470
- const integration = integrations[idx].name;
15471
- if (result2.status === "fulfilled") {
15472
- log19.info("integration.refreshed", { projectName, integration });
15473
- } else {
15482
+ const { name: integration, completion } = integrations[idx];
15483
+ if (result2.status === "rejected") {
15474
15484
  const reason = result2.reason;
15475
15485
  const message = describeError(reason);
15476
15486
  log19.warn("integration.refresh-failed", { projectName, integration, error: message });
15487
+ return;
15488
+ }
15489
+ switch (completion) {
15490
+ case "synchronous":
15491
+ log19.info("integration.refreshed", { projectName, integration });
15492
+ return;
15493
+ case "queued":
15494
+ log19.info("integration.queued", { projectName, integration, ...queuedRun(result2.value) });
15495
+ return;
15496
+ default: {
15497
+ const _exhaustive = completion;
15498
+ return _exhaustive;
15499
+ }
15477
15500
  }
15478
15501
  });
15479
15502
  }
@@ -5,7 +5,7 @@ import {
5
5
  PACKAGE_VERSION,
6
6
  canonryMcpTools,
7
7
  isMachineFormat
8
- } from "./chunk-LC7GMHOL.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-KOBK4I5N.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
  {