seo 0.8.2 → 0.8.3
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 +85 -85
- package/dist/cli.js.map +1 -1
- package/dist/{dist-DkegfPAq.js → dist-B8MTNp_F.js} +29 -22
- package/dist/{dist-DkegfPAq.js.map → dist-B8MTNp_F.js.map} +1 -1
- package/dist/{dist-BtwI_F7s.js → dist-Bv1nbxq_.js} +1 -1
- package/dist/{esm-DeY7lZeN.js → esm-BZhSLbcQ.js} +2 -2
- package/dist/{esm-DeY7lZeN.js.map → esm-BZhSLbcQ.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +177 -14
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -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 version, r as __commonJSMin, t as load } from "./esm-
|
|
2
|
+
import { i as __toESM, n as version, r as __commonJSMin, t as load } from "./esm-BZhSLbcQ.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";
|
|
@@ -38604,7 +38604,7 @@ async function totals$1(found, access, range, before, pages) {
|
|
|
38604
38604
|
}, { signIn: access.signIn });
|
|
38605
38605
|
const onPages = pages ? [filter("landingPage", pages, "CONTAINS")] : [];
|
|
38606
38606
|
const fromAi = [...onPages, filter("sessionSource", AI_SOURCE_PATTERN, "FULL_REGEXP")];
|
|
38607
|
-
const [channels, google, aiSources, aiPages] = await Promise.all([
|
|
38607
|
+
const [channels, google, aiSources, aiPages, engines] = await Promise.all([
|
|
38608
38608
|
read(["sessionDefaultChannelGroup"], onPages),
|
|
38609
38609
|
read([], [
|
|
38610
38610
|
...onPages,
|
|
@@ -38612,7 +38612,8 @@ async function totals$1(found, access, range, before, pages) {
|
|
|
38612
38612
|
filter("sessionSource", "google")
|
|
38613
38613
|
]),
|
|
38614
38614
|
read(["sessionSource"], fromAi),
|
|
38615
|
-
read(["landingPage"], fromAi)
|
|
38615
|
+
read(["landingPage"], fromAi),
|
|
38616
|
+
read(["sessionSource"], [...onPages, filter("sessionDefaultChannelGroup", "Organic Search")])
|
|
38616
38617
|
]);
|
|
38617
38618
|
const sum = () => ({
|
|
38618
38619
|
google: {
|
|
@@ -38679,6 +38680,7 @@ async function totals$1(found, access, range, before, pages) {
|
|
|
38679
38680
|
})).sort((a, b) => b.sessions - a.sessions || b.previousSessions - a.previousSessions || String(a[key]).localeCompare(String(b[key])));
|
|
38680
38681
|
};
|
|
38681
38682
|
const bySource = byName(aiSources, "source");
|
|
38683
|
+
const byEngine = byName(engines, "source");
|
|
38682
38684
|
const aiNow = bySource.reduce((total, row) => total + row.sessions, 0);
|
|
38683
38685
|
const aiBefore = bySource.reduce((total, row) => total + row.previousSessions, 0);
|
|
38684
38686
|
const data = { metadata: {
|
|
@@ -38687,13 +38689,15 @@ async function totals$1(found, access, range, before, pages) {
|
|
|
38687
38689
|
channels,
|
|
38688
38690
|
google,
|
|
38689
38691
|
aiSources,
|
|
38690
|
-
aiPages
|
|
38692
|
+
aiPages,
|
|
38693
|
+
engines
|
|
38691
38694
|
].some((data) => data.metadata?.subjectToThresholding),
|
|
38692
38695
|
dataLossFromOtherRow: [
|
|
38693
38696
|
channels,
|
|
38694
38697
|
google,
|
|
38695
38698
|
aiSources,
|
|
38696
|
-
aiPages
|
|
38699
|
+
aiPages,
|
|
38700
|
+
engines
|
|
38697
38701
|
].some((data) => data.metadata?.dataLossFromOtherRow)
|
|
38698
38702
|
} };
|
|
38699
38703
|
const notes = [];
|
|
@@ -38722,6 +38726,7 @@ async function totals$1(found, access, range, before, pages) {
|
|
|
38722
38726
|
previousSessions: periods.before.all.sessions,
|
|
38723
38727
|
sessionsChangePct: pct(periods.now.all.sessions, periods.before.all.sessions)
|
|
38724
38728
|
},
|
|
38729
|
+
organicSearchByEngine: byEngine.slice(0, 10),
|
|
38725
38730
|
aiAssistants: {
|
|
38726
38731
|
sessions: aiNow,
|
|
38727
38732
|
previousSessions: aiBefore,
|
|
@@ -38734,6 +38739,11 @@ async function totals$1(found, access, range, before, pages) {
|
|
|
38734
38739
|
};
|
|
38735
38740
|
}
|
|
38736
38741
|
const change$1 = (value) => value === null ? "n/a" : `${value > 0 ? "+" : ""}${value}%`;
|
|
38742
|
+
function engineText(rows) {
|
|
38743
|
+
const shown = rows.filter((row) => row.sessions || row.previousSessions);
|
|
38744
|
+
if (shown.length < 2) return "";
|
|
38745
|
+
return `Organic Search sessions by search engine: ${shown.slice(0, AI_ROWS).map((row) => `${row.source} ${row.sessions} (was ${row.previousSessions})`).join(", ")}.`;
|
|
38746
|
+
}
|
|
38737
38747
|
function aiText(ai) {
|
|
38738
38748
|
const list = (rows, key) => rows.slice(0, AI_ROWS).map((row) => `${row[key]} ${row.sessions} (was ${row.previousSessions})`).join(", ");
|
|
38739
38749
|
if (!ai.sessions && !ai.previousSessions) return "Visits from AI assistants (ChatGPT, Perplexity, Claude, Gemini, Copilot and others): none in either period.";
|
|
@@ -38762,10 +38772,11 @@ async function analyticsBeside(access, site, range, before, pages) {
|
|
|
38762
38772
|
return {
|
|
38763
38773
|
text: [
|
|
38764
38774
|
`Google Analytics, property ${data.propertyName} (${data.property}), matched by its web stream ${data.webStream}${pages ? `, landing pages whose path contains "${pages}"` : ""}: Organic Search sessions from Google ${google.sessions} (was ${google.previousSessions}, ${change$1(google.sessionsChangePct)}), from every search engine ${organic.sessions} (was ${organic.previousSessions}, ${change$1(organic.sessionsChangePct)})${keyEvents}, sessions from all channels ${all.sessions} (was ${all.previousSessions}, ${change$1(all.sessionsChangePct)}).`,
|
|
38775
|
+
engineText(data.organicSearchByEngine),
|
|
38765
38776
|
"Sessions are Google Analytics’ own count, not Search Console clicks.",
|
|
38766
38777
|
...data.notes,
|
|
38767
38778
|
...others.length ? [`Other properties with a matching web stream, with fewer sessions: ${others.join(", ")}.`] : []
|
|
38768
|
-
].join(" ").concat("\n\n", aiText(data.aiAssistants)),
|
|
38779
|
+
].filter(Boolean).join(" ").concat("\n\n", aiText(data.aiAssistants)),
|
|
38769
38780
|
data: {
|
|
38770
38781
|
...data,
|
|
38771
38782
|
...others.length ? { otherMatches: others } : {}
|
|
@@ -39000,6 +39011,102 @@ async function runBingRows(input, deps) {
|
|
|
39000
39011
|
}
|
|
39001
39012
|
};
|
|
39002
39013
|
}
|
|
39014
|
+
function moved(rows, range, before, name, limit) {
|
|
39015
|
+
const now = sumWeeks(rows, range);
|
|
39016
|
+
const then = sumWeeks(rows, before);
|
|
39017
|
+
const old = new Map(then.rows.map((row) => [row.key, row]));
|
|
39018
|
+
const seen = new Set(now.rows.map((row) => row.key));
|
|
39019
|
+
const all = now.rows.map((row) => {
|
|
39020
|
+
const earlier = old.get(row.key);
|
|
39021
|
+
return {
|
|
39022
|
+
[name]: row.key,
|
|
39023
|
+
clicks: row.clicks,
|
|
39024
|
+
previousClicks: earlier ? earlier.clicks : null,
|
|
39025
|
+
clicksChange: earlier ? row.clicks - earlier.clicks : null,
|
|
39026
|
+
impressions: row.impressions,
|
|
39027
|
+
position: row.position
|
|
39028
|
+
};
|
|
39029
|
+
});
|
|
39030
|
+
const by = (sign) => all.filter((row) => row.clicksChange !== null && sign * row.clicksChange > 0).sort((a, b) => sign * (b.clicksChange - a.clicksChange) || String(a[name]).localeCompare(String(b[name]))).slice(0, limit);
|
|
39031
|
+
return {
|
|
39032
|
+
top: all.slice(0, limit),
|
|
39033
|
+
biggestLosses: by(-1),
|
|
39034
|
+
biggestGains: by(1),
|
|
39035
|
+
leftTopList: then.rows.filter((row) => !seen.has(row.key)).slice(0, limit).map((row) => ({
|
|
39036
|
+
[name]: row.key,
|
|
39037
|
+
previousClicks: row.clicks
|
|
39038
|
+
})),
|
|
39039
|
+
weeks: now.weeks,
|
|
39040
|
+
previousWeeks: then.weeks
|
|
39041
|
+
};
|
|
39042
|
+
}
|
|
39043
|
+
async function bingOverview(source, range, before) {
|
|
39044
|
+
if (!source.call) throw new CommandError("NOT_CONNECTED", source.missing);
|
|
39045
|
+
const [totals, queries, pages] = await Promise.all([
|
|
39046
|
+
bingTotals(source, range, before),
|
|
39047
|
+
bingQueries(source.call, source.site),
|
|
39048
|
+
bingPages(source.call, source.site)
|
|
39049
|
+
]);
|
|
39050
|
+
if ("missing" in totals) throw new CommandError("NOT_CONNECTED", totals.missing);
|
|
39051
|
+
const { current: now, previous: then } = totals;
|
|
39052
|
+
const q = moved(queries.rows, range, before, "query", 15);
|
|
39053
|
+
const p = moved(pages.rows, range, before, "page", 15);
|
|
39054
|
+
return {
|
|
39055
|
+
bingSite: source.site,
|
|
39056
|
+
range,
|
|
39057
|
+
comparisonRange: before,
|
|
39058
|
+
summary: {
|
|
39059
|
+
clicks: now.clicks,
|
|
39060
|
+
previousClicks: then.clicks,
|
|
39061
|
+
clicksChangePct: pct(now.clicks, then.clicks),
|
|
39062
|
+
impressions: now.impressions,
|
|
39063
|
+
previousImpressions: then.impressions,
|
|
39064
|
+
impressionsChangePct: pct(now.impressions, then.impressions),
|
|
39065
|
+
days: now.days,
|
|
39066
|
+
previousDays: then.days
|
|
39067
|
+
},
|
|
39068
|
+
pages: p,
|
|
39069
|
+
queries: q,
|
|
39070
|
+
caveats: [
|
|
39071
|
+
...now.days < now.expectedDays || then.days < then.expectedDays ? [`Bing has data for ${now.days} of ${now.expectedDays} days (earlier period ${then.days} of ${then.expectedDays}), so its totals are partial.`] : [],
|
|
39072
|
+
`Totals are Bing's daily site totals. Page and query rows are Bing's weekly top rows: weeks ${q.weeks.join(", ") || "none"} against ${q.previousWeeks.join(", ") || "none"}.`,
|
|
39073
|
+
...BING_LIMITS.slice(1)
|
|
39074
|
+
]
|
|
39075
|
+
};
|
|
39076
|
+
}
|
|
39077
|
+
function bingOverviewText(report, heading, origin, rowsPerList) {
|
|
39078
|
+
const range = report.range;
|
|
39079
|
+
const before = report.comparisonRange;
|
|
39080
|
+
const t = report.summary;
|
|
39081
|
+
const columns = (name) => [
|
|
39082
|
+
name,
|
|
39083
|
+
"clicks",
|
|
39084
|
+
"previousClicks",
|
|
39085
|
+
"clicksChange",
|
|
39086
|
+
"impressions",
|
|
39087
|
+
"position"
|
|
39088
|
+
];
|
|
39089
|
+
const part = (title, rows, name) => {
|
|
39090
|
+
const list = rows ?? [];
|
|
39091
|
+
return list.length ? `${title}:\n${table(list.slice(0, rowsPerList), columns(name), origin)}` : "";
|
|
39092
|
+
};
|
|
39093
|
+
const group = (key, name) => {
|
|
39094
|
+
const lists = report[key];
|
|
39095
|
+
return [
|
|
39096
|
+
part(`Biggest Bing ${key === "pages" ? "page" : "query"} click losses`, lists.biggestLosses, name),
|
|
39097
|
+
part(`Biggest Bing ${key === "pages" ? "page" : "query"} click gains`, lists.biggestGains, name),
|
|
39098
|
+
lists.leftTopList?.length ? `Left Bing's top ${key} list (now unknown, not zero): ${lists.leftTopList.slice(0, rowsPerList).map((row) => `${row[name]} (had ${row.previousClicks} clicks)`).join(", ")}.` : "",
|
|
39099
|
+
rowsPerList >= 10 ? part(`Top Bing ${key}`, lists.top, name) : ""
|
|
39100
|
+
].filter(Boolean).join("\n\n");
|
|
39101
|
+
};
|
|
39102
|
+
return [
|
|
39103
|
+
`${heading}, ${range.startDate} to ${range.endDate} against ${before.startDate} to ${before.endDate}.`,
|
|
39104
|
+
t.days || t.previousDays ? `Bing totals: clicks ${t.clicks} (was ${t.previousClicks}, ${change(t.clicks, t.previousClicks)}), impressions ${t.impressions} (was ${t.previousImpressions}, ${change(t.impressions, t.previousImpressions)}).` : "Bing totals: unknown. Bing has no daily data for these dates.",
|
|
39105
|
+
group("pages", "page"),
|
|
39106
|
+
group("queries", "query"),
|
|
39107
|
+
`Limits:\n${report.caveats.map((line) => `- ${line}`).join("\n")}`
|
|
39108
|
+
].filter(Boolean).join("\n\n");
|
|
39109
|
+
}
|
|
39003
39110
|
//#endregion
|
|
39004
39111
|
//#region packages/core/dist/commands/clicky.js
|
|
39005
39112
|
const CLICKY_KEY_PREFIX = "x-clicky-";
|
|
@@ -39732,6 +39839,24 @@ async function enginesLine(overview, bing) {
|
|
|
39732
39839
|
return `Bing: could not be read. ${error instanceof Error ? error.message : String(error)}`;
|
|
39733
39840
|
}
|
|
39734
39841
|
}
|
|
39842
|
+
async function bingLead(overview, bing, origin, rowsPerList) {
|
|
39843
|
+
const range = overview.range;
|
|
39844
|
+
const before = overview.comparisonRange;
|
|
39845
|
+
const google = overview.summary.clicks;
|
|
39846
|
+
try {
|
|
39847
|
+
const source = await bing();
|
|
39848
|
+
if (!source.call) return null;
|
|
39849
|
+
const totals = await bingTotals(source, range, before);
|
|
39850
|
+
if ("missing" in totals || totals.current.clicks <= google) return null;
|
|
39851
|
+
const report = await bingOverview(source, range, before);
|
|
39852
|
+
return {
|
|
39853
|
+
text: `## Bing\n${bingOverviewText(report, `Bing sends more clicks than Google here, so here is Bing's overview of ${totals.site}`, origin, rowsPerList)}`,
|
|
39854
|
+
data: report
|
|
39855
|
+
};
|
|
39856
|
+
} catch {
|
|
39857
|
+
return null;
|
|
39858
|
+
}
|
|
39859
|
+
}
|
|
39735
39860
|
async function build(read, site, section, range, now, bing) {
|
|
39736
39861
|
const dependencies = {
|
|
39737
39862
|
searchAnalytics: read,
|
|
@@ -39851,6 +39976,8 @@ async function allSections(input, deps) {
|
|
|
39851
39976
|
if (!overview || overview instanceof Error) throw overview ?? /* @__PURE__ */ new Error("The overview did not run.");
|
|
39852
39977
|
const parts = [overviewText(overview, `Report on ${input.site}`, origin, 5)];
|
|
39853
39978
|
parts.push(deps.bing ? await enginesLine(overview, bing) : NO_BING_HERE);
|
|
39979
|
+
const bingFirst = deps.bing ? await bingLead(overview, bing, origin, 5) : null;
|
|
39980
|
+
if (bingFirst) parts.push(bingFirst.text);
|
|
39854
39981
|
const ga = await analytics;
|
|
39855
39982
|
if (ga) parts.push(ga.text);
|
|
39856
39983
|
const brand = rest.find((run) => !(run instanceof Error) && brandNote(run) !== null);
|
|
@@ -39883,6 +40010,7 @@ async function allSections(input, deps) {
|
|
|
39883
40010
|
site: input.site,
|
|
39884
40011
|
overview,
|
|
39885
40012
|
...ga ? { analytics: ga.data } : {},
|
|
40013
|
+
...bingFirst ? { bingOverview: bingFirst.data } : {},
|
|
39886
40014
|
sections
|
|
39887
40015
|
}
|
|
39888
40016
|
};
|
|
@@ -39899,10 +40027,13 @@ async function oneSection(input, deps) {
|
|
|
39899
40027
|
if (section === "overview") {
|
|
39900
40028
|
const ga = await analyticsBeside(deps.analytics, site, range, earlierRange(range));
|
|
39901
40029
|
if (ga) report.analytics = ga.data;
|
|
40030
|
+
const bingFirst = deps.bing ? await bingLead(report, bing, origin, 10) : null;
|
|
40031
|
+
if (bingFirst) report.bingOverview = bingFirst.data;
|
|
39902
40032
|
if (detail !== "full") return {
|
|
39903
40033
|
text: [
|
|
39904
40034
|
overviewText(report, `overview of ${site}`, origin, 10),
|
|
39905
40035
|
deps.bing ? await enginesLine(report, bing) : NO_BING_HERE,
|
|
40036
|
+
bingFirst?.text,
|
|
39906
40037
|
ga?.text,
|
|
39907
40038
|
`More: ${deps.call("report", {
|
|
39908
40039
|
site,
|
|
@@ -39967,6 +40098,27 @@ function runReport$1(input, deps) {
|
|
|
39967
40098
|
section: input.section
|
|
39968
40099
|
}, deps) : allSections(input, deps);
|
|
39969
40100
|
}
|
|
40101
|
+
async function runBingReport(input, deps) {
|
|
40102
|
+
const range = dateWindow(input, DEFAULT_DAYS.overview);
|
|
40103
|
+
const before = earlierRange(range);
|
|
40104
|
+
const source = await bingFor(deps, input.site);
|
|
40105
|
+
if (!source.call) throw new CommandError("NOT_CONNECTED", source.missing);
|
|
40106
|
+
const [report, ga] = await Promise.all([bingOverview(source, range, before), analyticsBeside(deps.analytics, source.site, range, before)]);
|
|
40107
|
+
if (ga) report.analytics = ga.data;
|
|
40108
|
+
return {
|
|
40109
|
+
text: [
|
|
40110
|
+
bingOverviewText(report, `Bing report on ${source.site}. No Search Console property covers this site, so this report uses Bing`, originOf(source.site), 10),
|
|
40111
|
+
ga?.text,
|
|
40112
|
+
`More: ${deps.call("data", {
|
|
40113
|
+
source: "bing",
|
|
40114
|
+
site: input.site,
|
|
40115
|
+
dimensions: "page"
|
|
40116
|
+
})} for every Bing page, or the same with query.`,
|
|
40117
|
+
FACTS_ONLY.replace("Search Console", "Bing Webmaster Tools")
|
|
40118
|
+
].filter(Boolean).join("\n\n"),
|
|
40119
|
+
data: report
|
|
40120
|
+
};
|
|
40121
|
+
}
|
|
39970
40122
|
//#endregion
|
|
39971
40123
|
//#region packages/core/dist/commands/research-providers.js
|
|
39972
40124
|
function need(headers, provider) {
|
|
@@ -40607,14 +40759,25 @@ const SHARED_TOOLS = [
|
|
|
40607
40759
|
...dates,
|
|
40608
40760
|
...detail
|
|
40609
40761
|
}),
|
|
40610
|
-
run: async (surface, input) =>
|
|
40611
|
-
|
|
40612
|
-
|
|
40613
|
-
|
|
40614
|
-
|
|
40615
|
-
|
|
40616
|
-
|
|
40617
|
-
|
|
40762
|
+
run: async (surface, input) => {
|
|
40763
|
+
const resolved = await surface.resolveSite(input.site).catch((error) => error instanceof Error ? error : new Error(String(error)));
|
|
40764
|
+
if (resolved instanceof Error && !input.section && surface.deps.bing) return runBingReport({
|
|
40765
|
+
site: input.site,
|
|
40766
|
+
startDate: input.start_date,
|
|
40767
|
+
endDate: input.end_date
|
|
40768
|
+
}, surface.deps).catch(() => {
|
|
40769
|
+
throw resolved;
|
|
40770
|
+
});
|
|
40771
|
+
if (resolved instanceof Error && input.section !== "bing") throw resolved;
|
|
40772
|
+
return runReport$1({
|
|
40773
|
+
site: resolved instanceof Error ? input.site : resolved,
|
|
40774
|
+
section: input.section,
|
|
40775
|
+
startDate: input.start_date,
|
|
40776
|
+
endDate: input.end_date,
|
|
40777
|
+
detail: input.detail,
|
|
40778
|
+
offset: input.offset
|
|
40779
|
+
}, surface.deps);
|
|
40780
|
+
}
|
|
40618
40781
|
}),
|
|
40619
40782
|
tool({
|
|
40620
40783
|
name: "seo_page",
|