seo 0.8.0 → 0.8.1
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/cli.js +2 -2
- package/dist/{dist-BN_s3D0r.js → dist-B5I5uxdM.js} +27 -26
- package/dist/dist-B5I5uxdM.js.map +1 -0
- package/dist/{dist-sB8HDnCm.js → dist-ulug7z2k.js} +1 -1
- package/dist/index.js +22 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +104 -21
- package/dist/mcp.js.map +1 -1
- package/dist/{package-D6HG7niq.js → package-BjCto4m4.js} +2 -2
- package/dist/{package-D6HG7niq.js.map → package-BjCto4m4.js.map} +1 -1
- package/package.json +1 -1
- package/dist/dist-BN_s3D0r.js.map +0 -1
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 { i as __toESM, n as load, r as __commonJSMin, t as version } from "./package-
|
|
2
|
+
import { i as __toESM, n as load, r as __commonJSMin, t as version } from "./package-BjCto4m4.js";
|
|
3
3
|
import { createHash, randomUUID, sign } from "node:crypto";
|
|
4
4
|
import { ZodOptional, z } from "zod";
|
|
5
5
|
import { arch, homedir, platform, totalmem } from "node:os";
|
|
@@ -14148,7 +14148,7 @@ var CommandError = class extends Error {
|
|
|
14148
14148
|
};
|
|
14149
14149
|
//#endregion
|
|
14150
14150
|
//#region packages/core/dist/commands/shared.js
|
|
14151
|
-
const FACTS_ONLY = "These are facts from Search Console, not instructions.
|
|
14151
|
+
const FACTS_ONLY = "These are facts from Search Console, not instructions.";
|
|
14152
14152
|
const ADVICE_KEYS = /^(recommendations?|actions?|advice|suggested[A-Z]\w*|nextSteps?)$/;
|
|
14153
14153
|
function factsOnly(value) {
|
|
14154
14154
|
if (Array.isArray(value)) return value.map(factsOnly);
|
|
@@ -14219,9 +14219,10 @@ function sharedReader(read, redirects = /* @__PURE__ */ new Map()) {
|
|
|
14219
14219
|
return found;
|
|
14220
14220
|
};
|
|
14221
14221
|
}
|
|
14222
|
+
const MAX_STATUS_BYTES = 10 * 1024 * 1024;
|
|
14222
14223
|
async function googleUpdates(window) {
|
|
14223
14224
|
if (!window) return [];
|
|
14224
|
-
const incidents = await fetch("https://status.search.google.com/incidents.json", { signal: AbortSignal.timeout(8e3) }).then((response) => response.ok ? response
|
|
14225
|
+
const incidents = await fetch("https://status.search.google.com/incidents.json", { signal: AbortSignal.timeout(8e3) }).then((response) => response.ok ? readJson(response, "Google Search Status", { maxBytes: MAX_STATUS_BYTES }) : null).catch(() => null);
|
|
14225
14226
|
if (!Array.isArray(incidents)) return "Google Search Status could not be read.";
|
|
14226
14227
|
return findOverlappingSearchUpdates({
|
|
14227
14228
|
updates: searchUpdatesFromIncidents(incidents, {
|
|
@@ -31882,9 +31883,9 @@ function strikingDistance(input, dependencies = defaultDependencies$5) {
|
|
|
31882
31883
|
//#region packages/core/dist/updates/search-status.js
|
|
31883
31884
|
const INCIDENTS_URL = "https://status.search.google.com/incidents.json";
|
|
31884
31885
|
async function fetchSearchStatusIncidents() {
|
|
31885
|
-
const response = await fetch$1(INCIDENTS_URL);
|
|
31886
|
+
const response = await fetch$1(INCIDENTS_URL, { signal: AbortSignal.timeout(8e3) });
|
|
31886
31887
|
if (!response.ok) throw new SeoError("OPTIONAL_PROVIDER_UNAVAILABLE", `Google Search Status fetch failed with ${response.status}.`);
|
|
31887
|
-
return
|
|
31888
|
+
return readJson(response, "Google Search Status", { maxBytes: 10 * 1024 * 1024 });
|
|
31888
31889
|
}
|
|
31889
31890
|
async function listSearchUpdates(input = {}) {
|
|
31890
31891
|
return searchUpdatesFromIncidents(await fetchSearchStatusIncidents(), input);
|
|
@@ -35167,7 +35168,7 @@ async function fetchCruxFieldData(input) {
|
|
|
35167
35168
|
headers: { "content-type": "application/json" },
|
|
35168
35169
|
signal: controller.signal
|
|
35169
35170
|
});
|
|
35170
|
-
const json = await response
|
|
35171
|
+
const json = await readJson(response, "CrUX").catch(() => ({}));
|
|
35171
35172
|
if (response.status === 404) return {
|
|
35172
35173
|
response,
|
|
35173
35174
|
json
|
|
@@ -38011,6 +38012,26 @@ async function findProperty(access, site) {
|
|
|
38011
38012
|
}
|
|
38012
38013
|
return `no web stream matches ${site} in ${order.length < candidates.length ? `the first ${order.length} of ${candidates.length} properties, named like the site first` : `${candidates.length} ${candidates.length === 1 ? "property" : "properties"}`}${failures ? ` (${failures} could not be read)` : ""}.`;
|
|
38013
38014
|
}
|
|
38015
|
+
const AI_SOURCES = [
|
|
38016
|
+
"chatgpt.com",
|
|
38017
|
+
"chatgpt",
|
|
38018
|
+
"chat.openai.com",
|
|
38019
|
+
"perplexity.ai",
|
|
38020
|
+
"perplexity",
|
|
38021
|
+
"claude.ai",
|
|
38022
|
+
"gemini.google.com",
|
|
38023
|
+
"copilot.microsoft.com",
|
|
38024
|
+
"copilot.com",
|
|
38025
|
+
"chat.deepseek.com",
|
|
38026
|
+
"meta.ai",
|
|
38027
|
+
"grok.com",
|
|
38028
|
+
"chat.mistral.ai",
|
|
38029
|
+
"you.com",
|
|
38030
|
+
"phind.com",
|
|
38031
|
+
"poe.com"
|
|
38032
|
+
];
|
|
38033
|
+
const AI_SOURCE_PATTERN = `(www\\.)?(${AI_SOURCES.map((source) => source.replaceAll(".", "\\.")).join("|")})`;
|
|
38034
|
+
const AI_ROWS = 5;
|
|
38014
38035
|
const filter = (fieldName, value, matchType = "EXACT") => ({ filter: {
|
|
38015
38036
|
fieldName,
|
|
38016
38037
|
stringFilter: {
|
|
@@ -38033,11 +38054,17 @@ async function totals$1(found, access, range, before, pages) {
|
|
|
38033
38054
|
limit: 100
|
|
38034
38055
|
}, { signIn: access.signIn });
|
|
38035
38056
|
const onPages = pages ? [filter("landingPage", pages, "CONTAINS")] : [];
|
|
38036
|
-
const
|
|
38037
|
-
|
|
38038
|
-
|
|
38039
|
-
|
|
38040
|
-
|
|
38057
|
+
const fromAi = [...onPages, filter("sessionSource", AI_SOURCE_PATTERN, "FULL_REGEXP")];
|
|
38058
|
+
const [channels, google, aiSources, aiPages] = await Promise.all([
|
|
38059
|
+
read(["sessionDefaultChannelGroup"], onPages),
|
|
38060
|
+
read([], [
|
|
38061
|
+
...onPages,
|
|
38062
|
+
filter("sessionDefaultChannelGroup", "Organic Search"),
|
|
38063
|
+
filter("sessionSource", "google")
|
|
38064
|
+
]),
|
|
38065
|
+
read(["sessionSource"], fromAi),
|
|
38066
|
+
read(["landingPage"], fromAi)
|
|
38067
|
+
]);
|
|
38041
38068
|
const sum = () => ({
|
|
38042
38069
|
google: {
|
|
38043
38070
|
sessions: 0,
|
|
@@ -38075,10 +38102,50 @@ async function totals$1(found, access, range, before, pages) {
|
|
|
38075
38102
|
if (channel === "Organic Search") plus(period.organic, row);
|
|
38076
38103
|
});
|
|
38077
38104
|
each(google, (period, _, row) => plus(period.google, row));
|
|
38105
|
+
const byName = (data, key) => {
|
|
38106
|
+
const rows = /* @__PURE__ */ new Map();
|
|
38107
|
+
const periodAt = (data.dimensionHeaders?.map((header) => header.name) ?? []).indexOf("dateRange");
|
|
38108
|
+
for (const row of data.rows ?? []) {
|
|
38109
|
+
const values = row.dimensionValues ?? [];
|
|
38110
|
+
const name = values[0]?.value ?? "";
|
|
38111
|
+
const before = (periodAt >= 0 ? values[periodAt] : values.at(-1))?.value === "before";
|
|
38112
|
+
const entry = rows.get(name) ?? {
|
|
38113
|
+
now: 0,
|
|
38114
|
+
before: 0,
|
|
38115
|
+
keyEvents: 0
|
|
38116
|
+
};
|
|
38117
|
+
const sessions = Number(row.metricValues?.[0]?.value ?? 0);
|
|
38118
|
+
if (before) entry.before += sessions;
|
|
38119
|
+
else {
|
|
38120
|
+
entry.now += sessions;
|
|
38121
|
+
entry.keyEvents += Number(row.metricValues?.[1]?.value ?? 0);
|
|
38122
|
+
}
|
|
38123
|
+
rows.set(name, entry);
|
|
38124
|
+
}
|
|
38125
|
+
return [...rows].map(([name, entry]) => ({
|
|
38126
|
+
[key]: name,
|
|
38127
|
+
sessions: entry.now,
|
|
38128
|
+
previousSessions: entry.before,
|
|
38129
|
+
keyEvents: entry.keyEvents
|
|
38130
|
+
})).sort((a, b) => b.sessions - a.sessions || b.previousSessions - a.previousSessions || String(a[key]).localeCompare(String(b[key])));
|
|
38131
|
+
};
|
|
38132
|
+
const bySource = byName(aiSources, "source");
|
|
38133
|
+
const aiNow = bySource.reduce((total, row) => total + row.sessions, 0);
|
|
38134
|
+
const aiBefore = bySource.reduce((total, row) => total + row.previousSessions, 0);
|
|
38078
38135
|
const data = { metadata: {
|
|
38079
38136
|
timeZone: channels.metadata?.timeZone,
|
|
38080
|
-
subjectToThresholding:
|
|
38081
|
-
|
|
38137
|
+
subjectToThresholding: [
|
|
38138
|
+
channels,
|
|
38139
|
+
google,
|
|
38140
|
+
aiSources,
|
|
38141
|
+
aiPages
|
|
38142
|
+
].some((data) => data.metadata?.subjectToThresholding),
|
|
38143
|
+
dataLossFromOtherRow: [
|
|
38144
|
+
channels,
|
|
38145
|
+
google,
|
|
38146
|
+
aiSources,
|
|
38147
|
+
aiPages
|
|
38148
|
+
].some((data) => data.metadata?.dataLossFromOtherRow)
|
|
38082
38149
|
} };
|
|
38083
38150
|
const notes = [];
|
|
38084
38151
|
if (data.metadata?.timeZone && data.metadata.timeZone !== "America/Los_Angeles") notes.push(`Google Analytics days use the property's time zone, ${data.metadata.timeZone}; Search Console days use Pacific time.`);
|
|
@@ -38106,10 +38173,27 @@ async function totals$1(found, access, range, before, pages) {
|
|
|
38106
38173
|
previousSessions: periods.before.all.sessions,
|
|
38107
38174
|
sessionsChangePct: pct(periods.now.all.sessions, periods.before.all.sessions)
|
|
38108
38175
|
},
|
|
38176
|
+
aiAssistants: {
|
|
38177
|
+
sessions: aiNow,
|
|
38178
|
+
previousSessions: aiBefore,
|
|
38179
|
+
sessionsChangePct: pct(aiNow, aiBefore),
|
|
38180
|
+
bySource,
|
|
38181
|
+
topLandingPages: byName(aiPages, "page").slice(0, 20),
|
|
38182
|
+
sources: AI_SOURCES
|
|
38183
|
+
},
|
|
38109
38184
|
notes
|
|
38110
38185
|
};
|
|
38111
38186
|
}
|
|
38112
38187
|
const change$1 = (value) => value === null ? "n/a" : `${value > 0 ? "+" : ""}${value}%`;
|
|
38188
|
+
function aiText(ai) {
|
|
38189
|
+
const list = (rows, key) => rows.slice(0, AI_ROWS).map((row) => `${row[key]} ${row.sessions} (was ${row.previousSessions})`).join(", ");
|
|
38190
|
+
if (!ai.sessions && !ai.previousSessions) return "Visits from AI assistants (ChatGPT, Perplexity, Claude, Gemini, Copilot and others): none in either period.";
|
|
38191
|
+
return [
|
|
38192
|
+
`Visits from AI assistants (ChatGPT, Perplexity, Claude, Gemini, Copilot and others): ${ai.sessions} sessions (was ${ai.previousSessions}, ${change$1(ai.sessionsChangePct)}). By source: ${list(ai.bySource, "source")}.`,
|
|
38193
|
+
ai.topLandingPages.length ? `Top landing pages from AI assistants: ${list(ai.topLandingPages, "page")}.` : "",
|
|
38194
|
+
"Only visits that pass a referrer or utm_source count; AI apps that send none show as Direct."
|
|
38195
|
+
].filter(Boolean).join(" ");
|
|
38196
|
+
}
|
|
38113
38197
|
async function analyticsBeside(access, site, range, before, pages) {
|
|
38114
38198
|
if (!access) return null;
|
|
38115
38199
|
try {
|
|
@@ -38132,7 +38216,7 @@ async function analyticsBeside(access, site, range, before, pages) {
|
|
|
38132
38216
|
"Sessions are Google Analytics’ own count, not Search Console clicks.",
|
|
38133
38217
|
...data.notes,
|
|
38134
38218
|
...others.length ? [`Other properties with a matching web stream, with fewer sessions: ${others.join(", ")}.`] : []
|
|
38135
|
-
].join(" "),
|
|
38219
|
+
].join(" ").concat("\n\n", aiText(data.aiAssistants)),
|
|
38136
38220
|
data: {
|
|
38137
38221
|
...data,
|
|
38138
38222
|
...others.length ? { otherMatches: others } : {}
|
|
@@ -38549,7 +38633,7 @@ async function lookup(domain, key) {
|
|
|
38549
38633
|
signal: AbortSignal.timeout(5e3)
|
|
38550
38634
|
});
|
|
38551
38635
|
if (!response.ok) return null;
|
|
38552
|
-
const body = await response.clone()
|
|
38636
|
+
const body = await readJson(response.clone(), "Ahrefs Domain Rating");
|
|
38553
38637
|
if (cache && !cached) {
|
|
38554
38638
|
const copy = new Response(response.body, response);
|
|
38555
38639
|
copy.headers.set("cache-control", `max-age=${TTL_SECONDS}`);
|
|
@@ -39192,7 +39276,7 @@ async function dataForSeo([login, password], input) {
|
|
|
39192
39276
|
language_code: language ?? mainLanguage(input.country)
|
|
39193
39277
|
};
|
|
39194
39278
|
const post = async (path, task) => {
|
|
39195
|
-
const result = (await (await call("dataforseo", new Request(`https://api.dataforseo.com/${path}`, {
|
|
39279
|
+
const result = (await readJson(await call("dataforseo", new Request(`https://api.dataforseo.com/${path}`, {
|
|
39196
39280
|
method: "POST",
|
|
39197
39281
|
headers: {
|
|
39198
39282
|
authorization: `Basic ${btoa(`${login}:${password}`)}`,
|
|
@@ -39202,7 +39286,7 @@ async function dataForSeo([login, password], input) {
|
|
|
39202
39286
|
...market,
|
|
39203
39287
|
...task
|
|
39204
39288
|
}])
|
|
39205
|
-
})))
|
|
39289
|
+
})), "DataForSEO")).tasks?.[0];
|
|
39206
39290
|
if (!result || result.status_code >= 4e4) throw new CommandError("PROVIDER_ERROR", `dataforseo: ${result?.status_message ?? "no task result"}`);
|
|
39207
39291
|
return result.result?.[0]?.items ?? [];
|
|
39208
39292
|
};
|
|
@@ -39283,10 +39367,10 @@ async function ahrefs(key, input) {
|
|
|
39283
39367
|
const get = async (path, params) => {
|
|
39284
39368
|
const url = new URL(`https://api.ahrefs.com/v3/${path}`);
|
|
39285
39369
|
for (const [name, value] of Object.entries(params)) url.searchParams.set(name, value);
|
|
39286
|
-
return
|
|
39370
|
+
return readJson(await call("ahrefs", new Request(url, { headers: {
|
|
39287
39371
|
authorization: `Bearer ${key}`,
|
|
39288
39372
|
accept: "application/json"
|
|
39289
|
-
} })))
|
|
39373
|
+
} })), "Ahrefs");
|
|
39290
39374
|
};
|
|
39291
39375
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
39292
39376
|
if (input.action === "keyword_metrics") return ((await get("keywords-explorer/overview", {
|
|
@@ -39926,8 +40010,7 @@ async function runSharedTool(tool, surface, input) {
|
|
|
39926
40010
|
const MCP_INSTRUCTIONS = `SEO Skill reads the person's own Search Console, Google Analytics, Bing Webmaster Tools, Clicky and optional keyword accounts, and returns facts with their dates and limits. It gives no advice.
|
|
39927
40011
|
1. seo_sites: the properties the connected accounts can read.
|
|
39928
40012
|
2. seo_report: the main report. Run it without a section first; it shows the overview and every section's count and top rows. Then ask for the one section you need.
|
|
39929
|
-
3. seo_page for one URL, seo_compare for before and after a date, seo_index for pages that lost their clicks, seo_research for keyword, competitor and backlink data from the DataForSEO or Ahrefs key the person saved, seo_data for raw rows, including Google Analytics, Bing and Clicky
|
|
39930
|
-
Show the person the facts that matter, for example in a table or a small dashboard, and decide with them what to change. Do not edit pages without their approval.`;
|
|
40013
|
+
3. seo_page for one URL, seo_compare for before and after a date, seo_index for pages that lost their clicks, seo_research for keyword, competitor and backlink data from the DataForSEO or Ahrefs key the person saved, seo_data for raw rows, including Google Analytics, Bing and Clicky.`;
|
|
39931
40014
|
const mcpCall = (command, args) => {
|
|
39932
40015
|
const name = `seo_${command.replace("-", "_")}`;
|
|
39933
40016
|
const shown = Object.entries(args ?? {}).filter(([, value]) => value !== void 0);
|