seo 0.2.4 → 0.2.5

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.
package/dist/mcp.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
2
- import { n as load, t as version } from "./package-D6bCOqrD.js";
2
+ import { n as load, t as version } from "./package-BF_XgK7-.js";
3
3
  import { Headers, fetch as fetch$1 } from "undici";
4
4
  import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
5
5
  import { homedir } from "node:os";
@@ -218,6 +218,7 @@ const siteSchema = z.object({
218
218
  isDefault: z.boolean().optional()
219
219
  });
220
220
  const providerPreferenceSchema = z.enum(["cheap", "authoritative"]);
221
+ const analyticsConnectionsSchema = z.object({ google: z.object({ propertyId: z.string() }).optional() }).default({});
221
222
  const clientProfileSchema = z.object({
222
223
  id: z.string(),
223
224
  name: z.string(),
@@ -225,7 +226,7 @@ const clientProfileSchema = z.object({
225
226
  startUrl: z.string().optional(),
226
227
  watchUrls: z.array(z.string()).default([]),
227
228
  brandTerms: z.array(z.string()).default([]),
228
- ga4PropertyId: z.string().optional(),
229
+ analytics: analyticsConnectionsSchema,
229
230
  reportDay: z.number().int().min(1).max(31).optional(),
230
231
  technicalWeekday: z.number().int().min(0).max(7).optional(),
231
232
  isDefault: z.boolean().optional(),
@@ -236,14 +237,14 @@ const configSchema = z.object({
236
237
  defaultSite: z.string().optional(),
237
238
  sites: z.array(siteSchema).default([]),
238
239
  clients: z.array(clientProfileSchema).default([]),
239
- google: z.object({
240
- defaultGa4PropertyId: z.string().optional(),
240
+ analytics: z.object({ google: z.object({
241
+ defaultPropertyId: z.string().optional(),
241
242
  propertyMappings: z.array(z.object({
242
243
  siteUrl: z.string(),
243
- ga4PropertyId: z.string(),
244
+ propertyId: z.string(),
244
245
  addedAt: z.number().int().optional()
245
246
  })).default([])
246
- }).default({ propertyMappings: [] }),
247
+ }).default({ propertyMappings: [] }) }).default({ google: { propertyMappings: [] } }),
247
248
  providers: z.object({
248
249
  semrushApiKey: z.string().optional(),
249
250
  dataForSeoLogin: z.string().optional(),
@@ -460,6 +461,14 @@ function missingOAuthClientMessage(source) {
460
461
  //#endregion
461
462
  //#region packages/core/dist/gsc/auth/types.js
462
463
  const GOOGLE_READONLY_SCOPES = ["https://www.googleapis.com/auth/webmasters.readonly", "https://www.googleapis.com/auth/analytics.readonly"];
464
+ const GOOGLE_READONLY_SCOPE_LABELS = {
465
+ "https://www.googleapis.com/auth/webmasters.readonly": "Search Console",
466
+ "https://www.googleapis.com/auth/analytics.readonly": "Google Analytics"
467
+ };
468
+ function missingGoogleReadonlyScopes(scope) {
469
+ const granted = new Set(scope.split(/\s+/u).filter(Boolean));
470
+ return GOOGLE_READONLY_SCOPES.filter((required) => !granted.has(required));
471
+ }
463
472
  [
464
473
  ...GOOGLE_READONLY_SCOPES,
465
474
  "openid",
@@ -657,6 +666,11 @@ function authRequired() {
657
666
  function authExpired() {
658
667
  return new SeoError("AUTH_EXPIRED", "Google refresh token is no longer valid. Run `seo auth login` again.");
659
668
  }
669
+ function requireReadonlyScopes(tokens) {
670
+ const missing = missingGoogleReadonlyScopes(tokens.scope);
671
+ if (missing.length === 0) return;
672
+ throw new SeoError("ACCESS_DENIED", `Google access is missing ${missing.map((scope) => GOOGLE_READONLY_SCOPE_LABELS[scope]).join(" and ")}. Run \`seo auth login\` again, choose Select all, and approve both permission boxes.`);
673
+ }
660
674
  function needsRefresh(tokens, now) {
661
675
  return !tokens.access_token || tokens.expires_at - now < REFRESH_BUFFER_MS;
662
676
  }
@@ -716,6 +730,7 @@ async function createAuthorizedClient() {
716
730
  let tokens = await readTokens();
717
731
  if (!tokens) throw authRequired();
718
732
  requireClientConfig(tokens);
733
+ requireReadonlyScopes(tokens);
719
734
  if (needsRefresh(tokens, Date.now())) tokens = await refreshStoredToken({ onlyIfExpiring: true });
720
735
  return {
721
736
  client: new LocalGoogleAccessTokenClient(tokens),
@@ -1068,8 +1083,8 @@ async function runGa4Report(propertyId, body, opts = {}) {
1068
1083
  body: JSON.stringify(body)
1069
1084
  });
1070
1085
  if (!response.ok) {
1071
- if (response.status === 403) throw new Error(`GA4 report failed with 403. Check access to property ${propertyId}.`);
1072
- throw new Error(`GA4 report failed with ${response.status}.`);
1086
+ if (response.status === 403) throw new Error(`Google Analytics report failed with 403. Check access to property ${propertyId}.`);
1087
+ throw new Error(`Google Analytics report failed with ${response.status}.`);
1073
1088
  }
1074
1089
  const result = await response.json();
1075
1090
  if (cacheable) db.prepare(`INSERT OR REPLACE INTO ga4_cache
@@ -1091,7 +1106,7 @@ function ga4RowsToObjects(result) {
1091
1106
  return output;
1092
1107
  });
1093
1108
  }
1094
- function ga4ReportQualityWarnings(result, label = "GA4 report") {
1109
+ function ga4ReportQualityWarnings(result, label = "Google Analytics report") {
1095
1110
  const warnings = [];
1096
1111
  if (result.metadata?.dataLossFromOtherRow) warnings.push(`${label} grouped high-cardinality data into (other).`);
1097
1112
  if (result.metadata?.subjectToThresholding) warnings.push(`${label} was subject to Google Analytics thresholding.`);
@@ -1197,11 +1212,11 @@ function compareText$16(left, right) {
1197
1212
  function metric(row, name) {
1198
1213
  const value = row[name];
1199
1214
  const parsed = Number(value);
1200
- if (value === void 0 || value.trim() === "" || !Number.isInteger(parsed) || parsed < 0) throw new SeoError("PROVIDER_UNAVAILABLE", `GA4 returned an invalid ${name} value.`);
1215
+ if (value === void 0 || value.trim() === "" || !Number.isInteger(parsed) || parsed < 0) throw new SeoError("PROVIDER_UNAVAILABLE", `Google Analytics returned an invalid ${name} value.`);
1201
1216
  return parsed;
1202
1217
  }
1203
1218
  function dateValue$1(value) {
1204
- if (!value || !/^\d{8}$/.test(value)) throw new SeoError("PROVIDER_UNAVAILABLE", "GA4 returned an invalid date value.");
1219
+ if (!value || !/^\d{8}$/.test(value)) throw new SeoError("PROVIDER_UNAVAILABLE", "Google Analytics returned an invalid date value.");
1205
1220
  return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`;
1206
1221
  }
1207
1222
  function addMetrics(target, source) {
@@ -1240,7 +1255,7 @@ function analyzeAiReferralRows(input) {
1240
1255
  const detailTotal = emptyMetrics();
1241
1256
  for (const row of input.detailRows) {
1242
1257
  const definition = aiReferralSourceForValue(row.sessionSource ?? "");
1243
- if (!definition || !sourceTotals.has(definition.id)) throw new SeoError("PROVIDER_UNAVAILABLE", "GA4 detail rows contained an unexpected session source.");
1258
+ if (!definition || !sourceTotals.has(definition.id)) throw new SeoError("PROVIDER_UNAVAILABLE", "Google Analytics detail rows contained an unexpected session source.");
1244
1259
  const metrics = {
1245
1260
  sessions: metric(row, "sessions"),
1246
1261
  eventCount: metric(row, "eventCount")
@@ -1273,11 +1288,11 @@ function analyzeAiReferralRows(input) {
1273
1288
  ...evidenceReasons("Source discovery", input.sourceDiscovery),
1274
1289
  ...evidenceReasons("Landing-page detail", input.detail),
1275
1290
  ...evidenceReasons("Total users", input.totalUsersEvidence),
1276
- ...detailMismatch ? ["GA4 detail totals differ from source-discovery totals; landing-page and daily breakdowns are incomplete."] : [],
1277
- ...timeZones.size > 1 ? ["GA4 queries returned inconsistent property time zones."] : []
1291
+ ...detailMismatch ? ["Google Analytics detail totals differ from source-discovery totals; landing-page and daily breakdowns are incomplete."] : [],
1292
+ ...timeZones.size > 1 ? ["Google Analytics queries returned inconsistent property time zones."] : []
1278
1293
  ];
1279
1294
  const dataStatus = partialReasons.length ? "partial" : "complete";
1280
- const generalCaveat = "GA4 only attributes visits that arrive with a recognized session source; some AI visits appear as direct, unassigned, or another source.";
1295
+ const generalCaveat = "Google Analytics only attributes visits that arrive with a recognized session source; some AI visits appear as direct, unassigned, or another source.";
1281
1296
  const possiblyTruncated = input.sourceDiscovery.truncated || input.detail.truncated || input.totalUsersEvidence.truncated;
1282
1297
  const allLandingPages = [...pageTotals.entries()].map(([landingPage, metrics]) => {
1283
1298
  const topSourceId = [...metrics.sources.entries()].sort((left, right) => right[1] - left[1] || compareText$16(left[0], right[0]))[0]?.[0];
@@ -1309,13 +1324,13 @@ function analyzeAiReferralRows(input) {
1309
1324
  dataStatus,
1310
1325
  range: input.range,
1311
1326
  methodology: {
1312
- id: "ga4-ai-referrals",
1327
+ id: "google-analytics-ai-referrals",
1313
1328
  version: 2,
1314
1329
  attributionDimension: "sessionSource",
1315
1330
  sourceRulesVersion: AI_REFERRAL_SOURCES_VERSION
1316
1331
  },
1317
1332
  dataSource: {
1318
- provider: "google-analytics-4",
1333
+ provider: "google-analytics",
1319
1334
  api: "analyticsdata.v1beta.runReport",
1320
1335
  maxRows: input.maxRows,
1321
1336
  calls: input.sourceDiscovery.calls + input.detail.calls + input.totalUsersEvidence.calls,
@@ -1332,7 +1347,7 @@ function analyzeAiReferralRows(input) {
1332
1347
  totalUsersStatus: input.totalUsers === null ? "not-reported" : "available",
1333
1348
  sources: sourceTotals.size,
1334
1349
  landingPages: pageTotals.size,
1335
- verdict: total.sessions ? dataStatus === "complete" ? "Detected AI referral traffic in GA4." : "Detected AI referral traffic; partial counts and breakdowns are lower bounds." : dataStatus === "complete" ? "No sessions attributed to the known AI session-source rules were detected." : "No AI-attributed sessions were found in retained GA4 data; this partial report cannot establish absence.",
1350
+ verdict: total.sessions ? dataStatus === "complete" ? "Detected AI referral traffic in Google Analytics." : "Detected AI referral traffic; partial counts and breakdowns are lower bounds." : dataStatus === "complete" ? "No sessions attributed to the known AI session-source rules were detected." : "No AI-attributed sessions were found in retained Google Analytics data; this partial report cannot establish absence.",
1336
1351
  caveat: generalCaveat
1337
1352
  },
1338
1353
  sources: [...sourceTotals.values()].map((source) => ({
@@ -1354,7 +1369,7 @@ function analyzeAiReferralRows(input) {
1354
1369
  })).sort((left, right) => compareText$16(left.date, right.date)),
1355
1370
  caveats: [
1356
1371
  generalCaveat,
1357
- "Attribution uses GA4 sessionSource only; landing-page text, medium text, and event referrers are never treated as referral sources.",
1372
+ "Attribution uses Google Analytics sessionSource only; landing-page text, medium text, and event referrers are never treated as referral sources.",
1358
1373
  "Source totals come from a low-cardinality source query; landing-page and daily detail is queried separately for the exact observed source values.",
1359
1374
  "Total users come from a separate filtered aggregate when AI sources are observed and are never summed across report rows; complete zero-session evidence implies zero users without another query.",
1360
1375
  ...selection.omittedRows > 0 ? [`Landing-page output returns the ${selection.returnedRows} highest-session pages from ${selection.retainedRows} retained detail rows; ${selection.omittedRows} lower-ranked pages are omitted. Increase resultLimit to inspect more.`] : [],
@@ -1456,7 +1471,7 @@ function reportRange(input) {
1456
1471
  const endDate = input.endDate ?? "yesterday";
1457
1472
  const absolute = validIsoDate$1(startDate) && validIsoDate$1(endDate);
1458
1473
  const relative = validRelativeDate(startDate) && validRelativeDate(endDate);
1459
- if (!absolute && !relative) throw new SeoError("INVALID_INPUT", "startDate and endDate must both be YYYY-MM-DD dates or GA4 relative dates such as 28daysAgo and yesterday.");
1474
+ if (!absolute && !relative) throw new SeoError("INVALID_INPUT", "startDate and endDate must both be YYYY-MM-DD dates or Google Analytics relative dates such as 28daysAgo and yesterday.");
1460
1475
  if (absolute && startDate > endDate) throw new SeoError("INVALID_INPUT", "startDate must be on or before endDate.");
1461
1476
  return {
1462
1477
  startDate,
@@ -1515,7 +1530,7 @@ async function aiReferralsReport(input, dependencies = {}) {
1515
1530
  const property = input.property.trim();
1516
1531
  if (!property) throw new SeoError("INVALID_INPUT", "property is required.");
1517
1532
  const range = reportRange(input);
1518
- const ga4Range = {
1533
+ const googleAnalyticsDateRange = {
1519
1534
  startDate: range.startDate,
1520
1535
  endDate: range.endDate
1521
1536
  };
@@ -1525,7 +1540,7 @@ async function aiReferralsReport(input, dependencies = {}) {
1525
1540
  const sourceResult = await fetchAiReferralRows({
1526
1541
  property,
1527
1542
  request: {
1528
- dateRanges: [ga4Range],
1543
+ dateRanges: [googleAnalyticsDateRange],
1529
1544
  dimensions: [{ name: "sessionSource" }],
1530
1545
  metrics: [{ name: "sessions" }, { name: "eventCount" }],
1531
1546
  orderBys: [{
@@ -1550,7 +1565,7 @@ async function aiReferralsReport(input, dependencies = {}) {
1550
1565
  detailResult = await fetchAiReferralRows({
1551
1566
  property,
1552
1567
  request: {
1553
- dateRanges: [ga4Range],
1568
+ dateRanges: [googleAnalyticsDateRange],
1554
1569
  dimensions: [
1555
1570
  { name: "date" },
1556
1571
  { name: "sessionSource" },
@@ -1586,7 +1601,7 @@ async function aiReferralsReport(input, dependencies = {}) {
1586
1601
  usersResult = await fetchAiReferralRows({
1587
1602
  property,
1588
1603
  request: {
1589
- dateRanges: [ga4Range],
1604
+ dateRanges: [googleAnalyticsDateRange],
1590
1605
  metrics: [{ name: "totalUsers" }],
1591
1606
  dimensionFilter
1592
1607
  },
@@ -9414,7 +9429,7 @@ function crawlDefinitionId(input) {
9414
9429
  const normalized = {
9415
9430
  config: normalizeCrawlConfig(input.config),
9416
9431
  site: input.site ?? null,
9417
- ga4PropertyId: input.ga4PropertyId ?? null
9432
+ googleAnalyticsPropertyId: input.googleAnalyticsPropertyId ?? null
9418
9433
  };
9419
9434
  return `crawl_def_${createHash("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 20)}`;
9420
9435
  }
@@ -9617,7 +9632,7 @@ function createCrawlReport(input) {
9617
9632
  const definitionId = crawlDefinitionId({
9618
9633
  config,
9619
9634
  site: input.site,
9620
- ga4PropertyId: input.ga4PropertyId
9635
+ googleAnalyticsPropertyId: input.googleAnalyticsPropertyId
9621
9636
  });
9622
9637
  const safePagesWithLinks = sortPages(sanitizePages(deriveInternalLinkAuthority(input.pages ?? [], input.linkGraph)));
9623
9638
  const requests = sortRequests(sanitizeTenantValue(input.requests ?? []));
@@ -9630,7 +9645,7 @@ function createCrawlReport(input) {
9630
9645
  definitionId,
9631
9646
  projectId: input.projectId,
9632
9647
  site: input.site,
9633
- ga4PropertyId: input.ga4PropertyId,
9648
+ googleAnalyticsPropertyId: input.googleAnalyticsPropertyId,
9634
9649
  generatedAt: input.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
9635
9650
  status: input.status ?? "completed",
9636
9651
  configHash: crawlConfigHash(config),
@@ -9668,7 +9683,7 @@ function normalizeLoadedCrawlReport(report) {
9668
9683
  definitionId: report.definitionId ?? crawlDefinitionId({
9669
9684
  config: report.config,
9670
9685
  site: report.site,
9671
- ga4PropertyId: report.ga4PropertyId
9686
+ googleAnalyticsPropertyId: report.googleAnalyticsPropertyId
9672
9687
  }),
9673
9688
  summary: summarizeCrawlReport({
9674
9689
  pages,
@@ -10074,7 +10089,7 @@ async function joinAnalytics(input) {
10074
10089
  limit: input.limit
10075
10090
  });
10076
10091
  } catch (error) {
10077
- const warning = `GA4 metrics unavailable: ${error instanceof Error ? error.message : String(error)}`;
10092
+ const warning = `Google Analytics metrics unavailable: ${error instanceof Error ? error.message : String(error)}`;
10078
10093
  input.warnings.push(warning);
10079
10094
  return {
10080
10095
  status: "unavailable",
@@ -10087,7 +10102,7 @@ async function joinAnalytics(input) {
10087
10102
  };
10088
10103
  }
10089
10104
  if (analytics.warning) {
10090
- const warning = `GA4 metrics unavailable: ${analytics.warning}`;
10105
+ const warning = `Google Analytics metrics unavailable: ${analytics.warning}`;
10091
10106
  input.warnings.push(warning);
10092
10107
  return {
10093
10108
  status: "unavailable",
@@ -10112,7 +10127,7 @@ async function joinAnalytics(input) {
10112
10127
  const sourceQualityKnown = analytics.source?.dataStatus !== void 0;
10113
10128
  const retainedRowLimitReached = analytics.source?.retainedRowLimitReached;
10114
10129
  const qualityWarnings = analytics.source?.qualityWarnings ?? [];
10115
- const warning = !sourceQualityKnown ? `GA4 metrics joined for ${joinedPages} of ${input.pages.length} crawled pages, but the provider did not expose row completeness; missing page metrics are not reliable zero-traffic evidence.` : retainedRowLimitReached ? `GA4 retained-row limit reached; missing page metrics are not reliable zero-traffic evidence.` : qualityWarnings.length ? `${qualityWarnings.join(" ")} Missing page metrics and relative analytics value are partial evidence.` : input.pages.length && joinedPages === 0 ? "GA4 metrics joined for 0 crawled pages." : void 0;
10130
+ const warning = !sourceQualityKnown ? `Google Analytics metrics joined for ${joinedPages} of ${input.pages.length} crawled pages, but the provider did not expose row completeness; missing page metrics are not reliable zero-traffic evidence.` : retainedRowLimitReached ? `Google Analytics retained-row limit reached; missing page metrics are not reliable zero-traffic evidence.` : qualityWarnings.length ? `${qualityWarnings.join(" ")} Missing page metrics and relative analytics value are partial evidence.` : input.pages.length && joinedPages === 0 ? "Google Analytics metrics joined for 0 crawled pages." : void 0;
10116
10131
  if (warning) input.warnings.push(warning);
10117
10132
  return {
10118
10133
  status: !sourceQualityKnown || analytics.source?.dataStatus === "partial" ? "partial" : joinedPages ? "joined" : "none",
@@ -10272,7 +10287,7 @@ async function crawlDataSources(input) {
10272
10287
  joinedPages: 0,
10273
10288
  retainedRowLimit: input.analyticsLimit,
10274
10289
  retainedRowLimitReached: false,
10275
- warning: input.cancelled ? "GA4 join skipped because the crawl was cancelled." : "GA4 join skipped because no property was selected."
10290
+ warning: input.cancelled ? "Google Analytics join skipped because the crawl was cancelled." : "Google Analytics join skipped because no property was selected."
10276
10291
  }
10277
10292
  };
10278
10293
  if (!input.cancelled && input.site) dataSources.searchConsole = await joinSearchMetrics({
@@ -10288,8 +10303,8 @@ async function crawlDataSources(input) {
10288
10303
  queryPagesMetricsBatch: input.queryPagesMetricsBatch,
10289
10304
  queryPagesTopQueriesBatch: input.queryPagesTopQueriesBatch
10290
10305
  });
10291
- if (!input.cancelled && input.ga4PropertyId) dataSources.analytics = await joinAnalytics({
10292
- propertyId: input.ga4PropertyId,
10306
+ if (!input.cancelled && input.googleAnalyticsPropertyId) dataSources.analytics = await joinAnalytics({
10307
+ propertyId: input.googleAnalyticsPropertyId,
10293
10308
  pages: input.pages,
10294
10309
  warnings: input.warnings,
10295
10310
  limit: input.analyticsLimit,
@@ -11111,11 +11126,11 @@ function landingPageRankingPolicy(input) {
11111
11126
  };
11112
11127
  const warningSuffix = "Observed landing-page values remain visible but do not affect priority scores.";
11113
11128
  const warnings = [
11114
- ...input.warning ? [`GA4: ${input.warning}`] : [],
11115
- ...input.source?.retainedRowLimitReached ? [`GA4: the retained-row limit was reached. ${warningSuffix}`] : [],
11129
+ ...input.warning ? [`Google Analytics: ${input.warning}`] : [],
11130
+ ...input.source?.retainedRowLimitReached ? [`Google Analytics: the retained-row limit was reached. ${warningSuffix}`] : [],
11116
11131
  ...input.source?.qualityWarnings?.map((warning) => `${warning} ${warningSuffix}`) ?? []
11117
11132
  ];
11118
- if (!input.source?.dataStatus && !input.warning) warnings.push(`GA4: landing-page completeness was not reported. ${warningSuffix}`);
11133
+ if (!input.source?.dataStatus && !input.warning) warnings.push(`Google Analytics: landing-page completeness was not reported. ${warningSuffix}`);
11119
11134
  return {
11120
11135
  canRank: !input.warning && warnings.length === 0 && landingPageValuesCanRank(input.source),
11121
11136
  warnings
@@ -11155,7 +11170,7 @@ async function fetchLandingPageValues(input, dependencies = defaultDependencies$
11155
11170
  const values = landingPageValuesFromRows(rows);
11156
11171
  const availableRows = result.rowCount;
11157
11172
  const retainedRowLimitReached = availableRows !== void 0 && availableRows > rows.length || rows.length >= retainedRowLimit;
11158
- const qualityWarnings = ga4ReportQualityWarnings(result, "GA4 landing-page report");
11173
+ const qualityWarnings = ga4ReportQualityWarnings(result, "Google Analytics landing-page report");
11159
11174
  return {
11160
11175
  values,
11161
11176
  source: {
@@ -11909,7 +11924,7 @@ async function crawlSite(input, dependencies) {
11909
11924
  const dataSources = await crawlDataSources({
11910
11925
  cancelled,
11911
11926
  site,
11912
- ga4PropertyId: input.ga4PropertyId,
11927
+ googleAnalyticsPropertyId: input.googleAnalyticsPropertyId,
11913
11928
  pages,
11914
11929
  warnings,
11915
11930
  searchMetricsLimit,
@@ -11964,7 +11979,7 @@ async function crawlSite(input, dependencies) {
11964
11979
  config,
11965
11980
  projectId: input.projectId,
11966
11981
  site,
11967
- ga4PropertyId: input.ga4PropertyId,
11982
+ googleAnalyticsPropertyId: input.googleAnalyticsPropertyId,
11968
11983
  dataSources,
11969
11984
  pages,
11970
11985
  requests,
@@ -12107,7 +12122,7 @@ function searchValueForGroup(report, urls) {
12107
12122
  };
12108
12123
  }
12109
12124
  function whyThisRanks(input, recommendation, searchStatus) {
12110
- return `${input.searchVisibleUrls ? `${input.searchVisibleUrls} affected URLs have GSC visibility (${input.clicks} clicks, ${input.impressions} impressions).` : searchStatus && !["joined", "none"].includes(searchStatus) ? `No joined GSC visibility is available for these affected URLs; Search Console evidence is ${searchStatus}.` : "No affected URL has joined GSC visibility yet."}${input.sessions ? ` GA4 adds ${input.sessions} sessions and ${input.conversions} conversions from affected landing pages.` : ""} ${recommendation === "review" ? "This is a review observation. Confirm it before scheduling implementation work." : `This ranks as a fix. Severity is ${input.severity}; ${input.affectedUrls} URL${input.affectedUrls === 1 ? " is" : "s are"} affected; estimated effort is ${input.effort}.`}`;
12125
+ return `${input.searchVisibleUrls ? `${input.searchVisibleUrls} affected URLs have GSC visibility (${input.clicks} clicks, ${input.impressions} impressions).` : searchStatus && !["joined", "none"].includes(searchStatus) ? `No joined GSC visibility is available for these affected URLs; Search Console evidence is ${searchStatus}.` : "No affected URL has joined GSC visibility yet."}${input.sessions ? ` Google Analytics adds ${input.sessions} sessions and ${input.conversions} conversions from affected landing pages.` : ""} ${recommendation === "review" ? "This is a review observation. Confirm it before scheduling implementation work." : `This ranks as a fix. Severity is ${input.severity}; ${input.affectedUrls} URL${input.affectedUrls === 1 ? " is" : "s are"} affected; estimated effort is ${input.effort}.`}`;
12111
12126
  }
12112
12127
  function verificationCommand(report, severity) {
12113
12128
  return `seo crawl ${report.config.url} --severity ${severity} --max-pages ${Math.min(report.config.maxPages, 100)}`;
@@ -12223,7 +12238,7 @@ function snapshotInput(report) {
12223
12238
  generatedAt: report.generatedAt,
12224
12239
  projectId: report.projectId,
12225
12240
  site: report.site,
12226
- ga4PropertyId: report.ga4PropertyId,
12241
+ googleAnalyticsPropertyId: report.googleAnalyticsPropertyId,
12227
12242
  url: report.config.url,
12228
12243
  status: report.status,
12229
12244
  configHash: report.configHash,
@@ -13861,7 +13876,7 @@ const crawlReportBaseSchema = z.object({
13861
13876
  definitionId: z.string(),
13862
13877
  projectId: z.string().optional(),
13863
13878
  site: z.string().optional(),
13864
- ga4PropertyId: z.string().optional(),
13879
+ googleAnalyticsPropertyId: z.string().optional(),
13865
13880
  generatedAt: z.string().datetime(),
13866
13881
  status: z.enum([
13867
13882
  "completed",
@@ -17727,7 +17742,7 @@ function stringMatchType(matchType) {
17727
17742
  if (matchType === "equals") return "EXACT";
17728
17743
  return "CONTAINS";
17729
17744
  }
17730
- function ga4LandingPageFilterForChange(change, group) {
17745
+ function googleAnalyticsLandingPageFilterForChange(change, group) {
17731
17746
  if (change.scope === "site") return void 0;
17732
17747
  if (change.scope === "query") return void 0;
17733
17748
  if (change.scope === "group") {
@@ -17750,7 +17765,7 @@ function ga4LandingPageFilterForChange(change, group) {
17750
17765
  }
17751
17766
  } };
17752
17767
  }
17753
- function summarizeGa4Rows(rows) {
17768
+ function summarizeGoogleAnalyticsRows(rows) {
17754
17769
  return rows.reduce((sum, row) => ({
17755
17770
  sessions: fixed(sum.sessions + (Number(row.sessions) || 0)),
17756
17771
  engagedSessions: fixed(sum.engagedSessions + (Number(row.engagedSessions) || 0)),
@@ -17817,8 +17832,8 @@ function classify(input) {
17817
17832
  }
17818
17833
  //#endregion
17819
17834
  //#region packages/core/dist/analyze/experiments/measurement/ga4-change-provider.js
17820
- async function queryGa4Metrics(input) {
17821
- const result = await input.ga4Report(input.propertyId, {
17835
+ async function queryGoogleAnalyticsMetrics(input) {
17836
+ const result = await input.googleAnalyticsReport(input.propertyId, {
17822
17837
  dateRanges: [input.window],
17823
17838
  dimensions: [{ name: "date" }],
17824
17839
  metrics: [
@@ -17832,7 +17847,7 @@ async function queryGa4Metrics(input) {
17832
17847
  }, { refresh: input.refresh });
17833
17848
  const rows = ga4RowsToObjects(result);
17834
17849
  return {
17835
- metrics: summarizeGa4Rows(rows),
17850
+ metrics: summarizeGoogleAnalyticsRows(rows),
17836
17851
  source: {
17837
17852
  rows: rows.length,
17838
17853
  rowCount: result.rowCount ?? rows.length,
@@ -17842,19 +17857,19 @@ async function queryGa4Metrics(input) {
17842
17857
  warnings: ga4ReportQualityWarnings(result, input.label)
17843
17858
  };
17844
17859
  }
17845
- async function queryGa4ChangeWindows(input) {
17846
- const [before, after] = await Promise.all([queryGa4Metrics({
17860
+ async function queryGoogleAnalyticsChangeWindows(input) {
17861
+ const [before, after] = await Promise.all([queryGoogleAnalyticsMetrics({
17847
17862
  ...input,
17848
17863
  window: input.before,
17849
- label: "GA4 before window"
17850
- }), queryGa4Metrics({
17864
+ label: "Google Analytics before window"
17865
+ }), queryGoogleAnalyticsMetrics({
17851
17866
  ...input,
17852
17867
  window: input.after,
17853
- label: "GA4 after window"
17868
+ label: "Google Analytics after window"
17854
17869
  })]);
17855
17870
  const metadataWarnings = [];
17856
- if (before.source.timeZone && after.source.timeZone && before.source.timeZone !== after.source.timeZone) metadataWarnings.push("GA4 returned different property timezones across the two windows.");
17857
- if (before.source.currencyCode && after.source.currencyCode && before.source.currencyCode !== after.source.currencyCode) metadataWarnings.push("GA4 returned different currency codes across the two windows.");
17871
+ if (before.source.timeZone && after.source.timeZone && before.source.timeZone !== after.source.timeZone) metadataWarnings.push("Google Analytics returned different property timezones across the two windows.");
17872
+ if (before.source.currencyCode && after.source.currencyCode && before.source.currencyCode !== after.source.currencyCode) metadataWarnings.push("Google Analytics returned different currency codes across the two windows.");
17858
17873
  return {
17859
17874
  report: {
17860
17875
  propertyId: input.propertyId,
@@ -17870,7 +17885,7 @@ async function queryGa4ChangeWindows(input) {
17870
17885
  before: before.metrics,
17871
17886
  after: after.metrics
17872
17887
  }),
17873
- note: "GA4 attribution is landing-page based. Query-level tests use GSC only."
17888
+ note: "Google Analytics attribution is landing-page based. Query-level tests use GSC only."
17874
17889
  },
17875
17890
  source: {
17876
17891
  before: before.source,
@@ -18108,7 +18123,7 @@ function measurementWindow(input) {
18108
18123
  //#region packages/core/dist/analyze/experiments/measure-change.js
18109
18124
  const defaultDependencies$5 = {
18110
18125
  searchAnalytics: querySearchAnalytics,
18111
- ga4Report: runGa4Report,
18126
+ googleAnalyticsReport: runGa4Report,
18112
18127
  contentGroup: getContentGroup,
18113
18128
  now: () => /* @__PURE__ */ new Date()
18114
18129
  };
@@ -18194,15 +18209,15 @@ async function measureChange(input, dependencies = defaultDependencies$5) {
18194
18209
  confidence: "medium",
18195
18210
  note: `${classified.note} Confidence is capped because Search Console evidence is partial.`
18196
18211
  } : classified;
18197
- const ga4Filter = ga4LandingPageFilterForChange(change, group);
18212
+ const googleAnalyticsFilter = googleAnalyticsLandingPageFilterForChange(change, group);
18198
18213
  const queryDimension = change.scope === "query" || group?.dimension === "query";
18199
- const analytics = input.ga4PropertyId && !queryDimension ? await queryGa4ChangeWindows({
18200
- propertyId: input.ga4PropertyId,
18214
+ const analytics = input.googleAnalyticsPropertyId && !queryDimension ? await queryGoogleAnalyticsChangeWindows({
18215
+ propertyId: input.googleAnalyticsPropertyId,
18201
18216
  before,
18202
18217
  after,
18203
- filter: ga4Filter,
18218
+ filter: googleAnalyticsFilter,
18204
18219
  refresh: input.refresh,
18205
- ga4Report: dependencies.ga4Report
18220
+ googleAnalyticsReport: dependencies.googleAnalyticsReport
18206
18221
  }) : void 0;
18207
18222
  const warnings = [
18208
18223
  ...measured.warnings,
@@ -18281,7 +18296,7 @@ async function measureChange(input, dependencies = defaultDependencies$5) {
18281
18296
  ...window.afterWindowTruncated ? [`The requested ${window.requestedDays}-day after window is incomplete, so this run is partial and has no directional verdict.`] : [],
18282
18297
  ...shortWindow ? ["Fewer than 7 finalized days per window is too short for a directional verdict."] : [],
18283
18298
  ...measured.completeness === "retained-query-date-aggregates" ? ["Query-scoped Search Console data can omit anonymized queries; an absent retained row is not zero traffic."] : [],
18284
- ...analytics ? [`GA4 attribution uses landing pages and the GA4 property timezone${analytics.source.before.timeZone ? ` (${analytics.source.before.timeZone})` : ""}; its day boundaries may differ from Search Console Pacific dates.`] : []
18299
+ ...analytics ? [`Google Analytics attribution uses landing pages and the Google Analytics property timezone${analytics.source.before.timeZone ? ` (${analytics.source.before.timeZone})` : ""}; its day boundaries may differ from Search Console Pacific dates.`] : []
18285
18300
  ]
18286
18301
  };
18287
18302
  }
@@ -23866,7 +23881,7 @@ function reportCaveats(input) {
23866
23881
  `Brand queries: ${input.includeBrand ? "included" : input.brandTerms?.length ? "excluded where saved brand terms matched" : "no saved brand terms, so no brand filter was applied"}.`,
23867
23882
  `Data freshness: ${input.refresh ? "fresh fetch requested; local cache bypassed where supported" : "local cache allowed; rerun with --refresh to bypass cached GSC/HTTP data"}.`,
23868
23883
  input.skipSearchData ? "Search Console: skipped because no property was selected. Run `seo start` to add performance evidence." : "Search Console: included where the selected property returned usable data.",
23869
- "GA4: not included in this narrative; use refresh-priorities when GA4 value should influence prioritisation.",
23884
+ "Google Analytics: not included in this narrative; use refresh-priorities when Google Analytics value should influence prioritisation.",
23870
23885
  input.skipSearchData ? "Content verification: not run because Search Console did not provide page candidates for this report." : input.verifyContent ? `Content verification: checked ${input.verified} of ${countLabel(input.quickWinCount, "quick-win candidate")}, limit ${input.verifyLimit ?? 3}.` : "Content verification: not run; recommendations are based on GSC/query data unless stated otherwise."
23871
23886
  ];
23872
23887
  }
@@ -24988,12 +25003,12 @@ async function refreshPrioritiesWorkflow(input) {
24988
25003
  });
24989
25004
  const range = defaultDateRange$1(input.days ?? 28);
24990
25005
  const analytics = await fetchLandingPageValues({
24991
- propertyId: input.ga4PropertyId,
25006
+ propertyId: input.googleAnalyticsPropertyId,
24992
25007
  startDate: range.startDate,
24993
25008
  endDate: range.endDate
24994
25009
  });
24995
25010
  const analyticsPolicy = landingPageRankingPolicy({
24996
- propertyId: input.ga4PropertyId,
25011
+ propertyId: input.googleAnalyticsPropertyId,
24997
25012
  source: analytics.source,
24998
25013
  warning: analytics.warning
24999
25014
  });
@@ -25590,7 +25605,7 @@ async function runDoctor() {
25590
25605
  const grantedScopes = new Set(tokens?.scope.split(/\s+/).filter(Boolean) ?? []);
25591
25606
  for (const scope of REQUIRED_SCOPES) checks.push({
25592
25607
  id: `scope:${scope}`,
25593
- label: scope.endsWith("webmasters.readonly") ? "GSC scope" : "GA4 scope",
25608
+ label: scope.endsWith("webmasters.readonly") ? "Search Console scope" : "Google Analytics scope",
25594
25609
  status: serviceAccount.configured || tokens && grantedScopes.has(scope) ? "pass" : tokens ? "fail" : "warn",
25595
25610
  detail: serviceAccount.configured ? `${scope} requested by the service account.` : tokens ? grantedScopes.has(scope) ? scope : `Missing ${scope}.` : "Cannot inspect scopes until login is complete.",
25596
25611
  fix: !serviceAccount.configured && tokens && !grantedScopes.has(scope) ? "Run `seo auth logout` then `seo auth login`." : void 0
@@ -25602,10 +25617,10 @@ async function runDoctor() {
25602
25617
  detail: config.defaultSite ?? "No saved default. Human CLI commands will prompt; agents can pass --site."
25603
25618
  });
25604
25619
  checks.push({
25605
- id: "default-ga4",
25606
- label: "Saved GA4 property",
25620
+ id: "default-google-analytics",
25621
+ label: "Saved Google Analytics property",
25607
25622
  status: "pass",
25608
- detail: config.google.defaultGa4PropertyId ?? "No saved default. Human CLI commands will prompt; agents can pass --property."
25623
+ detail: config.analytics.google.defaultPropertyId ?? "No saved default. Human CLI commands will prompt; agents can pass --property."
25609
25624
  });
25610
25625
  return {
25611
25626
  ok: checks.every((check) => check.status !== "fail"),
@@ -25971,7 +25986,7 @@ function toolSuccess(summaryText, structuredContent, options = {}) {
25971
25986
  }
25972
25987
  //#endregion
25973
25988
  //#region packages/mcp/src/ai-opportunity-tools.ts
25974
- const ga4DateSchema = z$1.union([calendarDateSchema, z$1.string().regex(/^(?:today|yesterday|\d+daysAgo)$/)]).describe("Use YYYY-MM-DD or a GA4 relative date.");
25989
+ const googleAnalyticsDateSchema = z$1.union([calendarDateSchema, z$1.string().regex(/^(?:today|yesterday|\d+daysAgo)$/)]).describe("Use YYYY-MM-DD or a Google Analytics relative date.");
25975
25990
  const queryOpportunitySchema = {
25976
25991
  site: z$1.string().trim().min(1),
25977
25992
  days: z$1.number().int().min(1).max(548).optional(),
@@ -25986,12 +26001,12 @@ const queryOpportunitySchema = {
25986
26001
  };
25987
26002
  function registerAiOpportunityTools(server, dependencies = {}) {
25988
26003
  server.registerTool("seo_ai_referrals", {
25989
- description: "Find AI referral traffic detected in GA4. maxRows bounds retained provider rows; resultLimit bounds returned landing pages.",
26004
+ description: "Find AI referral traffic detected in Google Analytics. maxRows bounds retained provider rows; resultLimit bounds returned landing pages.",
25990
26005
  inputSchema: {
25991
26006
  property: z$1.string().trim().min(1),
25992
- startDate: ga4DateSchema.optional(),
25993
- endDate: ga4DateSchema.optional(),
25994
- maxRows: z$1.number().int().min(1).max(1e5).describe("Maximum retained GA4 rows. If limit is also supplied, both values must match.").optional(),
26007
+ startDate: googleAnalyticsDateSchema.optional(),
26008
+ endDate: googleAnalyticsDateSchema.optional(),
26009
+ maxRows: z$1.number().int().min(1).max(1e5).describe("Maximum retained Google Analytics rows. If limit is also supplied, both values must match.").optional(),
25995
26010
  limit: z$1.number().int().min(1).max(1e5).describe("Legacy alias for maxRows. If maxRows is also supplied, both values must match.").optional(),
25996
26011
  resultLimit: z$1.number().int().min(1).max(1e3).describe("Maximum ranked landing pages returned. Defaults to 25.").optional(),
25997
26012
  refresh: z$1.boolean().optional()
@@ -26209,7 +26224,7 @@ function registerCrawlerTools(server) {
26209
26224
  inputSchema: {
26210
26225
  url: z$1.string().url(),
26211
26226
  site: z$1.string().optional(),
26212
- ga4PropertyId: z$1.string().optional(),
26227
+ googleAnalyticsPropertyId: z$1.string().optional(),
26213
26228
  maxPages: z$1.number().int().positive().optional(),
26214
26229
  maxDepth: z$1.number().int().nonnegative().optional(),
26215
26230
  concurrency: z$1.number().int().positive().optional(),
@@ -26225,12 +26240,12 @@ function registerCrawlerTools(server) {
26225
26240
  includeIssues: z$1.boolean().optional(),
26226
26241
  saveReport: z$1.boolean().optional()
26227
26242
  }
26228
- }, async ({ url, site, ga4PropertyId, maxPages, maxDepth, concurrency, fetchIntervalCap, fetchIntervalMs, refresh, include, exclude, useSitemap, respectRobots, js, includePages, includeIssues, saveReport }) => {
26243
+ }, async ({ url, site, googleAnalyticsPropertyId, maxPages, maxDepth, concurrency, fetchIntervalCap, fetchIntervalMs, refresh, include, exclude, useSitemap, respectRobots, js, includePages, includeIssues, saveReport }) => {
26229
26244
  try {
26230
26245
  const report = await crawlSite({
26231
26246
  url,
26232
26247
  site,
26233
- ga4PropertyId,
26248
+ googleAnalyticsPropertyId,
26234
26249
  maxPages,
26235
26250
  maxDepth,
26236
26251
  concurrency,
@@ -26263,7 +26278,7 @@ function registerCrawlerTools(server) {
26263
26278
  inputSchema: {
26264
26279
  urls: z$1.array(z$1.string().url()).min(1),
26265
26280
  site: z$1.string().optional(),
26266
- ga4PropertyId: z$1.string().optional(),
26281
+ googleAnalyticsPropertyId: z$1.string().optional(),
26267
26282
  maxPages: z$1.number().int().positive().optional(),
26268
26283
  concurrency: z$1.number().int().positive().optional(),
26269
26284
  fetchIntervalCap: z$1.number().int().positive().optional(),
@@ -26273,14 +26288,14 @@ function registerCrawlerTools(server) {
26273
26288
  includeIssues: z$1.boolean().optional(),
26274
26289
  saveReport: z$1.boolean().optional()
26275
26290
  }
26276
- }, async ({ urls, site, ga4PropertyId, maxPages, concurrency, fetchIntervalCap, fetchIntervalMs, refresh, includePages, includeIssues, saveReport }) => {
26291
+ }, async ({ urls, site, googleAnalyticsPropertyId, maxPages, concurrency, fetchIntervalCap, fetchIntervalMs, refresh, includePages, includeIssues, saveReport }) => {
26277
26292
  try {
26278
26293
  const report = await crawlSite({
26279
26294
  url: urls[0] ?? "",
26280
26295
  urls,
26281
26296
  mode: "list",
26282
26297
  site,
26283
- ga4PropertyId,
26298
+ googleAnalyticsPropertyId,
26284
26299
  maxPages: maxPages ?? urls.length,
26285
26300
  concurrency,
26286
26301
  refresh,
@@ -27048,7 +27063,7 @@ function registerExperimentTools(server) {
27048
27063
  }
27049
27064
  });
27050
27065
  server.registerTool("seo_measure_change", {
27051
- description: "Measure a saved or ad hoc SEO change with equal finalized GSC windows and optional GA4/control evidence",
27066
+ description: "Measure a saved or ad hoc SEO change with equal finalized GSC windows and optional Google Analytics/control evidence",
27052
27067
  inputSchema: {
27053
27068
  id: z$1.string().min(1).optional(),
27054
27069
  site: z$1.string().min(1).optional(),
@@ -27061,7 +27076,7 @@ function registerExperimentTools(server) {
27061
27076
  target: z$1.string().min(1).optional(),
27062
27077
  title: z$1.string().min(1).optional(),
27063
27078
  changedAt: calendarDateSchema.optional(),
27064
- ga4PropertyId: z$1.string().min(1).optional(),
27079
+ googleAnalyticsPropertyId: z$1.string().min(1).optional(),
27065
27080
  controlScope: z$1.enum([
27066
27081
  "site",
27067
27082
  "page",
@@ -27074,7 +27089,7 @@ function registerExperimentTools(server) {
27074
27089
  afterDays: z$1.number().int().min(1).max(548).optional(),
27075
27090
  refresh: z$1.boolean().optional()
27076
27091
  }
27077
- }, async ({ id, site, scope, target, title, changedAt, ga4PropertyId, controlScope, controlTarget, controlTitle, beforeDays, afterDays, refresh }) => {
27092
+ }, async ({ id, site, scope, target, title, changedAt, googleAnalyticsPropertyId, controlScope, controlTarget, controlTitle, beforeDays, afterDays, refresh }) => {
27078
27093
  try {
27079
27094
  const result = await measureChange({
27080
27095
  id,
@@ -27083,7 +27098,7 @@ function registerExperimentTools(server) {
27083
27098
  target,
27084
27099
  title,
27085
27100
  changedAt,
27086
- ga4PropertyId,
27101
+ googleAnalyticsPropertyId,
27087
27102
  controlScope,
27088
27103
  controlTarget,
27089
27104
  controlTitle,
@@ -27737,7 +27752,7 @@ const REPORT_DEPTH = {
27737
27752
  "Referral sessions did not necessarily cause conversions and are not all AI traffic.",
27738
27753
  "totalUsers is not additive and must not be reconstructed by summing rows."
27739
27754
  ],
27740
- verify: "Compare complete like-for-like GA4 periods and check whether high-session landing pages answer the referring context.",
27755
+ verify: "Compare complete like-for-like Google Analytics periods and check whether high-session landing pages answer the referring context.",
27741
27756
  related: [{
27742
27757
  id: "seo-to-ai-query",
27743
27758
  reason: "Builds monitoring prompts."
@@ -27892,7 +27907,7 @@ const REPORT_DEPTH = {
27892
27907
  ],
27893
27908
  doNotClaim: [
27894
27909
  "An absent page from a capped, skipped, failed, or differently scoped crawl is not proven removed.",
27895
- "Source row caps limit joined GSC or GA4 evidence even when the document crawl completed.",
27910
+ "Source row caps limit joined GSC or Google Analytics evidence even when the document crawl completed.",
27896
27911
  "A change between snapshots is not proof the release caused it."
27897
27912
  ],
27898
27913
  verify: "Re-run the same definition and request scope after deployment and confirm the delta.",
@@ -28398,7 +28413,7 @@ const REPORT_DEPTH = {
28398
28413
  "dataStatus, window, source, warnings, and caveats",
28399
28414
  "window.effectiveDays versus requestedDays and afterWindowTruncated",
28400
28415
  "GSC before and after metrics",
28401
- "GA4 source.analytics.status and timezone and any control adjusted deltas"
28416
+ "Google Analytics source.analytics.status and timezone and any control adjusted deltas"
28402
28417
  ],
28403
28418
  doNotClaim: [
28404
28419
  "Timing does not prove the recorded change caused the movement.",
@@ -28716,8 +28731,8 @@ const REPORT_DEPTH = {
28716
28731
  "the top few URLs manually"
28717
28732
  ],
28718
28733
  doNotClaim: [
28719
- "A click decline, low CTR, second-page position, and GA4 session signal have different semantics and are not interchangeable.",
28720
- "Missing GA4 or failed fetches stay unavailable, not zero-weight proof.",
28734
+ "A click decline, low CTR, second-page position, and Google Analytics session signal have different semantics and are not interchangeable.",
28735
+ "Missing Google Analytics or failed fetches stay unavailable, not zero-weight proof.",
28721
28736
  "The score is not expected clicks, revenue, ranking lift, or causal confidence."
28722
28737
  ],
28723
28738
  verify: "Confirm query intent, current content, and internal links for the top URLs before choosing a bounded edit.",
@@ -28835,7 +28850,7 @@ const REPORT_DEPTH = {
28835
28850
  related: [
28836
28851
  {
28837
28852
  id: "ai-referrals",
28838
- reason: "Adds GA4 referral evidence."
28853
+ reason: "Adds Google Analytics referral evidence."
28839
28854
  },
28840
28855
  {
28841
28856
  id: "community-intent",
@@ -29082,9 +29097,9 @@ const REPORT_GUIDANCE = {
29082
29097
  },
29083
29098
  "ai-referrals": {
29084
29099
  name: "AI referral traffic",
29085
- description: "Find visits from known AI products in GA4 and see a ranked, bounded set of landing pages that received them.",
29086
- useWhen: ["You want measured AI referral traffic from GA4.", "You need the landing pages and sources behind those visits."],
29087
- avoidWhen: ["GA4 is not connected or does not track the site.", "You want to measure citations or unclicked mentions inside AI answers."],
29100
+ description: "Find visits from known AI products in Google Analytics and see a ranked, bounded set of landing pages that received them.",
29101
+ useWhen: ["You want measured AI referral traffic from Google Analytics.", "You need the landing pages and sources behind those visits."],
29102
+ avoidWhen: ["Google Analytics is not connected or does not track the site.", "You want to measure citations or unclicked mentions inside AI answers."],
29088
29103
  outcome: "A source and landing-page breakdown of identifiable AI referrals."
29089
29104
  },
29090
29105
  "ai-search-scorecard": {
@@ -29763,9 +29778,9 @@ function registerWorkflowTools(server) {
29763
29778
  "verifyLimit",
29764
29779
  "refresh"
29765
29780
  ]),
29766
- ga4PropertyId: z$1.string().optional()
29781
+ googleAnalyticsPropertyId: z$1.string().optional()
29767
29782
  }
29768
- }, async ({ site, days, recentDays, limit, includeBrand, ga4PropertyId, verifyContent, verifyLimit, refresh }) => {
29783
+ }, async ({ site, days, recentDays, limit, includeBrand, googleAnalyticsPropertyId, verifyContent, verifyLimit, refresh }) => {
29769
29784
  try {
29770
29785
  return workflowSuccess(await refreshPrioritiesWorkflow({
29771
29786
  site,
@@ -29773,7 +29788,7 @@ function registerWorkflowTools(server) {
29773
29788
  recentDays,
29774
29789
  limit,
29775
29790
  includeBrand,
29776
- ga4PropertyId,
29791
+ googleAnalyticsPropertyId,
29777
29792
  verifyContent,
29778
29793
  verifyLimit,
29779
29794
  refresh