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/PRIVACY.md +1 -1
- package/README.md +19 -11
- package/dist/cli.js +91 -92
- package/dist/cli.js.map +1 -1
- package/dist/{dist-BhdgeyGe.js → dist-oHPXlHac.js} +1 -1
- package/dist/{dist-DMSBrZYd.js → dist-oOrG0Vub.js} +69 -227
- package/dist/dist-oOrG0Vub.js.map +1 -0
- package/dist/index.d.ts +48 -32
- package/dist/index.js +187 -281
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +112 -97
- package/dist/mcp.js.map +1 -1
- package/dist/{package-D6bCOqrD.js → package-BF_XgK7-.js} +2 -2
- package/dist/{package-D6bCOqrD.js.map → package-BF_XgK7-.js.map} +1 -1
- package/evals/ai-referrals.json +7 -7
- package/evals/compare-crawls.json +2 -2
- package/evals/monthly-action-plan.json +1 -1
- package/evals/refresh-priorities.json +7 -7
- package/evals/seo-fix-queue.json +2 -2
- package/evals/seo-geo-readiness.json +1 -1
- package/evals/seo-to-ai-query.json +2 -2
- package/package.json +2 -2
- package/skills/seo/SKILL.md +7 -6
- package/skills/seo/agents/openai.yaml +1 -1
- package/dist/dist-DMSBrZYd.js.map +0 -1
package/dist/index.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-
|
|
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, createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { homedir } from "node:os";
|
|
@@ -222,6 +222,7 @@ const siteSchema = z.object({
|
|
|
222
222
|
isDefault: z.boolean().optional()
|
|
223
223
|
});
|
|
224
224
|
const providerPreferenceSchema = z.enum(["cheap", "authoritative"]);
|
|
225
|
+
const analyticsConnectionsSchema = z.object({ google: z.object({ propertyId: z.string() }).optional() }).default({});
|
|
225
226
|
const clientProfileSchema = z.object({
|
|
226
227
|
id: z.string(),
|
|
227
228
|
name: z.string(),
|
|
@@ -229,7 +230,7 @@ const clientProfileSchema = z.object({
|
|
|
229
230
|
startUrl: z.string().optional(),
|
|
230
231
|
watchUrls: z.array(z.string()).default([]),
|
|
231
232
|
brandTerms: z.array(z.string()).default([]),
|
|
232
|
-
|
|
233
|
+
analytics: analyticsConnectionsSchema,
|
|
233
234
|
reportDay: z.number().int().min(1).max(31).optional(),
|
|
234
235
|
technicalWeekday: z.number().int().min(0).max(7).optional(),
|
|
235
236
|
isDefault: z.boolean().optional(),
|
|
@@ -240,14 +241,14 @@ const configSchema = z.object({
|
|
|
240
241
|
defaultSite: z.string().optional(),
|
|
241
242
|
sites: z.array(siteSchema).default([]),
|
|
242
243
|
clients: z.array(clientProfileSchema).default([]),
|
|
243
|
-
google: z.object({
|
|
244
|
-
|
|
244
|
+
analytics: z.object({ google: z.object({
|
|
245
|
+
defaultPropertyId: z.string().optional(),
|
|
245
246
|
propertyMappings: z.array(z.object({
|
|
246
247
|
siteUrl: z.string(),
|
|
247
|
-
|
|
248
|
+
propertyId: z.string(),
|
|
248
249
|
addedAt: z.number().int().optional()
|
|
249
250
|
})).default([])
|
|
250
|
-
}).default({ propertyMappings: [] }),
|
|
251
|
+
}).default({ propertyMappings: [] }) }).default({ google: { propertyMappings: [] } }),
|
|
251
252
|
providers: z.object({
|
|
252
253
|
semrushApiKey: z.string().optional(),
|
|
253
254
|
dataForSeoLogin: z.string().optional(),
|
|
@@ -566,6 +567,14 @@ function missingOAuthClientMessage(source) {
|
|
|
566
567
|
//#endregion
|
|
567
568
|
//#region packages/core/src/gsc/auth/types.ts
|
|
568
569
|
const GOOGLE_READONLY_SCOPES = ["https://www.googleapis.com/auth/webmasters.readonly", "https://www.googleapis.com/auth/analytics.readonly"];
|
|
570
|
+
const GOOGLE_READONLY_SCOPE_LABELS = {
|
|
571
|
+
"https://www.googleapis.com/auth/webmasters.readonly": "Search Console",
|
|
572
|
+
"https://www.googleapis.com/auth/analytics.readonly": "Google Analytics"
|
|
573
|
+
};
|
|
574
|
+
function missingGoogleReadonlyScopes(scope) {
|
|
575
|
+
const granted = new Set(scope.split(/\s+/u).filter(Boolean));
|
|
576
|
+
return GOOGLE_READONLY_SCOPES.filter((required) => !granted.has(required));
|
|
577
|
+
}
|
|
569
578
|
const GOOGLE_SCOPE = [
|
|
570
579
|
...GOOGLE_READONLY_SCOPES,
|
|
571
580
|
"openid",
|
|
@@ -763,6 +772,11 @@ function authRequired() {
|
|
|
763
772
|
function authExpired() {
|
|
764
773
|
return new SeoError("AUTH_EXPIRED", "Google refresh token is no longer valid. Run `seo auth login` again.");
|
|
765
774
|
}
|
|
775
|
+
function requireReadonlyScopes(tokens) {
|
|
776
|
+
const missing = missingGoogleReadonlyScopes(tokens.scope);
|
|
777
|
+
if (missing.length === 0) return;
|
|
778
|
+
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.`);
|
|
779
|
+
}
|
|
766
780
|
function needsRefresh(tokens, now) {
|
|
767
781
|
return !tokens.access_token || tokens.expires_at - now < REFRESH_BUFFER_MS;
|
|
768
782
|
}
|
|
@@ -822,6 +836,7 @@ async function createAuthorizedClient() {
|
|
|
822
836
|
let tokens = await readTokens();
|
|
823
837
|
if (!tokens) throw authRequired();
|
|
824
838
|
requireClientConfig(tokens);
|
|
839
|
+
requireReadonlyScopes(tokens);
|
|
825
840
|
if (needsRefresh(tokens, Date.now())) tokens = await refreshStoredToken({ onlyIfExpiring: true });
|
|
826
841
|
return {
|
|
827
842
|
client: new LocalGoogleAccessTokenClient(tokens),
|
|
@@ -879,176 +894,36 @@ async function authStatus() {
|
|
|
879
894
|
}
|
|
880
895
|
//#endregion
|
|
881
896
|
//#region packages/core/src/gsc/auth/callback-page.ts
|
|
882
|
-
function
|
|
897
|
+
function callbackCopy(options) {
|
|
898
|
+
if (options.status === "connected") return {
|
|
899
|
+
title: "Google account connected",
|
|
900
|
+
heading: "Google account connected.",
|
|
901
|
+
detail: "This tab can be closed."
|
|
902
|
+
};
|
|
903
|
+
if (options.status === "permissions-missing") return {
|
|
904
|
+
title: "Google permissions not granted",
|
|
905
|
+
heading: "Google permissions were not granted.",
|
|
906
|
+
detail: `Return to your terminal and run seo auth login again. Select all permission boxes for ${options.missing.join(" and ")}.`
|
|
907
|
+
};
|
|
908
|
+
return {
|
|
909
|
+
title: "Google connection failed",
|
|
910
|
+
heading: "Google connection failed.",
|
|
911
|
+
detail: "Return to your terminal for the error and try again."
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function oauthCallbackPage(options = { status: "connected" }) {
|
|
915
|
+
const copy = callbackCopy(options);
|
|
883
916
|
return `<!doctype html>
|
|
884
917
|
<html lang="en">
|
|
885
918
|
<head>
|
|
886
919
|
<meta charset="utf-8">
|
|
887
920
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
888
|
-
<meta
|
|
889
|
-
<
|
|
890
|
-
<title>seo is connected</title>
|
|
891
|
-
<style>
|
|
892
|
-
:root {
|
|
893
|
-
color-scheme: light dark;
|
|
894
|
-
--background: #fafafa;
|
|
895
|
-
--background-raised: #ffffff;
|
|
896
|
-
--foreground: #343434;
|
|
897
|
-
--foreground-muted: #737373;
|
|
898
|
-
--border: #eaeaea;
|
|
899
|
-
--primary: #2589b6;
|
|
900
|
-
--primary-soft: #e9f6fb;
|
|
901
|
-
}
|
|
902
|
-
* { box-sizing: border-box; }
|
|
903
|
-
body {
|
|
904
|
-
background: var(--background);
|
|
905
|
-
color: var(--foreground);
|
|
906
|
-
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
907
|
-
margin: 0;
|
|
908
|
-
min-height: 100vh;
|
|
909
|
-
}
|
|
910
|
-
.frame {
|
|
911
|
-
margin: 0 auto;
|
|
912
|
-
max-width: 768px;
|
|
913
|
-
padding: 0 24px;
|
|
914
|
-
width: 100%;
|
|
915
|
-
}
|
|
916
|
-
header { border-bottom: 1px solid var(--border); }
|
|
917
|
-
.nav {
|
|
918
|
-
align-items: center;
|
|
919
|
-
display: flex;
|
|
920
|
-
gap: 8px;
|
|
921
|
-
min-height: 65px;
|
|
922
|
-
}
|
|
923
|
-
.brand {
|
|
924
|
-
align-items: center;
|
|
925
|
-
color: inherit;
|
|
926
|
-
display: inline-flex;
|
|
927
|
-
font-size: 16px;
|
|
928
|
-
font-weight: 700;
|
|
929
|
-
gap: 6px;
|
|
930
|
-
letter-spacing: -0.02em;
|
|
931
|
-
text-decoration: none;
|
|
932
|
-
}
|
|
933
|
-
.brand svg { height: 20px; width: 20px; }
|
|
934
|
-
main { min-height: calc(100vh - 65px); }
|
|
935
|
-
.hero {
|
|
936
|
-
border-bottom: 1px solid var(--border);
|
|
937
|
-
padding: clamp(72px, 15vw, 144px) 0;
|
|
938
|
-
}
|
|
939
|
-
.content { max-width: 620px; }
|
|
940
|
-
.status {
|
|
941
|
-
align-items: center;
|
|
942
|
-
color: var(--primary);
|
|
943
|
-
display: flex;
|
|
944
|
-
font-size: 15px;
|
|
945
|
-
font-weight: 650;
|
|
946
|
-
gap: 8px;
|
|
947
|
-
margin: 0 0 18px;
|
|
948
|
-
}
|
|
949
|
-
.status-mark {
|
|
950
|
-
background: var(--primary);
|
|
951
|
-
border-radius: 50%;
|
|
952
|
-
height: 8px;
|
|
953
|
-
width: 8px;
|
|
954
|
-
}
|
|
955
|
-
h1 {
|
|
956
|
-
font-size: clamp(38px, 7vw, 58px);
|
|
957
|
-
font-weight: 550;
|
|
958
|
-
letter-spacing: -0.055em;
|
|
959
|
-
line-height: 0.98;
|
|
960
|
-
margin: 0;
|
|
961
|
-
text-wrap: balance;
|
|
962
|
-
}
|
|
963
|
-
.lead {
|
|
964
|
-
color: var(--foreground-muted);
|
|
965
|
-
font-size: clamp(20px, 3vw, 24px);
|
|
966
|
-
font-weight: 500;
|
|
967
|
-
letter-spacing: -0.02em;
|
|
968
|
-
line-height: 1.35;
|
|
969
|
-
margin: 24px 0 0;
|
|
970
|
-
max-width: 600px;
|
|
971
|
-
}
|
|
972
|
-
code {
|
|
973
|
-
background: var(--background-raised);
|
|
974
|
-
border: 1px solid var(--border);
|
|
975
|
-
color: var(--foreground);
|
|
976
|
-
display: inline-block;
|
|
977
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
978
|
-
font-size: 0.84em;
|
|
979
|
-
letter-spacing: -0.02em;
|
|
980
|
-
padding: 3px 7px;
|
|
981
|
-
}
|
|
982
|
-
.details {
|
|
983
|
-
display: grid;
|
|
984
|
-
gap: 16px;
|
|
985
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
986
|
-
padding: 36px 0 48px;
|
|
987
|
-
}
|
|
988
|
-
.detail {
|
|
989
|
-
border-left: 1px solid var(--border);
|
|
990
|
-
padding-left: 16px;
|
|
991
|
-
}
|
|
992
|
-
.detail h2 {
|
|
993
|
-
font-size: 15px;
|
|
994
|
-
font-weight: 650;
|
|
995
|
-
letter-spacing: -0.01em;
|
|
996
|
-
margin: 0;
|
|
997
|
-
}
|
|
998
|
-
.detail p {
|
|
999
|
-
color: var(--foreground-muted);
|
|
1000
|
-
font-size: 15px;
|
|
1001
|
-
line-height: 1.5;
|
|
1002
|
-
margin: 6px 0 0;
|
|
1003
|
-
}
|
|
1004
|
-
@media (prefers-color-scheme: dark) {
|
|
1005
|
-
:root {
|
|
1006
|
-
--background: #343434;
|
|
1007
|
-
--background-raised: #3d3d3d;
|
|
1008
|
-
--foreground: #fafafa;
|
|
1009
|
-
--foreground-muted: #b8b8b8;
|
|
1010
|
-
--border: #484848;
|
|
1011
|
-
--primary: #8ed2ef;
|
|
1012
|
-
--primary-soft: #16485f;
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
@media (max-width: 560px) {
|
|
1016
|
-
.frame { padding: 0 20px; }
|
|
1017
|
-
.hero { padding: 72px 0; }
|
|
1018
|
-
.details { grid-template-columns: 1fr; }
|
|
1019
|
-
}
|
|
1020
|
-
</style>
|
|
921
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
|
|
922
|
+
<title>${copy.title}</title>
|
|
1021
923
|
</head>
|
|
1022
924
|
<body>
|
|
1023
|
-
<
|
|
1024
|
-
|
|
1025
|
-
<span class="brand">
|
|
1026
|
-
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
1027
|
-
<path fill="currentColor" d="M12,8c-2.2,0-4,1.8-4,4s1.8,4,4,4s4-1.8,4-4C16,9.8,14.2,8,12,8z M3,9L3,9c0.6,0,1-0.4,1-1V5c0-0.6,0.4-1,1-1h3 c0.6,0,1-0.4,1-1S8.6,2,8,2H5C3.3,2,2,3.3,2,5v3C2,8.6,2.4,9,3,9z M8,20H5c-0.6,0-1-0.4-1-1v-3c0-0.6-0.4-1-1-1s-1,0.4-1,1v3 c0,1.7,1.3,3,3,3h3c0.6,0,1-0.4,1-1S8.6,20,8,20z M21,15L21,15c-0.6,0-1,0.4-1,1v3c0,0.6-0.4,1-1,1h-3c-0.6,0-1,0.4-1,1s0.4,1,1,1h3 c1.7,0,3-1.3,3-3v-3C22,15.4,21.6,15,21,15z M19,2h-3c-0.6,0-1,0.4-1,1s0.4,1,1,1h3c0.6,0,1,0.4,1,1v3c0,0.6,0.4,1,1,1s1-0.4,1-1V5 C22,3.3,20.7,2,19,2z" />
|
|
1028
|
-
</svg>
|
|
1029
|
-
seo
|
|
1030
|
-
</span>
|
|
1031
|
-
</div>
|
|
1032
|
-
</header>
|
|
1033
|
-
<main>
|
|
1034
|
-
<section class="hero">
|
|
1035
|
-
<div class="frame content">
|
|
1036
|
-
<p class="status"><span class="status-mark" aria-hidden="true"></span>Google account connected</p>
|
|
1037
|
-
<h1>You can start your first SEO report.</h1>
|
|
1038
|
-
<p class="lead">Go back to your terminal to continue, and the CLI will help you choose a site and save a project profile before future reports.</p>
|
|
1039
|
-
</div>
|
|
1040
|
-
</section>
|
|
1041
|
-
<div class="frame details">
|
|
1042
|
-
<section class="detail">
|
|
1043
|
-
<h2>Safe to close</h2>
|
|
1044
|
-
<p>This tab has done its job. The CLI is waiting for you in the terminal.</p>
|
|
1045
|
-
</section>
|
|
1046
|
-
<section class="detail">
|
|
1047
|
-
<h2>Read-only access</h2>
|
|
1048
|
-
<p>The seo CLI can read the Google data you choose. It cannot change your site or Google account.</p>
|
|
1049
|
-
</section>
|
|
1050
|
-
</div>
|
|
1051
|
-
</main>
|
|
925
|
+
<p>${copy.heading}</p>
|
|
926
|
+
<p>${copy.detail}</p>
|
|
1052
927
|
</body>
|
|
1053
928
|
</html>`;
|
|
1054
929
|
}
|
|
@@ -1100,31 +975,50 @@ async function loginWithLoopback(clientConfig = getClientConfig()) {
|
|
|
1100
975
|
code_challenge_method: "S256"
|
|
1101
976
|
}).toString();
|
|
1102
977
|
await open(authUrl.toString());
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
978
|
+
let callback;
|
|
979
|
+
try {
|
|
980
|
+
callback = await waitForCode({
|
|
1106
981
|
server,
|
|
1107
982
|
redirectUri,
|
|
1108
983
|
state
|
|
1109
|
-
})
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
984
|
+
});
|
|
985
|
+
const tokenJson = await exchangeCode({
|
|
986
|
+
clientConfig,
|
|
987
|
+
code: callback.code,
|
|
988
|
+
redirectUri,
|
|
989
|
+
verifier
|
|
990
|
+
});
|
|
991
|
+
const missingScopes = missingGoogleReadonlyScopes(tokenJson.scope);
|
|
992
|
+
if (missingScopes.length > 0) {
|
|
993
|
+
const labels = missingScopes.map((scope) => GOOGLE_READONLY_SCOPE_LABELS[scope]);
|
|
994
|
+
callback.respond(400, oauthCallbackPage({
|
|
995
|
+
status: "permissions-missing",
|
|
996
|
+
missing: labels
|
|
997
|
+
}));
|
|
998
|
+
throw new SeoError("ACCESS_DENIED", `Google login did not grant ${labels.join(" and ")} read-only access. Run \`seo auth login\` again, choose Select all, and approve both permission boxes.`);
|
|
999
|
+
}
|
|
1000
|
+
const accountEmail = decodeJwtEmail(tokenJson.id_token) ?? await fetchUserEmail(tokenJson.access_token);
|
|
1001
|
+
if (!accountEmail) throw new Error("Google login succeeded but account email could not be determined.");
|
|
1002
|
+
const tokens = {
|
|
1003
|
+
provider: "google",
|
|
1004
|
+
account_email: accountEmail,
|
|
1005
|
+
scope: tokenJson.scope,
|
|
1006
|
+
token_type: tokenJson.token_type,
|
|
1007
|
+
access_token: tokenJson.access_token,
|
|
1008
|
+
refresh_token: tokenJson.refresh_token,
|
|
1009
|
+
expires_at: Date.now() + tokenJson.expires_in * 1e3,
|
|
1010
|
+
obtained_at: Date.now(),
|
|
1011
|
+
client_source: clientConfig.source
|
|
1012
|
+
};
|
|
1013
|
+
await writeTokens(tokens);
|
|
1014
|
+
callback.respond(200, oauthCallbackPage({ status: "connected" }));
|
|
1015
|
+
return tokens;
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
callback?.respond(400, oauthCallbackPage({ status: "failed" }));
|
|
1018
|
+
throw error;
|
|
1019
|
+
} finally {
|
|
1020
|
+
server.close();
|
|
1021
|
+
}
|
|
1128
1022
|
}
|
|
1129
1023
|
function waitForCode(input) {
|
|
1130
1024
|
return new Promise((resolve, reject) => {
|
|
@@ -1137,11 +1031,20 @@ function waitForCode(input) {
|
|
|
1137
1031
|
if (error) throw new Error(`OAuth error: ${error}`);
|
|
1138
1032
|
const incomingCode = reqUrl.searchParams.get("code");
|
|
1139
1033
|
if (!incomingCode) throw new Error("OAuth code missing.");
|
|
1140
|
-
|
|
1141
|
-
res.end(oauthCallbackPage());
|
|
1034
|
+
let responded = false;
|
|
1142
1035
|
clearTimeout(timer);
|
|
1143
|
-
resolve(
|
|
1036
|
+
resolve({
|
|
1037
|
+
code: incomingCode,
|
|
1038
|
+
respond: (status, page) => {
|
|
1039
|
+
if (responded) return;
|
|
1040
|
+
responded = true;
|
|
1041
|
+
res.writeHead(status, { "content-type": "text/html; charset=utf-8" });
|
|
1042
|
+
res.end(page);
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1144
1045
|
} catch (error) {
|
|
1046
|
+
res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
1047
|
+
res.end(oauthCallbackPage({ status: "failed" }));
|
|
1145
1048
|
clearTimeout(timer);
|
|
1146
1049
|
reject(error);
|
|
1147
1050
|
}
|
|
@@ -1463,7 +1366,7 @@ function getCacheStats() {
|
|
|
1463
1366
|
const counts = {
|
|
1464
1367
|
sites: database.prepare("SELECT COUNT(*) AS count FROM sites").get(),
|
|
1465
1368
|
gsc_cache: database.prepare("SELECT COUNT(*) AS count FROM gsc_cache").get(),
|
|
1466
|
-
|
|
1369
|
+
google_analytics_cache: database.prepare("SELECT COUNT(*) AS count FROM ga4_cache").get(),
|
|
1467
1370
|
semrush_cache: database.prepare("SELECT COUNT(*) AS count FROM semrush_cache").get(),
|
|
1468
1371
|
http_cache: database.prepare("SELECT COUNT(*) AS count FROM http_cache").get()
|
|
1469
1372
|
};
|
|
@@ -1476,7 +1379,7 @@ function getCacheStats() {
|
|
|
1476
1379
|
function clearCache(provider, olderThanMs) {
|
|
1477
1380
|
const database = getDb();
|
|
1478
1381
|
const cutoff = olderThanMs ? Date.now() - olderThanMs : void 0;
|
|
1479
|
-
const tables = provider === "gsc" ? ["gsc_cache"] : provider === "
|
|
1382
|
+
const tables = provider === "gsc" ? ["gsc_cache"] : provider === "google-analytics" ? ["ga4_cache"] : provider === "semrush" ? ["semrush_cache"] : provider === "http" ? ["http_cache"] : [
|
|
1480
1383
|
"gsc_cache",
|
|
1481
1384
|
"ga4_cache",
|
|
1482
1385
|
"semrush_cache",
|
|
@@ -1520,8 +1423,8 @@ async function runGa4Report(propertyId, body, opts = {}) {
|
|
|
1520
1423
|
body: JSON.stringify(body)
|
|
1521
1424
|
});
|
|
1522
1425
|
if (!response.ok) {
|
|
1523
|
-
if (response.status === 403) throw new Error(`
|
|
1524
|
-
throw new Error(`
|
|
1426
|
+
if (response.status === 403) throw new Error(`Google Analytics report failed with 403. Check access to property ${propertyId}.`);
|
|
1427
|
+
throw new Error(`Google Analytics report failed with ${response.status}.`);
|
|
1525
1428
|
}
|
|
1526
1429
|
const result = await response.json();
|
|
1527
1430
|
if (cacheable) db.prepare(`INSERT OR REPLACE INTO ga4_cache
|
|
@@ -1533,8 +1436,8 @@ async function listGa4AccountSummaries() {
|
|
|
1533
1436
|
const { client } = await createGoogleAccessTokenClient();
|
|
1534
1437
|
const response = await authedFetch$1(client, "https://analyticsadmin.googleapis.com/v1beta/accountSummaries");
|
|
1535
1438
|
if (!response.ok) {
|
|
1536
|
-
if (response.status === 403) throw new Error("
|
|
1537
|
-
throw new Error(`
|
|
1439
|
+
if (response.status === 403) throw new Error("Google Analytics account summary fetch failed with 403. Enable the Google Analytics Admin API and check Analytics access.");
|
|
1440
|
+
throw new Error(`Google Analytics account summary fetch failed with ${response.status}.`);
|
|
1538
1441
|
}
|
|
1539
1442
|
return (await response.json()).accountSummaries ?? [];
|
|
1540
1443
|
}
|
|
@@ -1544,14 +1447,14 @@ async function listGa4DataStreams(propertyId) {
|
|
|
1544
1447
|
let pageToken;
|
|
1545
1448
|
let pageCount = 0;
|
|
1546
1449
|
do {
|
|
1547
|
-
if (pageCount >= 20) throw new Error(`
|
|
1450
|
+
if (pageCount >= 20) throw new Error(`Google Analytics data stream discovery exceeded 20 pages for property ${propertyId}.`);
|
|
1548
1451
|
const url = new URL(`https://analyticsadmin.googleapis.com/v1beta/properties/${ga4PropertyIdFromName(propertyId)}/dataStreams`);
|
|
1549
1452
|
url.searchParams.set("pageSize", "200");
|
|
1550
1453
|
if (pageToken) url.searchParams.set("pageToken", pageToken);
|
|
1551
1454
|
const response = await authedFetch$1(client, url.toString());
|
|
1552
1455
|
if (!response.ok) {
|
|
1553
|
-
if (response.status === 403) throw new Error(`
|
|
1554
|
-
throw new Error(`
|
|
1456
|
+
if (response.status === 403) throw new Error(`Google Analytics data stream fetch failed with 403 for property ${propertyId}. Check Analytics access.`);
|
|
1457
|
+
throw new Error(`Google Analytics data stream fetch failed with ${response.status} for property ${propertyId}.`);
|
|
1555
1458
|
}
|
|
1556
1459
|
const json = await response.json();
|
|
1557
1460
|
streams.push(...json.dataStreams ?? []);
|
|
@@ -1577,7 +1480,7 @@ function ga4RowsToObjects(result) {
|
|
|
1577
1480
|
return output;
|
|
1578
1481
|
});
|
|
1579
1482
|
}
|
|
1580
|
-
function ga4ReportQualityWarnings(result, label = "
|
|
1483
|
+
function ga4ReportQualityWarnings(result, label = "Google Analytics report") {
|
|
1581
1484
|
const warnings = [];
|
|
1582
1485
|
if (result.metadata?.dataLossFromOtherRow) warnings.push(`${label} grouped high-cardinality data into (other).`);
|
|
1583
1486
|
if (result.metadata?.subjectToThresholding) warnings.push(`${label} was subject to Google Analytics thresholding.`);
|
|
@@ -1683,11 +1586,11 @@ function compareText$16(left, right) {
|
|
|
1683
1586
|
function metric(row, name) {
|
|
1684
1587
|
const value = row[name];
|
|
1685
1588
|
const parsed = Number(value);
|
|
1686
|
-
if (value === void 0 || value.trim() === "" || !Number.isInteger(parsed) || parsed < 0) throw new SeoError("PROVIDER_UNAVAILABLE", `
|
|
1589
|
+
if (value === void 0 || value.trim() === "" || !Number.isInteger(parsed) || parsed < 0) throw new SeoError("PROVIDER_UNAVAILABLE", `Google Analytics returned an invalid ${name} value.`);
|
|
1687
1590
|
return parsed;
|
|
1688
1591
|
}
|
|
1689
1592
|
function dateValue$1(value) {
|
|
1690
|
-
if (!value || !/^\d{8}$/.test(value)) throw new SeoError("PROVIDER_UNAVAILABLE", "
|
|
1593
|
+
if (!value || !/^\d{8}$/.test(value)) throw new SeoError("PROVIDER_UNAVAILABLE", "Google Analytics returned an invalid date value.");
|
|
1691
1594
|
return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`;
|
|
1692
1595
|
}
|
|
1693
1596
|
function addMetrics(target, source) {
|
|
@@ -1726,7 +1629,7 @@ function analyzeAiReferralRows(input) {
|
|
|
1726
1629
|
const detailTotal = emptyMetrics();
|
|
1727
1630
|
for (const row of input.detailRows) {
|
|
1728
1631
|
const definition = aiReferralSourceForValue(row.sessionSource ?? "");
|
|
1729
|
-
if (!definition || !sourceTotals.has(definition.id)) throw new SeoError("PROVIDER_UNAVAILABLE", "
|
|
1632
|
+
if (!definition || !sourceTotals.has(definition.id)) throw new SeoError("PROVIDER_UNAVAILABLE", "Google Analytics detail rows contained an unexpected session source.");
|
|
1730
1633
|
const metrics = {
|
|
1731
1634
|
sessions: metric(row, "sessions"),
|
|
1732
1635
|
eventCount: metric(row, "eventCount")
|
|
@@ -1759,11 +1662,11 @@ function analyzeAiReferralRows(input) {
|
|
|
1759
1662
|
...evidenceReasons("Source discovery", input.sourceDiscovery),
|
|
1760
1663
|
...evidenceReasons("Landing-page detail", input.detail),
|
|
1761
1664
|
...evidenceReasons("Total users", input.totalUsersEvidence),
|
|
1762
|
-
...detailMismatch ? ["
|
|
1763
|
-
...timeZones.size > 1 ? ["
|
|
1665
|
+
...detailMismatch ? ["Google Analytics detail totals differ from source-discovery totals; landing-page and daily breakdowns are incomplete."] : [],
|
|
1666
|
+
...timeZones.size > 1 ? ["Google Analytics queries returned inconsistent property time zones."] : []
|
|
1764
1667
|
];
|
|
1765
1668
|
const dataStatus = partialReasons.length ? "partial" : "complete";
|
|
1766
|
-
const generalCaveat = "
|
|
1669
|
+
const generalCaveat = "Google Analytics only attributes visits that arrive with a recognized session source; some AI visits appear as direct, unassigned, or another source.";
|
|
1767
1670
|
const possiblyTruncated = input.sourceDiscovery.truncated || input.detail.truncated || input.totalUsersEvidence.truncated;
|
|
1768
1671
|
const allLandingPages = [...pageTotals.entries()].map(([landingPage, metrics]) => {
|
|
1769
1672
|
const topSourceId = [...metrics.sources.entries()].sort((left, right) => right[1] - left[1] || compareText$16(left[0], right[0]))[0]?.[0];
|
|
@@ -1795,13 +1698,13 @@ function analyzeAiReferralRows(input) {
|
|
|
1795
1698
|
dataStatus,
|
|
1796
1699
|
range: input.range,
|
|
1797
1700
|
methodology: {
|
|
1798
|
-
id: "
|
|
1701
|
+
id: "google-analytics-ai-referrals",
|
|
1799
1702
|
version: 2,
|
|
1800
1703
|
attributionDimension: "sessionSource",
|
|
1801
1704
|
sourceRulesVersion: AI_REFERRAL_SOURCES_VERSION
|
|
1802
1705
|
},
|
|
1803
1706
|
dataSource: {
|
|
1804
|
-
provider: "google-analytics
|
|
1707
|
+
provider: "google-analytics",
|
|
1805
1708
|
api: "analyticsdata.v1beta.runReport",
|
|
1806
1709
|
maxRows: input.maxRows,
|
|
1807
1710
|
calls: input.sourceDiscovery.calls + input.detail.calls + input.totalUsersEvidence.calls,
|
|
@@ -1818,7 +1721,7 @@ function analyzeAiReferralRows(input) {
|
|
|
1818
1721
|
totalUsersStatus: input.totalUsers === null ? "not-reported" : "available",
|
|
1819
1722
|
sources: sourceTotals.size,
|
|
1820
1723
|
landingPages: pageTotals.size,
|
|
1821
|
-
verdict: total.sessions ? dataStatus === "complete" ? "Detected AI referral traffic in
|
|
1724
|
+
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.",
|
|
1822
1725
|
caveat: generalCaveat
|
|
1823
1726
|
},
|
|
1824
1727
|
sources: [...sourceTotals.values()].map((source) => ({
|
|
@@ -1840,7 +1743,7 @@ function analyzeAiReferralRows(input) {
|
|
|
1840
1743
|
})).sort((left, right) => compareText$16(left.date, right.date)),
|
|
1841
1744
|
caveats: [
|
|
1842
1745
|
generalCaveat,
|
|
1843
|
-
"Attribution uses
|
|
1746
|
+
"Attribution uses Google Analytics sessionSource only; landing-page text, medium text, and event referrers are never treated as referral sources.",
|
|
1844
1747
|
"Source totals come from a low-cardinality source query; landing-page and daily detail is queried separately for the exact observed source values.",
|
|
1845
1748
|
"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.",
|
|
1846
1749
|
...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.`] : [],
|
|
@@ -1942,7 +1845,7 @@ function reportRange(input) {
|
|
|
1942
1845
|
const endDate = input.endDate ?? "yesterday";
|
|
1943
1846
|
const absolute = validIsoDate$1(startDate) && validIsoDate$1(endDate);
|
|
1944
1847
|
const relative = validRelativeDate(startDate) && validRelativeDate(endDate);
|
|
1945
|
-
if (!absolute && !relative) throw new SeoError("INVALID_INPUT", "startDate and endDate must both be YYYY-MM-DD dates or
|
|
1848
|
+
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.");
|
|
1946
1849
|
if (absolute && startDate > endDate) throw new SeoError("INVALID_INPUT", "startDate must be on or before endDate.");
|
|
1947
1850
|
return {
|
|
1948
1851
|
startDate,
|
|
@@ -2001,7 +1904,7 @@ async function aiReferralsReport(input, dependencies = {}) {
|
|
|
2001
1904
|
const property = input.property.trim();
|
|
2002
1905
|
if (!property) throw new SeoError("INVALID_INPUT", "property is required.");
|
|
2003
1906
|
const range = reportRange(input);
|
|
2004
|
-
const
|
|
1907
|
+
const googleAnalyticsDateRange = {
|
|
2005
1908
|
startDate: range.startDate,
|
|
2006
1909
|
endDate: range.endDate
|
|
2007
1910
|
};
|
|
@@ -2011,7 +1914,7 @@ async function aiReferralsReport(input, dependencies = {}) {
|
|
|
2011
1914
|
const sourceResult = await fetchAiReferralRows({
|
|
2012
1915
|
property,
|
|
2013
1916
|
request: {
|
|
2014
|
-
dateRanges: [
|
|
1917
|
+
dateRanges: [googleAnalyticsDateRange],
|
|
2015
1918
|
dimensions: [{ name: "sessionSource" }],
|
|
2016
1919
|
metrics: [{ name: "sessions" }, { name: "eventCount" }],
|
|
2017
1920
|
orderBys: [{
|
|
@@ -2036,7 +1939,7 @@ async function aiReferralsReport(input, dependencies = {}) {
|
|
|
2036
1939
|
detailResult = await fetchAiReferralRows({
|
|
2037
1940
|
property,
|
|
2038
1941
|
request: {
|
|
2039
|
-
dateRanges: [
|
|
1942
|
+
dateRanges: [googleAnalyticsDateRange],
|
|
2040
1943
|
dimensions: [
|
|
2041
1944
|
{ name: "date" },
|
|
2042
1945
|
{ name: "sessionSource" },
|
|
@@ -2072,7 +1975,7 @@ async function aiReferralsReport(input, dependencies = {}) {
|
|
|
2072
1975
|
usersResult = await fetchAiReferralRows({
|
|
2073
1976
|
property,
|
|
2074
1977
|
request: {
|
|
2075
|
-
dateRanges: [
|
|
1978
|
+
dateRanges: [googleAnalyticsDateRange],
|
|
2076
1979
|
metrics: [{ name: "totalUsers" }],
|
|
2077
1980
|
dimensionFilter
|
|
2078
1981
|
},
|
|
@@ -10033,7 +9936,7 @@ function crawlDefinitionId(input) {
|
|
|
10033
9936
|
const normalized = {
|
|
10034
9937
|
config: normalizeCrawlConfig(input.config),
|
|
10035
9938
|
site: input.site ?? null,
|
|
10036
|
-
|
|
9939
|
+
googleAnalyticsPropertyId: input.googleAnalyticsPropertyId ?? null
|
|
10037
9940
|
};
|
|
10038
9941
|
return `crawl_def_${createHash("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 20)}`;
|
|
10039
9942
|
}
|
|
@@ -10236,7 +10139,7 @@ function createCrawlReport(input) {
|
|
|
10236
10139
|
const definitionId = crawlDefinitionId({
|
|
10237
10140
|
config,
|
|
10238
10141
|
site: input.site,
|
|
10239
|
-
|
|
10142
|
+
googleAnalyticsPropertyId: input.googleAnalyticsPropertyId
|
|
10240
10143
|
});
|
|
10241
10144
|
const safePagesWithLinks = sortPages(sanitizePages(deriveInternalLinkAuthority(input.pages ?? [], input.linkGraph)));
|
|
10242
10145
|
const requests = sortRequests(sanitizeTenantValue(input.requests ?? []));
|
|
@@ -10249,7 +10152,7 @@ function createCrawlReport(input) {
|
|
|
10249
10152
|
definitionId,
|
|
10250
10153
|
projectId: input.projectId,
|
|
10251
10154
|
site: input.site,
|
|
10252
|
-
|
|
10155
|
+
googleAnalyticsPropertyId: input.googleAnalyticsPropertyId,
|
|
10253
10156
|
generatedAt: input.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
10254
10157
|
status: input.status ?? "completed",
|
|
10255
10158
|
configHash: crawlConfigHash(config),
|
|
@@ -10287,7 +10190,7 @@ function normalizeLoadedCrawlReport(report) {
|
|
|
10287
10190
|
definitionId: report.definitionId ?? crawlDefinitionId({
|
|
10288
10191
|
config: report.config,
|
|
10289
10192
|
site: report.site,
|
|
10290
|
-
|
|
10193
|
+
googleAnalyticsPropertyId: report.googleAnalyticsPropertyId
|
|
10291
10194
|
}),
|
|
10292
10195
|
summary: summarizeCrawlReport({
|
|
10293
10196
|
pages,
|
|
@@ -10700,7 +10603,7 @@ async function joinAnalytics(input) {
|
|
|
10700
10603
|
limit: input.limit
|
|
10701
10604
|
});
|
|
10702
10605
|
} catch (error) {
|
|
10703
|
-
const warning = `
|
|
10606
|
+
const warning = `Google Analytics metrics unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
10704
10607
|
input.warnings.push(warning);
|
|
10705
10608
|
return {
|
|
10706
10609
|
status: "unavailable",
|
|
@@ -10713,7 +10616,7 @@ async function joinAnalytics(input) {
|
|
|
10713
10616
|
};
|
|
10714
10617
|
}
|
|
10715
10618
|
if (analytics.warning) {
|
|
10716
|
-
const warning = `
|
|
10619
|
+
const warning = `Google Analytics metrics unavailable: ${analytics.warning}`;
|
|
10717
10620
|
input.warnings.push(warning);
|
|
10718
10621
|
return {
|
|
10719
10622
|
status: "unavailable",
|
|
@@ -10738,7 +10641,7 @@ async function joinAnalytics(input) {
|
|
|
10738
10641
|
const sourceQualityKnown = analytics.source?.dataStatus !== void 0;
|
|
10739
10642
|
const retainedRowLimitReached = analytics.source?.retainedRowLimitReached;
|
|
10740
10643
|
const qualityWarnings = analytics.source?.qualityWarnings ?? [];
|
|
10741
|
-
const warning = !sourceQualityKnown ? `
|
|
10644
|
+
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;
|
|
10742
10645
|
if (warning) input.warnings.push(warning);
|
|
10743
10646
|
return {
|
|
10744
10647
|
status: !sourceQualityKnown || analytics.source?.dataStatus === "partial" ? "partial" : joinedPages ? "joined" : "none",
|
|
@@ -10898,7 +10801,7 @@ async function crawlDataSources(input) {
|
|
|
10898
10801
|
joinedPages: 0,
|
|
10899
10802
|
retainedRowLimit: input.analyticsLimit,
|
|
10900
10803
|
retainedRowLimitReached: false,
|
|
10901
|
-
warning: input.cancelled ? "
|
|
10804
|
+
warning: input.cancelled ? "Google Analytics join skipped because the crawl was cancelled." : "Google Analytics join skipped because no property was selected."
|
|
10902
10805
|
}
|
|
10903
10806
|
};
|
|
10904
10807
|
if (!input.cancelled && input.site) dataSources.searchConsole = await joinSearchMetrics({
|
|
@@ -10914,8 +10817,8 @@ async function crawlDataSources(input) {
|
|
|
10914
10817
|
queryPagesMetricsBatch: input.queryPagesMetricsBatch,
|
|
10915
10818
|
queryPagesTopQueriesBatch: input.queryPagesTopQueriesBatch
|
|
10916
10819
|
});
|
|
10917
|
-
if (!input.cancelled && input.
|
|
10918
|
-
propertyId: input.
|
|
10820
|
+
if (!input.cancelled && input.googleAnalyticsPropertyId) dataSources.analytics = await joinAnalytics({
|
|
10821
|
+
propertyId: input.googleAnalyticsPropertyId,
|
|
10919
10822
|
pages: input.pages,
|
|
10920
10823
|
warnings: input.warnings,
|
|
10921
10824
|
limit: input.analyticsLimit,
|
|
@@ -11737,11 +11640,11 @@ function landingPageRankingPolicy(input) {
|
|
|
11737
11640
|
};
|
|
11738
11641
|
const warningSuffix = "Observed landing-page values remain visible but do not affect priority scores.";
|
|
11739
11642
|
const warnings = [
|
|
11740
|
-
...input.warning ? [`
|
|
11741
|
-
...input.source?.retainedRowLimitReached ? [`
|
|
11643
|
+
...input.warning ? [`Google Analytics: ${input.warning}`] : [],
|
|
11644
|
+
...input.source?.retainedRowLimitReached ? [`Google Analytics: the retained-row limit was reached. ${warningSuffix}`] : [],
|
|
11742
11645
|
...input.source?.qualityWarnings?.map((warning) => `${warning} ${warningSuffix}`) ?? []
|
|
11743
11646
|
];
|
|
11744
|
-
if (!input.source?.dataStatus && !input.warning) warnings.push(`
|
|
11647
|
+
if (!input.source?.dataStatus && !input.warning) warnings.push(`Google Analytics: landing-page completeness was not reported. ${warningSuffix}`);
|
|
11745
11648
|
return {
|
|
11746
11649
|
canRank: !input.warning && warnings.length === 0 && landingPageValuesCanRank(input.source),
|
|
11747
11650
|
warnings
|
|
@@ -11781,7 +11684,7 @@ async function fetchLandingPageValues(input, dependencies = defaultDependencies$
|
|
|
11781
11684
|
const values = landingPageValuesFromRows(rows);
|
|
11782
11685
|
const availableRows = result.rowCount;
|
|
11783
11686
|
const retainedRowLimitReached = availableRows !== void 0 && availableRows > rows.length || rows.length >= retainedRowLimit;
|
|
11784
|
-
const qualityWarnings = ga4ReportQualityWarnings(result, "
|
|
11687
|
+
const qualityWarnings = ga4ReportQualityWarnings(result, "Google Analytics landing-page report");
|
|
11785
11688
|
return {
|
|
11786
11689
|
values,
|
|
11787
11690
|
source: {
|
|
@@ -12535,7 +12438,7 @@ async function crawlSite(input, dependencies) {
|
|
|
12535
12438
|
const dataSources = await crawlDataSources({
|
|
12536
12439
|
cancelled,
|
|
12537
12440
|
site,
|
|
12538
|
-
|
|
12441
|
+
googleAnalyticsPropertyId: input.googleAnalyticsPropertyId,
|
|
12539
12442
|
pages,
|
|
12540
12443
|
warnings,
|
|
12541
12444
|
searchMetricsLimit,
|
|
@@ -12590,7 +12493,7 @@ async function crawlSite(input, dependencies) {
|
|
|
12590
12493
|
config,
|
|
12591
12494
|
projectId: input.projectId,
|
|
12592
12495
|
site,
|
|
12593
|
-
|
|
12496
|
+
googleAnalyticsPropertyId: input.googleAnalyticsPropertyId,
|
|
12594
12497
|
dataSources,
|
|
12595
12498
|
pages,
|
|
12596
12499
|
requests,
|
|
@@ -12679,7 +12582,7 @@ function crawlInput$1(input) {
|
|
|
12679
12582
|
url: input.url,
|
|
12680
12583
|
...input.searchSite ? { site: input.searchSite } : {},
|
|
12681
12584
|
projectId: input.projectId,
|
|
12682
|
-
|
|
12585
|
+
googleAnalyticsPropertyId: input.googleAnalyticsPropertyId,
|
|
12683
12586
|
mode: "site",
|
|
12684
12587
|
maxPages: input.maxPages,
|
|
12685
12588
|
maxDepth: input.maxDepth,
|
|
@@ -13067,8 +12970,8 @@ const CSV_SCHEMAS = {
|
|
|
13067
12970
|
"target",
|
|
13068
12971
|
"template",
|
|
13069
12972
|
"template_count",
|
|
13070
|
-
"
|
|
13071
|
-
"
|
|
12973
|
+
"google_analytics_sessions",
|
|
12974
|
+
"google_analytics_total_users",
|
|
13072
12975
|
"evidence",
|
|
13073
12976
|
"action"
|
|
13074
12977
|
],
|
|
@@ -13097,7 +13000,7 @@ const CSV_SCHEMAS = {
|
|
|
13097
13000
|
"title",
|
|
13098
13001
|
"target",
|
|
13099
13002
|
"template",
|
|
13100
|
-
"
|
|
13003
|
+
"google_analytics_sessions",
|
|
13101
13004
|
"evidence",
|
|
13102
13005
|
"action"
|
|
13103
13006
|
],
|
|
@@ -13544,8 +13447,8 @@ function refreshPrioritiesCsvFiles(report) {
|
|
|
13544
13447
|
target: item.target,
|
|
13545
13448
|
template: item.template?.label,
|
|
13546
13449
|
template_count: item.template?.count,
|
|
13547
|
-
|
|
13548
|
-
|
|
13450
|
+
google_analytics_sessions: item.analytics?.sessions,
|
|
13451
|
+
google_analytics_total_users: item.analytics?.totalUsers,
|
|
13549
13452
|
evidence: item.evidence,
|
|
13550
13453
|
action: item.action
|
|
13551
13454
|
}))
|
|
@@ -13580,7 +13483,7 @@ function refreshPrioritiesCsvFiles(report) {
|
|
|
13580
13483
|
title: finding.title,
|
|
13581
13484
|
target: finding.target,
|
|
13582
13485
|
template: finding.template?.label,
|
|
13583
|
-
|
|
13486
|
+
google_analytics_sessions: finding.analytics?.sessions,
|
|
13584
13487
|
evidence: finding.evidence,
|
|
13585
13488
|
action: finding.action
|
|
13586
13489
|
})))
|
|
@@ -13829,7 +13732,7 @@ function searchValueForGroup(report, urls) {
|
|
|
13829
13732
|
};
|
|
13830
13733
|
}
|
|
13831
13734
|
function whyThisRanks(input, recommendation, searchStatus) {
|
|
13832
|
-
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 ? `
|
|
13735
|
+
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}.`}`;
|
|
13833
13736
|
}
|
|
13834
13737
|
function verificationCommand(report, severity) {
|
|
13835
13738
|
return `seo crawl ${report.config.url} --severity ${severity} --max-pages ${Math.min(report.config.maxPages, 100)}`;
|
|
@@ -14139,7 +14042,7 @@ function snapshotInput(report) {
|
|
|
14139
14042
|
generatedAt: report.generatedAt,
|
|
14140
14043
|
projectId: report.projectId,
|
|
14141
14044
|
site: report.site,
|
|
14142
|
-
|
|
14045
|
+
googleAnalyticsPropertyId: report.googleAnalyticsPropertyId,
|
|
14143
14046
|
url: report.config.url,
|
|
14144
14047
|
status: report.status,
|
|
14145
14048
|
configHash: report.configHash,
|
|
@@ -15780,7 +15683,7 @@ const crawlReportBaseSchema = z.object({
|
|
|
15780
15683
|
definitionId: z.string(),
|
|
15781
15684
|
projectId: z.string().optional(),
|
|
15782
15685
|
site: z.string().optional(),
|
|
15783
|
-
|
|
15686
|
+
googleAnalyticsPropertyId: z.string().optional(),
|
|
15784
15687
|
generatedAt: z.string().datetime(),
|
|
15785
15688
|
status: z.enum([
|
|
15786
15689
|
"completed",
|
|
@@ -19715,7 +19618,7 @@ function stringMatchType(matchType) {
|
|
|
19715
19618
|
if (matchType === "equals") return "EXACT";
|
|
19716
19619
|
return "CONTAINS";
|
|
19717
19620
|
}
|
|
19718
|
-
function
|
|
19621
|
+
function googleAnalyticsLandingPageFilterForChange(change, group) {
|
|
19719
19622
|
if (change.scope === "site") return void 0;
|
|
19720
19623
|
if (change.scope === "query") return void 0;
|
|
19721
19624
|
if (change.scope === "group") {
|
|
@@ -19738,7 +19641,7 @@ function ga4LandingPageFilterForChange(change, group) {
|
|
|
19738
19641
|
}
|
|
19739
19642
|
} };
|
|
19740
19643
|
}
|
|
19741
|
-
function
|
|
19644
|
+
function summarizeGoogleAnalyticsRows(rows) {
|
|
19742
19645
|
return rows.reduce((sum, row) => ({
|
|
19743
19646
|
sessions: fixed(sum.sessions + (Number(row.sessions) || 0)),
|
|
19744
19647
|
engagedSessions: fixed(sum.engagedSessions + (Number(row.engagedSessions) || 0)),
|
|
@@ -19805,8 +19708,8 @@ function classify(input) {
|
|
|
19805
19708
|
}
|
|
19806
19709
|
//#endregion
|
|
19807
19710
|
//#region packages/core/src/analyze/experiments/measurement/ga4-change-provider.ts
|
|
19808
|
-
async function
|
|
19809
|
-
const result = await input.
|
|
19711
|
+
async function queryGoogleAnalyticsMetrics(input) {
|
|
19712
|
+
const result = await input.googleAnalyticsReport(input.propertyId, {
|
|
19810
19713
|
dateRanges: [input.window],
|
|
19811
19714
|
dimensions: [{ name: "date" }],
|
|
19812
19715
|
metrics: [
|
|
@@ -19820,7 +19723,7 @@ async function queryGa4Metrics(input) {
|
|
|
19820
19723
|
}, { refresh: input.refresh });
|
|
19821
19724
|
const rows = ga4RowsToObjects(result);
|
|
19822
19725
|
return {
|
|
19823
|
-
metrics:
|
|
19726
|
+
metrics: summarizeGoogleAnalyticsRows(rows),
|
|
19824
19727
|
source: {
|
|
19825
19728
|
rows: rows.length,
|
|
19826
19729
|
rowCount: result.rowCount ?? rows.length,
|
|
@@ -19830,19 +19733,19 @@ async function queryGa4Metrics(input) {
|
|
|
19830
19733
|
warnings: ga4ReportQualityWarnings(result, input.label)
|
|
19831
19734
|
};
|
|
19832
19735
|
}
|
|
19833
|
-
async function
|
|
19834
|
-
const [before, after] = await Promise.all([
|
|
19736
|
+
async function queryGoogleAnalyticsChangeWindows(input) {
|
|
19737
|
+
const [before, after] = await Promise.all([queryGoogleAnalyticsMetrics({
|
|
19835
19738
|
...input,
|
|
19836
19739
|
window: input.before,
|
|
19837
|
-
label: "
|
|
19838
|
-
}),
|
|
19740
|
+
label: "Google Analytics before window"
|
|
19741
|
+
}), queryGoogleAnalyticsMetrics({
|
|
19839
19742
|
...input,
|
|
19840
19743
|
window: input.after,
|
|
19841
|
-
label: "
|
|
19744
|
+
label: "Google Analytics after window"
|
|
19842
19745
|
})]);
|
|
19843
19746
|
const metadataWarnings = [];
|
|
19844
|
-
if (before.source.timeZone && after.source.timeZone && before.source.timeZone !== after.source.timeZone) metadataWarnings.push("
|
|
19845
|
-
if (before.source.currencyCode && after.source.currencyCode && before.source.currencyCode !== after.source.currencyCode) metadataWarnings.push("
|
|
19747
|
+
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.");
|
|
19748
|
+
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.");
|
|
19846
19749
|
return {
|
|
19847
19750
|
report: {
|
|
19848
19751
|
propertyId: input.propertyId,
|
|
@@ -19858,7 +19761,7 @@ async function queryGa4ChangeWindows(input) {
|
|
|
19858
19761
|
before: before.metrics,
|
|
19859
19762
|
after: after.metrics
|
|
19860
19763
|
}),
|
|
19861
|
-
note: "
|
|
19764
|
+
note: "Google Analytics attribution is landing-page based. Query-level tests use GSC only."
|
|
19862
19765
|
},
|
|
19863
19766
|
source: {
|
|
19864
19767
|
before: before.source,
|
|
@@ -20096,7 +19999,7 @@ function measurementWindow(input) {
|
|
|
20096
19999
|
//#region packages/core/src/analyze/experiments/measure-change.ts
|
|
20097
20000
|
const defaultDependencies$5 = {
|
|
20098
20001
|
searchAnalytics: querySearchAnalytics,
|
|
20099
|
-
|
|
20002
|
+
googleAnalyticsReport: runGa4Report,
|
|
20100
20003
|
contentGroup: getContentGroup,
|
|
20101
20004
|
now: () => /* @__PURE__ */ new Date()
|
|
20102
20005
|
};
|
|
@@ -20182,15 +20085,15 @@ async function measureChange(input, dependencies = defaultDependencies$5) {
|
|
|
20182
20085
|
confidence: "medium",
|
|
20183
20086
|
note: `${classified.note} Confidence is capped because Search Console evidence is partial.`
|
|
20184
20087
|
} : classified;
|
|
20185
|
-
const
|
|
20088
|
+
const googleAnalyticsFilter = googleAnalyticsLandingPageFilterForChange(change, group);
|
|
20186
20089
|
const queryDimension = change.scope === "query" || group?.dimension === "query";
|
|
20187
|
-
const analytics = input.
|
|
20188
|
-
propertyId: input.
|
|
20090
|
+
const analytics = input.googleAnalyticsPropertyId && !queryDimension ? await queryGoogleAnalyticsChangeWindows({
|
|
20091
|
+
propertyId: input.googleAnalyticsPropertyId,
|
|
20189
20092
|
before,
|
|
20190
20093
|
after,
|
|
20191
|
-
filter:
|
|
20094
|
+
filter: googleAnalyticsFilter,
|
|
20192
20095
|
refresh: input.refresh,
|
|
20193
|
-
|
|
20096
|
+
googleAnalyticsReport: dependencies.googleAnalyticsReport
|
|
20194
20097
|
}) : void 0;
|
|
20195
20098
|
const warnings = [
|
|
20196
20099
|
...measured.warnings,
|
|
@@ -20269,7 +20172,7 @@ async function measureChange(input, dependencies = defaultDependencies$5) {
|
|
|
20269
20172
|
...window.afterWindowTruncated ? [`The requested ${window.requestedDays}-day after window is incomplete, so this run is partial and has no directional verdict.`] : [],
|
|
20270
20173
|
...shortWindow ? ["Fewer than 7 finalized days per window is too short for a directional verdict."] : [],
|
|
20271
20174
|
...measured.completeness === "retained-query-date-aggregates" ? ["Query-scoped Search Console data can omit anonymized queries; an absent retained row is not zero traffic."] : [],
|
|
20272
|
-
...analytics ? [`
|
|
20175
|
+
...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.`] : []
|
|
20273
20176
|
]
|
|
20274
20177
|
};
|
|
20275
20178
|
}
|
|
@@ -25940,7 +25843,7 @@ function reportCaveats(input) {
|
|
|
25940
25843
|
`Brand queries: ${input.includeBrand ? "included" : input.brandTerms?.length ? "excluded where saved brand terms matched" : "no saved brand terms, so no brand filter was applied"}.`,
|
|
25941
25844
|
`Data freshness: ${input.refresh ? "fresh fetch requested; local cache bypassed where supported" : "local cache allowed; rerun with --refresh to bypass cached GSC/HTTP data"}.`,
|
|
25942
25845
|
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.",
|
|
25943
|
-
"
|
|
25846
|
+
"Google Analytics: not included in this narrative; use refresh-priorities when Google Analytics value should influence prioritisation.",
|
|
25944
25847
|
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."
|
|
25945
25848
|
];
|
|
25946
25849
|
}
|
|
@@ -26836,17 +26739,17 @@ async function crawlImplementationQueueWorkflow(input, dependencies) {
|
|
|
26836
26739
|
warnings: crawl.warnings
|
|
26837
26740
|
}),
|
|
26838
26741
|
joinStep({
|
|
26839
|
-
tool: "
|
|
26840
|
-
enabled: Boolean(input.
|
|
26841
|
-
warningPrefix: "
|
|
26842
|
-
enabledSummary: "Joined
|
|
26843
|
-
skippedSummary: "No
|
|
26742
|
+
tool: "seo_google_analytics_landing_page_values",
|
|
26743
|
+
enabled: Boolean(input.googleAnalyticsPropertyId),
|
|
26744
|
+
warningPrefix: "Google Analytics metrics skipped:",
|
|
26745
|
+
enabledSummary: "Joined Google Analytics landing-page value where URLs matched.",
|
|
26746
|
+
skippedSummary: "No Google Analytics property was selected.",
|
|
26844
26747
|
warnings: crawl.warnings
|
|
26845
26748
|
}),
|
|
26846
26749
|
{
|
|
26847
26750
|
tool: "seo_top_crawl_fixes",
|
|
26848
26751
|
status: "completed",
|
|
26849
|
-
summary: "Grouped crawl issues, weighted them by severity, affected URLs, GSC visibility,
|
|
26752
|
+
summary: "Grouped crawl issues, weighted them by severity, affected URLs, GSC visibility, Google Analytics value, and effort."
|
|
26850
26753
|
}
|
|
26851
26754
|
],
|
|
26852
26755
|
actions: queue.slice(0, 5).map((item) => ({
|
|
@@ -27199,12 +27102,12 @@ async function refreshPrioritiesWorkflow(input) {
|
|
|
27199
27102
|
});
|
|
27200
27103
|
const range = defaultDateRange$1(input.days ?? 28);
|
|
27201
27104
|
const analytics = await fetchLandingPageValues({
|
|
27202
|
-
propertyId: input.
|
|
27105
|
+
propertyId: input.googleAnalyticsPropertyId,
|
|
27203
27106
|
startDate: range.startDate,
|
|
27204
27107
|
endDate: range.endDate
|
|
27205
27108
|
});
|
|
27206
27109
|
const analyticsPolicy = landingPageRankingPolicy({
|
|
27207
|
-
propertyId: input.
|
|
27110
|
+
propertyId: input.googleAnalyticsPropertyId,
|
|
27208
27111
|
source: analytics.source,
|
|
27209
27112
|
warning: analytics.warning
|
|
27210
27113
|
});
|
|
@@ -27761,6 +27664,9 @@ async function updatePostmortemWorkflow(input, dependencies = {}) {
|
|
|
27761
27664
|
}
|
|
27762
27665
|
//#endregion
|
|
27763
27666
|
//#region packages/core/src/clients.ts
|
|
27667
|
+
function googleAnalyticsPropertyId(client) {
|
|
27668
|
+
return client?.analytics.google?.propertyId;
|
|
27669
|
+
}
|
|
27764
27670
|
function slug(value) {
|
|
27765
27671
|
return value.toLowerCase().replace(/^https?:\/\//, "").replace(/^sc-domain:/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
27766
27672
|
}
|
|
@@ -27797,7 +27703,7 @@ function saveClient(input) {
|
|
|
27797
27703
|
name: input.name ?? existing?.name,
|
|
27798
27704
|
siteUrl: input.siteUrl
|
|
27799
27705
|
})),
|
|
27800
|
-
|
|
27706
|
+
analytics: input.analytics ?? existing?.analytics ?? {},
|
|
27801
27707
|
reportDay: input.reportDay ?? existing?.reportDay,
|
|
27802
27708
|
technicalWeekday: input.technicalWeekday ?? existing?.technicalWeekday,
|
|
27803
27709
|
isDefault: input.isDefault ?? existing?.isDefault,
|
|
@@ -27879,7 +27785,7 @@ async function runDoctor() {
|
|
|
27879
27785
|
const grantedScopes = new Set(tokens?.scope.split(/\s+/).filter(Boolean) ?? []);
|
|
27880
27786
|
for (const scope of REQUIRED_SCOPES) checks.push({
|
|
27881
27787
|
id: `scope:${scope}`,
|
|
27882
|
-
label: scope.endsWith("webmasters.readonly") ? "
|
|
27788
|
+
label: scope.endsWith("webmasters.readonly") ? "Search Console scope" : "Google Analytics scope",
|
|
27883
27789
|
status: serviceAccount.configured || tokens && grantedScopes.has(scope) ? "pass" : tokens ? "fail" : "warn",
|
|
27884
27790
|
detail: serviceAccount.configured ? `${scope} requested by the service account.` : tokens ? grantedScopes.has(scope) ? scope : `Missing ${scope}.` : "Cannot inspect scopes until login is complete.",
|
|
27885
27791
|
fix: !serviceAccount.configured && tokens && !grantedScopes.has(scope) ? "Run `seo auth logout` then `seo auth login`." : void 0
|
|
@@ -27891,10 +27797,10 @@ async function runDoctor() {
|
|
|
27891
27797
|
detail: config.defaultSite ?? "No saved default. Human CLI commands will prompt; agents can pass --site."
|
|
27892
27798
|
});
|
|
27893
27799
|
checks.push({
|
|
27894
|
-
id: "default-
|
|
27895
|
-
label: "Saved
|
|
27800
|
+
id: "default-google-analytics",
|
|
27801
|
+
label: "Saved Google Analytics property",
|
|
27896
27802
|
status: "pass",
|
|
27897
|
-
detail: config.google.
|
|
27803
|
+
detail: config.analytics.google.defaultPropertyId ?? "No saved default. Human CLI commands will prompt; agents can pass --property."
|
|
27898
27804
|
});
|
|
27899
27805
|
return {
|
|
27900
27806
|
ok: checks.every((check) => check.status !== "fail"),
|
|
@@ -28266,7 +28172,7 @@ function matchGa4WebStreams(site, candidates) {
|
|
|
28266
28172
|
function ga4MatchReason(match, site) {
|
|
28267
28173
|
const stream = match.stream.displayName ?? match.defaultUri;
|
|
28268
28174
|
const location = match.match === "domain" ? "is inside" : "matches";
|
|
28269
|
-
return `Matched
|
|
28175
|
+
return `Matched Google Analytics web stream ${stream} (${match.defaultUri}): its hostname ${location} ${site}.`;
|
|
28270
28176
|
}
|
|
28271
28177
|
//#endregion
|
|
28272
28178
|
//#region packages/core/src/logger.ts
|
|
@@ -28611,6 +28517,6 @@ function isoDate(daysAgo = 0) {
|
|
|
28611
28517
|
//#region packages/core/src/version.ts
|
|
28612
28518
|
const SEO_VERSION = version;
|
|
28613
28519
|
//#endregion
|
|
28614
|
-
export { AI_REFERRAL_SOURCES, AI_REFERRAL_SOURCES_VERSION, AI_SEARCH_SCORECARD_METHODOLOGY_ID, AI_SEARCH_SCORECARD_METHODOLOGY_VERSION, AUDIT_PAGE_RULE_IDS, BROWSER_USER_AGENT, CANNIBAL_MAXIMUM_DOMINANT_SHARE, CANNIBAL_MINIMUM_PAGE_SHARE, CRAWL_REPORT_STORAGE_VERSION, CRAWL_SKIP_REASONS, CTR_DEFAULT_LIMIT, CTR_DEFAULT_MIN_IMPRESSIONS, CTR_MAX_LIMIT, CTR_MAX_MIN_IMPRESSIONS, DataForSeoProvider, GSC_FINAL_DATA_LAG_DAYS, GSC_TIME_ZONE, INTERNAL_LINK_LEXICAL_TARGET_LIMIT, INTERNAL_LINK_MATCH_EVIDENCE_LIMIT, JsonFileStorageAdapter, LocalCredentialsProvider, OKF_DEFAULT_CONCEPTS, OKF_MAX_CONCEPTS, REPORT_BASELINE_MAX_AGE_MS, REPORT_BASELINE_MAX_DEPTH, REPORT_BASELINE_MAX_PAGES, SCORECARD_CHECK_WEIGHTS, SCORECARD_STATUS_CREDIT, SEO_VERSION, SERP_TITLE_REFERENCE_WIDTH_PX, SERP_TITLE_WIDTH_PROFILE, SemrushProvider, SeoError, ServiceAccountAccessTokenClient, SessionLedger, affectedUrls, agentReadiness, aiPromptsForQuery, aiReadiness, aiReferralSourceForValue, aiReferralsReport, aiSearchScorecard, allocateIndexUrlsToProperties, analyzeAiReferralRows, analyzeCannibalRows, analyzeCtrUnderperformersFromRows, analyzeDecay, analyzeIndexCoverageSignals, analyzeInternalLinksFromRows, analyzePageOpportunitiesFromRows, analyzeQueryClustersFromRows, analyzeQuickWinsFromRows, analyzeSecondPageRows, analyzeSegmentRows, analyzeStrikingDistanceRows, analyzeTrafficAnomalyRows, assertUrlMatchesGscProperty, attachCrawlRecommendations, auditCrawlPages, auditCrawlRequests, auditLlmsTxt, auditPage, authStatus, boundedSitemapInventory, buildOkfBundle, buildPseoAuditReportFromRows, cannibalReport, changeMeasurementCsvRows, classifyCommunityIntent, clearCache, clientProfileSchema, clusterPseoTemplates, clusterQueryRows, collectAgentDiscovery, combineRobotsValues, communityIntentReport, compareCrawlPages, compareCrawlReports, compareSegmentRows, configSchema, contentCoverageRecommendation, contentCoverageType, contentOptimizationFromPageOpportunities, contentOptimizationReport, countLabel, crawlConfigHash, crawlConfigSchema, crawlDefinitionId, crawlDiff, crawlFixToQueueItem, crawlImplementationQueueWorkflow, crawlIssueGroupSchema, crawlIssueSchema, crawlOne, crawlPageSnapshotSchema, crawlReportSchema, crawlReportStore, crawlReportSummarySchema, crawlRequestObservationSchema, crawlRunId, crawlSite, crawlSkipImpact, crawlSkipReasonCountsFromRecord, crawlTopFixSchema, crawlerJsonSchemas, crawlerRuleCategorySchema, crawlerRuleIdSchema, crawlerRuleInfoSchema, crawlerRuleRecommendationSchema, crawlerRuleSeveritySchema, crawlerSchemas, createAuthorizedClient, createContentGroup, createCrawlReport, createCtrBenchmarkContext, createGoogleAccessTokenClient, createLogger, createPageRenderer, ctrUnderperformersReport, decayClusterDrafts, decayComparisonRange, decayingReport, deleteChange, deleteClient, deleteContentGroup, deleteCrawlReport, deleteTokens, deriveBrandTerms, detectBrandTerms, diagnoseCsvFiles, diagnoseProperty, diagnosePropertyWorkflow, effectiveRobotsDirectives, effectiveSnippetControl, ensureSeoCliDirs, entityReadiness, estimateSerpTitleWidth, expectedCtrForPosition, explainOkfValidation, explainRule, fetchCruxFieldData, fetchPage, fetchSearchStatusIncidents, fetchSitemapUrls, fileSize, finalGscDateRange, findOverlappingSearchUpdates, formatRelativeExpiry, ga4MatchReason, ga4PropertyIdFromName, ga4ReportQualityWarnings, ga4RequestCanUseCache, ga4RowsToObjects, generateLlmsTxt, geoGaps, geoGapsReport, getAuthModeStatus, getCacheStats, getChange, getClient, getClientConfig, getContentGroup, getDb, getKeywordProvider, getPreviousRun, getPrivacySnapshot, getRepeatedLinkRecoverUrls, getRunPages, getSeoCliPaths, getServiceAccountConfig, getServiceAccountStatus, getTokenStorageStatus, groupCrawlIssues, hasMetaRobotsDirective, hasRule, hasXRobotsDirective, hashKey, indexCoveragePlan, indexCoverageSignals, indexMonitor, indexWatch, inferPseoTemplate, inferTemplateMovement, insertCrawlRun, insertLinkRecoverRun, inspectUrl, internalLinksReport, interpretUpdateCorrelation, isBrandQuery, isCompatibleTechnicalBaseline, isCurrentTechnicalBaseline, isEntityEvidenceSchemaType, isSiteIdentitySchemaType, isSkippableReportError, isoDate, latestCrawlReport, latestCrawlSummaries, latestFinalGscDate, latestIndexWatchSummary, latestLinkRecoverSummary, linkRecover, linkRecoverRecommendation, listChanges, listClients, listContentGroups, listCrawlReports, listGa4AccountSummaries, listGa4DataStreams, listRules, listSearchUpdates, listSites, loadCrawlReport, localCredentialsProvider, loginWithLoopback, makeRunId, matchGa4WebStreams, measureChange, measureCoverage, metaRobotsDirectives, migrateLegacyMacConfig, missingOAuthClientMessage, missingSharedClientLoginMessage, monitoringStatus, monthlyReport, monthlyReportWorkflow, narrativeCsvFiles, normalizeCrawlConfig, normalizeCrawlSkipReasonCounts, normalizeForCoverage, normalizeHttpUrl, normalizeJavaScriptRenderingMode, normalizeLoadedCrawlReport, okfConceptLimit, pageEvidenceRecommendation, pageOpportunitiesReport, parseOlderThan, parsePseoPath, performanceAudit, performanceReportIsCacheable, planDueIndexUrls, planIndexCoverageFromUrls, plural, propertyMatchesUrl, providerPreferenceSchema, pseoAuditOptions, pseoAuditReport, pseoCsvFiles, pseoIndexStatus, pseoPresentation, publicHttpFetch, queryClusterReport, queryClusterTokens, queryContentCoverageFromPage, queryContentFieldsFromPage, queryFieldCoverage, queryOpportunityRecommendation, queryPageMetrics, queryPageTopQuery, queryPagesMetrics, queryPagesMetricsBatch, queryPagesTopQueries, queryPagesTopQueriesBatch, querySearchAnalytics, quickWinsCsvRows, quickWinsReport, readConfig, readOauthClient, readTokens, recommendCrawlDiffItem, recommendationForRule, recordChange, redirectTrace, refreshAuthToken, refreshPrioritiesCsvFiles, refreshPrioritiesWorkflow, renderCrawlCsv, renderCrawlHtml, renderCrawlMarkdownTickets, renderCrawlPagesCsv, renderCrawlPretty, renderCsv, renderPresentationTablesMarkdown, renderPseoMarkdown, renderWorkflowMarkdown, reportNarrative, reportPresentation, requestHeaders, resolveSegmentRanges, resolveSeoCliPaths, resolveTechnicalBaseline, reviewObservations, roundedPosition, runDoctor, runGa4Report, saveClient, saveCrawlReport, secondPage, segmentComparisonRange, segmentImpact, segmentRangeDays, selectAffectedUrls, selectDueIndexUrls, seoErrorEnvelope, seoToAiQueryReport, sessionSourceInListFilter, setDefaultClient, setTokenStorageMode, shiftIsoDate, shouldExcludeBrandQuery, siteSchema, sqliteCrawlReportStore, strikingDistance, summarizeCrawlReport, pageTechnicalSignals as technicalPageSignals, technicalWatchWorkflow, templateForUrl, toSeoError, tokenSchema, topCrawlRecommendations, topFixes, trafficAnomaly, unavailableSegmentImpactReport, unicodeTokens, updateCorrelation, updatePostmortemCsvFiles, updatePostmortemWorkflow, validateOkfFiles, verifyQueryContent, workflowPresentation, writeConfig, writeOauthClient, writeTokens, xRobotsDirectives };
|
|
28520
|
+
export { AI_REFERRAL_SOURCES, AI_REFERRAL_SOURCES_VERSION, AI_SEARCH_SCORECARD_METHODOLOGY_ID, AI_SEARCH_SCORECARD_METHODOLOGY_VERSION, AUDIT_PAGE_RULE_IDS, BROWSER_USER_AGENT, CANNIBAL_MAXIMUM_DOMINANT_SHARE, CANNIBAL_MINIMUM_PAGE_SHARE, CRAWL_REPORT_STORAGE_VERSION, CRAWL_SKIP_REASONS, CTR_DEFAULT_LIMIT, CTR_DEFAULT_MIN_IMPRESSIONS, CTR_MAX_LIMIT, CTR_MAX_MIN_IMPRESSIONS, DataForSeoProvider, GSC_FINAL_DATA_LAG_DAYS, GSC_TIME_ZONE, INTERNAL_LINK_LEXICAL_TARGET_LIMIT, INTERNAL_LINK_MATCH_EVIDENCE_LIMIT, JsonFileStorageAdapter, LocalCredentialsProvider, OKF_DEFAULT_CONCEPTS, OKF_MAX_CONCEPTS, REPORT_BASELINE_MAX_AGE_MS, REPORT_BASELINE_MAX_DEPTH, REPORT_BASELINE_MAX_PAGES, SCORECARD_CHECK_WEIGHTS, SCORECARD_STATUS_CREDIT, SEO_VERSION, SERP_TITLE_REFERENCE_WIDTH_PX, SERP_TITLE_WIDTH_PROFILE, SemrushProvider, SeoError, ServiceAccountAccessTokenClient, SessionLedger, affectedUrls, agentReadiness, aiPromptsForQuery, aiReadiness, aiReferralSourceForValue, aiReferralsReport, aiSearchScorecard, allocateIndexUrlsToProperties, analyticsConnectionsSchema, analyzeAiReferralRows, analyzeCannibalRows, analyzeCtrUnderperformersFromRows, analyzeDecay, analyzeIndexCoverageSignals, analyzeInternalLinksFromRows, analyzePageOpportunitiesFromRows, analyzeQueryClustersFromRows, analyzeQuickWinsFromRows, analyzeSecondPageRows, analyzeSegmentRows, analyzeStrikingDistanceRows, analyzeTrafficAnomalyRows, assertUrlMatchesGscProperty, attachCrawlRecommendations, auditCrawlPages, auditCrawlRequests, auditLlmsTxt, auditPage, authStatus, boundedSitemapInventory, buildOkfBundle, buildPseoAuditReportFromRows, cannibalReport, changeMeasurementCsvRows, classifyCommunityIntent, clearCache, clientProfileSchema, clusterPseoTemplates, clusterQueryRows, collectAgentDiscovery, combineRobotsValues, communityIntentReport, compareCrawlPages, compareCrawlReports, compareSegmentRows, configSchema, contentCoverageRecommendation, contentCoverageType, contentOptimizationFromPageOpportunities, contentOptimizationReport, countLabel, crawlConfigHash, crawlConfigSchema, crawlDefinitionId, crawlDiff, crawlFixToQueueItem, crawlImplementationQueueWorkflow, crawlIssueGroupSchema, crawlIssueSchema, crawlOne, crawlPageSnapshotSchema, crawlReportSchema, crawlReportStore, crawlReportSummarySchema, crawlRequestObservationSchema, crawlRunId, crawlSite, crawlSkipImpact, crawlSkipReasonCountsFromRecord, crawlTopFixSchema, crawlerJsonSchemas, crawlerRuleCategorySchema, crawlerRuleIdSchema, crawlerRuleInfoSchema, crawlerRuleRecommendationSchema, crawlerRuleSeveritySchema, crawlerSchemas, createAuthorizedClient, createContentGroup, createCrawlReport, createCtrBenchmarkContext, createGoogleAccessTokenClient, createLogger, createPageRenderer, ctrUnderperformersReport, decayClusterDrafts, decayComparisonRange, decayingReport, deleteChange, deleteClient, deleteContentGroup, deleteCrawlReport, deleteTokens, deriveBrandTerms, detectBrandTerms, diagnoseCsvFiles, diagnoseProperty, diagnosePropertyWorkflow, effectiveRobotsDirectives, effectiveSnippetControl, ensureSeoCliDirs, entityReadiness, estimateSerpTitleWidth, expectedCtrForPosition, explainOkfValidation, explainRule, fetchCruxFieldData, fetchPage, fetchSearchStatusIncidents, fetchSitemapUrls, fileSize, finalGscDateRange, findOverlappingSearchUpdates, formatRelativeExpiry, ga4MatchReason, ga4PropertyIdFromName, ga4ReportQualityWarnings, ga4RequestCanUseCache, ga4RowsToObjects, generateLlmsTxt, geoGaps, geoGapsReport, getAuthModeStatus, getCacheStats, getChange, getClient, getClientConfig, getContentGroup, getDb, getKeywordProvider, getPreviousRun, getPrivacySnapshot, getRepeatedLinkRecoverUrls, getRunPages, getSeoCliPaths, getServiceAccountConfig, getServiceAccountStatus, getTokenStorageStatus, googleAnalyticsPropertyId, groupCrawlIssues, hasMetaRobotsDirective, hasRule, hasXRobotsDirective, hashKey, indexCoveragePlan, indexCoverageSignals, indexMonitor, indexWatch, inferPseoTemplate, inferTemplateMovement, insertCrawlRun, insertLinkRecoverRun, inspectUrl, internalLinksReport, interpretUpdateCorrelation, isBrandQuery, isCompatibleTechnicalBaseline, isCurrentTechnicalBaseline, isEntityEvidenceSchemaType, isSiteIdentitySchemaType, isSkippableReportError, isoDate, latestCrawlReport, latestCrawlSummaries, latestFinalGscDate, latestIndexWatchSummary, latestLinkRecoverSummary, linkRecover, linkRecoverRecommendation, listChanges, listClients, listContentGroups, listCrawlReports, listGa4AccountSummaries, listGa4DataStreams, listRules, listSearchUpdates, listSites, loadCrawlReport, localCredentialsProvider, loginWithLoopback, makeRunId, matchGa4WebStreams, measureChange, measureCoverage, metaRobotsDirectives, migrateLegacyMacConfig, missingOAuthClientMessage, missingSharedClientLoginMessage, monitoringStatus, monthlyReport, monthlyReportWorkflow, narrativeCsvFiles, normalizeCrawlConfig, normalizeCrawlSkipReasonCounts, normalizeForCoverage, normalizeHttpUrl, normalizeJavaScriptRenderingMode, normalizeLoadedCrawlReport, okfConceptLimit, pageEvidenceRecommendation, pageOpportunitiesReport, parseOlderThan, parsePseoPath, performanceAudit, performanceReportIsCacheable, planDueIndexUrls, planIndexCoverageFromUrls, plural, propertyMatchesUrl, providerPreferenceSchema, pseoAuditOptions, pseoAuditReport, pseoCsvFiles, pseoIndexStatus, pseoPresentation, publicHttpFetch, queryClusterReport, queryClusterTokens, queryContentCoverageFromPage, queryContentFieldsFromPage, queryFieldCoverage, queryOpportunityRecommendation, queryPageMetrics, queryPageTopQuery, queryPagesMetrics, queryPagesMetricsBatch, queryPagesTopQueries, queryPagesTopQueriesBatch, querySearchAnalytics, quickWinsCsvRows, quickWinsReport, readConfig, readOauthClient, readTokens, recommendCrawlDiffItem, recommendationForRule, recordChange, redirectTrace, refreshAuthToken, refreshPrioritiesCsvFiles, refreshPrioritiesWorkflow, renderCrawlCsv, renderCrawlHtml, renderCrawlMarkdownTickets, renderCrawlPagesCsv, renderCrawlPretty, renderCsv, renderPresentationTablesMarkdown, renderPseoMarkdown, renderWorkflowMarkdown, reportNarrative, reportPresentation, requestHeaders, resolveSegmentRanges, resolveSeoCliPaths, resolveTechnicalBaseline, reviewObservations, roundedPosition, runDoctor, runGa4Report, saveClient, saveCrawlReport, secondPage, segmentComparisonRange, segmentImpact, segmentRangeDays, selectAffectedUrls, selectDueIndexUrls, seoErrorEnvelope, seoToAiQueryReport, sessionSourceInListFilter, setDefaultClient, setTokenStorageMode, shiftIsoDate, shouldExcludeBrandQuery, siteSchema, sqliteCrawlReportStore, strikingDistance, summarizeCrawlReport, pageTechnicalSignals as technicalPageSignals, technicalWatchWorkflow, templateForUrl, toSeoError, tokenSchema, topCrawlRecommendations, topFixes, trafficAnomaly, unavailableSegmentImpactReport, unicodeTokens, updateCorrelation, updatePostmortemCsvFiles, updatePostmortemWorkflow, validateOkfFiles, verifyQueryContent, workflowPresentation, writeConfig, writeOauthClient, writeTokens, xRobotsDirectives };
|
|
28615
28521
|
|
|
28616
28522
|
//# sourceMappingURL=index.js.map
|