seo 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PRIVACY.md +39 -11
- package/README.md +8 -8
- package/dist/cli.js +85 -83
- package/dist/cli.js.map +1 -1
- package/dist/{dist-DBHFmJmr.js → dist-C_Ezkcm5.js} +1 -1
- package/dist/{dist-DV2Z-HIp.js → dist-nmppE37y.js} +85 -84
- package/dist/dist-nmppE37y.js.map +1 -0
- package/dist/index.d.ts +95 -45
- package/dist/index.js +625 -327
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +1720 -1196
- package/dist/mcp.js.map +1 -1
- package/dist/{package-D46BDwUk.js → package-CwLtPH6B.js} +2 -2
- package/dist/{package-D46BDwUk.js.map → package-CwLtPH6B.js.map} +1 -1
- package/package.json +2 -2
- package/skills/seo/SKILL.md +19 -18
- package/dist/dist-DV2Z-HIp.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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-CwLtPH6B.js";
|
|
3
3
|
import { SEO_PROVIDER_API_VERSION } from "./provider-sdk.js";
|
|
4
4
|
import crypto, { createHash, randomUUID, sign } from "node:crypto";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import {
|
|
7
|
-
import { arch, homedir, platform, totalmem } from "node:os";
|
|
6
|
+
import { arch, homedir, hostname, platform, totalmem } from "node:os";
|
|
8
7
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
8
|
+
import open from "open";
|
|
9
|
+
import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
9
10
|
import lockfile from "proper-lockfile";
|
|
10
11
|
import http from "node:http";
|
|
11
|
-
import open from "open";
|
|
12
12
|
import { Headers as Headers$1, fetch as fetch$1 } from "undici";
|
|
13
13
|
import Libsql from "libsql";
|
|
14
14
|
import { Parser } from "htmlparser2";
|
|
@@ -1596,6 +1596,327 @@ async function runAnalyticsReport(gfetch, propertyId, body, options) {
|
|
|
1596
1596
|
});
|
|
1597
1597
|
}
|
|
1598
1598
|
//#endregion
|
|
1599
|
+
//#region packages/core/src/bing/api.ts
|
|
1600
|
+
const BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
1601
|
+
const MAX_BYTES$2 = 8e6;
|
|
1602
|
+
function bingCall(auth, options) {
|
|
1603
|
+
return async (method, params = {}) => {
|
|
1604
|
+
const url = new URL(`${BASE}/${method}`);
|
|
1605
|
+
for (const [name, value] of Object.entries(params)) url.searchParams.set(name, value);
|
|
1606
|
+
const headers = { accept: "application/json" };
|
|
1607
|
+
if ("apiKey" in auth) url.searchParams.set("apikey", auth.apiKey);
|
|
1608
|
+
else headers.authorization = `Bearer ${await auth.accessToken()}`;
|
|
1609
|
+
let response;
|
|
1610
|
+
try {
|
|
1611
|
+
response = await fetch(url, {
|
|
1612
|
+
headers,
|
|
1613
|
+
signal: AbortSignal.timeout(3e4)
|
|
1614
|
+
});
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
throw new SeoError("OPTIONAL_PROVIDER_UNAVAILABLE", `Bing Webmaster did not respond: ${error instanceof Error ? error.message : String(error)}`);
|
|
1617
|
+
}
|
|
1618
|
+
if (!response.ok) {
|
|
1619
|
+
const detail = (await response.text().catch(() => "")).slice(0, 200);
|
|
1620
|
+
if (response.status === 401 || /invalidapikey/i.test(detail)) throw new SeoError("AUTH_REQUIRED", `Bing Webmaster rejected the sign-in. ${options.signIn}`);
|
|
1621
|
+
if (response.status === 403 || /notauthorized/i.test(detail)) throw new SeoError("ACCESS_DENIED", "Bing Webmaster denied access to this site. It must be verified in the connected Bing account.");
|
|
1622
|
+
if (/ThrottleIP/i.test(detail)) throw new SeoError("RATE_LIMITED", "Bing Webmaster is blocking requests from this network (ThrottleIP). Run the same report with the CLI on your own machine, where Bing answers.");
|
|
1623
|
+
if (response.status === 429 || /throttle/i.test(detail)) throw new SeoError("RATE_LIMITED", "Bing Webmaster is limiting requests. Try again in a few minutes.");
|
|
1624
|
+
throw new SeoError("OPTIONAL_PROVIDER_UNAVAILABLE", `Bing Webmaster answered ${response.status}. ${detail}`.trim());
|
|
1625
|
+
}
|
|
1626
|
+
return (await readJson(response, "Bing Webmaster", { maxBytes: MAX_BYTES$2 })).d;
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
//#endregion
|
|
1630
|
+
//#region packages/core/src/gsc/dates.ts
|
|
1631
|
+
const GSC_TIME_ZONE = "America/Los_Angeles";
|
|
1632
|
+
const GSC_FINAL_DATA_LAG_DAYS = 4;
|
|
1633
|
+
function dateInTimeZone(now, timeZone) {
|
|
1634
|
+
const parts = Object.fromEntries(new Intl.DateTimeFormat("en-US", {
|
|
1635
|
+
timeZone,
|
|
1636
|
+
year: "numeric",
|
|
1637
|
+
month: "2-digit",
|
|
1638
|
+
day: "2-digit"
|
|
1639
|
+
}).formatToParts(now).map((part) => [part.type, part.value]));
|
|
1640
|
+
return new Date(Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day)));
|
|
1641
|
+
}
|
|
1642
|
+
function shiftIsoDate(date, days) {
|
|
1643
|
+
const value = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
1644
|
+
value.setUTCDate(value.getUTCDate() + days);
|
|
1645
|
+
return value.toISOString().slice(0, 10);
|
|
1646
|
+
}
|
|
1647
|
+
function latestFinalGscDate(now = /* @__PURE__ */ new Date()) {
|
|
1648
|
+
const pacificToday = dateInTimeZone(now, GSC_TIME_ZONE);
|
|
1649
|
+
pacificToday.setUTCDate(pacificToday.getUTCDate() - 4);
|
|
1650
|
+
return pacificToday.toISOString().slice(0, 10);
|
|
1651
|
+
}
|
|
1652
|
+
function finalGscDateRange(days = 28, now = /* @__PURE__ */ new Date()) {
|
|
1653
|
+
const endDate = latestFinalGscDate(now);
|
|
1654
|
+
return {
|
|
1655
|
+
startDate: shiftIsoDate(endDate, -(days - 1)),
|
|
1656
|
+
endDate
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
//#endregion
|
|
1660
|
+
//#region packages/core/src/analyze/site-diagnostics/quick-wins-report-input.ts
|
|
1661
|
+
function integerOption$1(input) {
|
|
1662
|
+
if (input.value === void 0) return input.fallback;
|
|
1663
|
+
if (!Number.isInteger(input.value) || input.value < input.minimum || input.value > input.maximum) throw new SeoError("INVALID_INPUT", `${input.label} must be a whole number between ${input.minimum} and ${input.maximum}.`);
|
|
1664
|
+
return input.value;
|
|
1665
|
+
}
|
|
1666
|
+
function defaultDateRange$2(days, now) {
|
|
1667
|
+
return finalGscDateRange(days, now);
|
|
1668
|
+
}
|
|
1669
|
+
function explicitDateRange(input, maximumDays) {
|
|
1670
|
+
if (!input.startDate && !input.endDate) return void 0;
|
|
1671
|
+
if (!input.startDate || !input.endDate) throw new SeoError("INVALID_INPUT", "Start date and end date must be provided together.");
|
|
1672
|
+
const pattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
1673
|
+
const start = /* @__PURE__ */ new Date(`${input.startDate}T00:00:00.000Z`);
|
|
1674
|
+
const end = /* @__PURE__ */ new Date(`${input.endDate}T00:00:00.000Z`);
|
|
1675
|
+
if (!pattern.test(input.startDate) || !pattern.test(input.endDate) || Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()) || start.toISOString().slice(0, 10) !== input.startDate || end.toISOString().slice(0, 10) !== input.endDate || start > end) throw new SeoError("INVALID_INPUT", "Start date and end date must be valid YYYY-MM-DD values in ascending order.");
|
|
1676
|
+
const days = Math.floor((end.getTime() - start.getTime()) / 864e5) + 1;
|
|
1677
|
+
if (days > maximumDays) throw new SeoError("INVALID_INPUT", `Date range must not exceed ${maximumDays} days.`);
|
|
1678
|
+
return {
|
|
1679
|
+
range: {
|
|
1680
|
+
startDate: input.startDate,
|
|
1681
|
+
endDate: input.endDate
|
|
1682
|
+
},
|
|
1683
|
+
days
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
//#endregion
|
|
1687
|
+
//#region packages/core/src/analyze/site-diagnostics/decay-report-input.ts
|
|
1688
|
+
function decayNumberOption(input) {
|
|
1689
|
+
const value = input.value ?? input.fallback;
|
|
1690
|
+
if (!Number.isFinite(value) || value < input.minimum || value > input.maximum) throw new SeoError("INVALID_INPUT", `${input.label} must be between ${input.minimum} and ${input.maximum}.`);
|
|
1691
|
+
return value;
|
|
1692
|
+
}
|
|
1693
|
+
function rangeDays$1(range) {
|
|
1694
|
+
return Math.floor(((/* @__PURE__ */ new Date(`${range.endDate}T00:00:00.000Z`)).getTime() - (/* @__PURE__ */ new Date(`${range.startDate}T00:00:00.000Z`)).getTime()) / 864e5) + 1;
|
|
1695
|
+
}
|
|
1696
|
+
function dateString(date) {
|
|
1697
|
+
return date.toISOString().slice(0, 10);
|
|
1698
|
+
}
|
|
1699
|
+
function subtractDays$1(value, days) {
|
|
1700
|
+
const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
1701
|
+
date.setUTCDate(date.getUTCDate() - days);
|
|
1702
|
+
return dateString(date);
|
|
1703
|
+
}
|
|
1704
|
+
function priorYearDate(value) {
|
|
1705
|
+
const source = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
1706
|
+
const year = source.getUTCFullYear() - 1;
|
|
1707
|
+
const month = source.getUTCMonth();
|
|
1708
|
+
const day = source.getUTCDate();
|
|
1709
|
+
const candidate = new Date(Date.UTC(year, month, day));
|
|
1710
|
+
if (candidate.getUTCMonth() !== month) return dateString(new Date(Date.UTC(year, month + 1, 0)));
|
|
1711
|
+
return dateString(candidate);
|
|
1712
|
+
}
|
|
1713
|
+
function subtractCalendarMonths(date, months) {
|
|
1714
|
+
const day = date.getUTCDate();
|
|
1715
|
+
const target = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() - months, 1));
|
|
1716
|
+
const lastDay = new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0)).getUTCDate();
|
|
1717
|
+
target.setUTCDate(Math.min(day, lastDay));
|
|
1718
|
+
return dateString(target);
|
|
1719
|
+
}
|
|
1720
|
+
function validateDecayRanges(input) {
|
|
1721
|
+
const latestFinalDate = defaultDateRange$2(1, input.now).endDate;
|
|
1722
|
+
if (input.current.endDate > latestFinalDate) throw new SeoError("INVALID_INPUT", `Decay end date must be ${latestFinalDate} or earlier so both GSC windows use final data.`);
|
|
1723
|
+
const pacificToday = /* @__PURE__ */ new Date(`${latestFinalDate}T00:00:00.000Z`);
|
|
1724
|
+
pacificToday.setUTCDate(pacificToday.getUTCDate() + 4);
|
|
1725
|
+
const retentionStart = subtractCalendarMonths(pacificToday, 16);
|
|
1726
|
+
if (input.previous.startDate < retentionStart) throw new SeoError("INVALID_INPUT", `Comparison range starts before Search Console's rolling 16-month API history (${retentionStart}). Choose a shorter range or use previously stored data.`);
|
|
1727
|
+
}
|
|
1728
|
+
function decayComparisonRange(current, comparison = "previous-period") {
|
|
1729
|
+
const days = rangeDays$1(current);
|
|
1730
|
+
if (comparison === "year-over-year") {
|
|
1731
|
+
const endDate = priorYearDate(current.endDate);
|
|
1732
|
+
return {
|
|
1733
|
+
startDate: subtractDays$1(endDate, days - 1),
|
|
1734
|
+
endDate
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
const endDate = subtractDays$1(current.startDate, 1);
|
|
1738
|
+
return {
|
|
1739
|
+
startDate: subtractDays$1(endDate, days - 1),
|
|
1740
|
+
endDate
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
//#endregion
|
|
1744
|
+
//#region packages/core/src/updates/search-updates.ts
|
|
1745
|
+
function classifyUpdate(name, product) {
|
|
1746
|
+
const lower = `${name} ${product}`.toLowerCase();
|
|
1747
|
+
if (lower.includes("core")) return "core";
|
|
1748
|
+
if (lower.includes("spam")) return "spam";
|
|
1749
|
+
if (lower.includes("discover")) return "discover";
|
|
1750
|
+
if (lower.includes("ranking")) return "ranking";
|
|
1751
|
+
if (lower.includes("index")) return "indexing";
|
|
1752
|
+
if (lower.includes("serving")) return "serving";
|
|
1753
|
+
return "other";
|
|
1754
|
+
}
|
|
1755
|
+
function searchUpdatesFromIncidents(incidents, input = {}) {
|
|
1756
|
+
const product = input.product?.toLowerCase();
|
|
1757
|
+
return incidents.filter((incident) => !product || incident.service_name.toLowerCase() === product).map((incident) => {
|
|
1758
|
+
const status = incident.end ? "complete" : incident.status_impact === "SERVICE_INFORMATION" ? "rolling-out" : "incident";
|
|
1759
|
+
return {
|
|
1760
|
+
id: incident.id,
|
|
1761
|
+
name: incident.external_desc,
|
|
1762
|
+
type: classifyUpdate(incident.external_desc, incident.service_name),
|
|
1763
|
+
product: incident.service_name,
|
|
1764
|
+
start: incident.begin,
|
|
1765
|
+
end: incident.end,
|
|
1766
|
+
status,
|
|
1767
|
+
sourceUrl: `https://status.search.google.com/${incident.uri}`
|
|
1768
|
+
};
|
|
1769
|
+
}).slice(0, input.limit ?? 25);
|
|
1770
|
+
}
|
|
1771
|
+
function findOverlappingSearchUpdates(input) {
|
|
1772
|
+
const paddingMs = (input.paddingDays ?? 3) * 864e5;
|
|
1773
|
+
const windowStart = new Date(input.startDate).getTime() - paddingMs;
|
|
1774
|
+
const windowEnd = new Date(input.endDate).getTime() + paddingMs;
|
|
1775
|
+
return input.updates.filter((update) => {
|
|
1776
|
+
const updateStart = new Date(update.start).getTime();
|
|
1777
|
+
const updateEnd = update.end ? new Date(update.end).getTime() : Date.now();
|
|
1778
|
+
return updateStart <= windowEnd && updateEnd >= windowStart;
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
//#endregion
|
|
1782
|
+
//#region packages/core/src/commands/types.ts
|
|
1783
|
+
var CommandError = class extends Error {
|
|
1784
|
+
code;
|
|
1785
|
+
constructor(code, message) {
|
|
1786
|
+
super(message);
|
|
1787
|
+
this.code = code;
|
|
1788
|
+
}
|
|
1789
|
+
};
|
|
1790
|
+
const ADVICE_KEYS = /^(recommendations?|actions?|advice|suggested[A-Z]\w*|nextSteps?)$/;
|
|
1791
|
+
function factsOnly(value) {
|
|
1792
|
+
if (Array.isArray(value)) return value.map(factsOnly);
|
|
1793
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).filter(([key]) => !ADVICE_KEYS.test(key)).map(([key, child]) => [key, factsOnly(child)]));
|
|
1794
|
+
return value;
|
|
1795
|
+
}
|
|
1796
|
+
function matchProperty(names, site) {
|
|
1797
|
+
if (names.includes(site)) return site;
|
|
1798
|
+
let host;
|
|
1799
|
+
let origin;
|
|
1800
|
+
try {
|
|
1801
|
+
const url = new URL(site.includes("://") ? site : `https://${site.replace(/^sc-domain:/, "")}`);
|
|
1802
|
+
host = url.hostname.replace(/^www\./, "");
|
|
1803
|
+
origin = url.origin;
|
|
1804
|
+
} catch {
|
|
1805
|
+
return null;
|
|
1806
|
+
}
|
|
1807
|
+
const domain = names.filter((name) => name.startsWith("sc-domain:")).map((name) => name.slice(10)).filter((name) => host === name || host.endsWith(`.${name}`)).sort((a, b) => b.length - a.length)[0];
|
|
1808
|
+
if (domain) return `sc-domain:${domain}`;
|
|
1809
|
+
return names.find((name) => name === `${origin}/` || name === `${origin.replace("://", "://www.")}/` || name === `${origin.replace("://www.", "://")}/`) ?? null;
|
|
1810
|
+
}
|
|
1811
|
+
//#endregion
|
|
1812
|
+
//#region packages/core/src/commands/sites.ts
|
|
1813
|
+
async function readableSites(google, signIn) {
|
|
1814
|
+
return (await listSearchConsoleSites(google, { signIn })).filter((entry) => entry.permissionLevel !== "siteUnverifiedUser").map((entry) => ({
|
|
1815
|
+
siteUrl: entry.siteUrl,
|
|
1816
|
+
permissionLevel: entry.permissionLevel ?? ""
|
|
1817
|
+
})).sort((a, b) => a.siteUrl.localeCompare(b.siteUrl));
|
|
1818
|
+
}
|
|
1819
|
+
//#endregion
|
|
1820
|
+
//#region packages/core/src/commands/linked.ts
|
|
1821
|
+
const MAX_ROWS$1 = 1e5;
|
|
1822
|
+
const PERMISSION_RANK = {
|
|
1823
|
+
siteOwner: 0,
|
|
1824
|
+
siteFullUser: 1,
|
|
1825
|
+
siteRestrictedUser: 2
|
|
1826
|
+
};
|
|
1827
|
+
function bingRouter(calls) {
|
|
1828
|
+
if (calls.length === 0) return null;
|
|
1829
|
+
const only = calls[0];
|
|
1830
|
+
if (calls.length === 1 && only) return only;
|
|
1831
|
+
let lists;
|
|
1832
|
+
const sitesOf = () => {
|
|
1833
|
+
lists ??= Promise.all(calls.map(async (call) => ({
|
|
1834
|
+
call,
|
|
1835
|
+
sites: await call("GetUserSites").then((data) => Array.isArray(data) ? data : []).catch(() => [])
|
|
1836
|
+
})));
|
|
1837
|
+
return lists;
|
|
1838
|
+
};
|
|
1839
|
+
return async (method, params = {}) => {
|
|
1840
|
+
const all = await sitesOf();
|
|
1841
|
+
if (method === "GetUserSites") return all.flatMap((item) => item.sites);
|
|
1842
|
+
return (all.find((item) => item.sites.some((entry) => entry.IsVerified === true && entry.Url === params.siteUrl))?.call ?? only)(method, params);
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
var LinkedAccounts = class {
|
|
1846
|
+
hint;
|
|
1847
|
+
google;
|
|
1848
|
+
bing;
|
|
1849
|
+
sites = /* @__PURE__ */ new Map();
|
|
1850
|
+
owners;
|
|
1851
|
+
constructor(input, hint) {
|
|
1852
|
+
this.hint = hint;
|
|
1853
|
+
this.google = input.google.map((account) => ({
|
|
1854
|
+
email: account.email,
|
|
1855
|
+
google: bearerFetch(account.token)
|
|
1856
|
+
}));
|
|
1857
|
+
this.bing = bingRouter(input.bing);
|
|
1858
|
+
}
|
|
1859
|
+
accounts() {
|
|
1860
|
+
return this.google.map((linked) => ({
|
|
1861
|
+
email: linked.email,
|
|
1862
|
+
google: linked.google
|
|
1863
|
+
}));
|
|
1864
|
+
}
|
|
1865
|
+
sitesOf(linked) {
|
|
1866
|
+
let found = this.sites.get(linked);
|
|
1867
|
+
if (!found) {
|
|
1868
|
+
found = readableSites(linked.google, this.hint.signIn);
|
|
1869
|
+
this.sites.set(linked, found);
|
|
1870
|
+
}
|
|
1871
|
+
return found;
|
|
1872
|
+
}
|
|
1873
|
+
ownerMap() {
|
|
1874
|
+
this.owners ??= (async () => {
|
|
1875
|
+
const lists = await Promise.all(this.google.map(async (linked) => ({
|
|
1876
|
+
linked,
|
|
1877
|
+
sites: await this.sitesOf(linked).catch((error) => {
|
|
1878
|
+
if (this.google.length === 1) throw error;
|
|
1879
|
+
return [];
|
|
1880
|
+
})
|
|
1881
|
+
})));
|
|
1882
|
+
const best = /* @__PURE__ */ new Map();
|
|
1883
|
+
for (const { linked, sites } of lists) for (const site of sites) {
|
|
1884
|
+
const rank = PERMISSION_RANK[site.permissionLevel] ?? 3;
|
|
1885
|
+
const found = best.get(site.siteUrl);
|
|
1886
|
+
if (!found || rank < found.rank) best.set(site.siteUrl, {
|
|
1887
|
+
linked,
|
|
1888
|
+
rank
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
return new Map([...best].map(([site, { linked }]) => [site, linked]));
|
|
1892
|
+
})();
|
|
1893
|
+
return this.owners;
|
|
1894
|
+
}
|
|
1895
|
+
async resolve(site) {
|
|
1896
|
+
if (this.google.length === 0) throw new CommandError("NOT_CONNECTED", `No Google account is connected. ${this.hint.signIn}`);
|
|
1897
|
+
const found = matchProperty([...(await this.ownerMap()).keys()], site);
|
|
1898
|
+
if (found) return found;
|
|
1899
|
+
throw new CommandError("NO_ACCESS", `None of the connected Google accounts (${this.google.map((linked) => linked.email).join(", ")}) has a Search Console property for ${site}. ${this.hint.addGoogle}`);
|
|
1900
|
+
}
|
|
1901
|
+
async owner(site) {
|
|
1902
|
+
const owner = (await this.ownerMap()).get(await this.resolve(site));
|
|
1903
|
+
if (!owner) throw new CommandError("NO_ACCESS", `No access to ${site}.`);
|
|
1904
|
+
return owner;
|
|
1905
|
+
}
|
|
1906
|
+
searchAnalytics() {
|
|
1907
|
+
return async (site, body) => searchAnalyticsRows((await this.owner(site)).google, site, {
|
|
1908
|
+
...body,
|
|
1909
|
+
maxRows: body.maxRows ?? body.rowLimit ?? MAX_ROWS$1
|
|
1910
|
+
}, { signIn: this.hint.signIn });
|
|
1911
|
+
}
|
|
1912
|
+
async inspect(site, url) {
|
|
1913
|
+
return inspectSearchConsoleUrl((await this.owner(site)).google, site, url, {
|
|
1914
|
+
signIn: this.hint.signIn,
|
|
1915
|
+
languageCode: "en-US"
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
};
|
|
1919
|
+
//#endregion
|
|
1599
1920
|
//#region packages/core/src/paths.ts
|
|
1600
1921
|
let memoizedPaths;
|
|
1601
1922
|
function defaultConfigDir(input) {
|
|
@@ -1692,6 +2013,189 @@ function fileSize(path) {
|
|
|
1692
2013
|
}
|
|
1693
2014
|
}
|
|
1694
2015
|
//#endregion
|
|
2016
|
+
//#region packages/core/src/storage/files.ts
|
|
2017
|
+
function ensureParentDir(path, mode = 448) {
|
|
2018
|
+
mkdirSync(dirname(path), {
|
|
2019
|
+
recursive: true,
|
|
2020
|
+
mode
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
function readJsonFile(path) {
|
|
2024
|
+
try {
|
|
2025
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
2026
|
+
} catch {
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
function writeJsonAtomic(path, data, mode) {
|
|
2031
|
+
ensureParentDir(path);
|
|
2032
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
2033
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), {
|
|
2034
|
+
encoding: "utf8",
|
|
2035
|
+
mode
|
|
2036
|
+
});
|
|
2037
|
+
chmodSync(tmp, mode);
|
|
2038
|
+
renameSync(tmp, path);
|
|
2039
|
+
chmodSync(path, mode);
|
|
2040
|
+
}
|
|
2041
|
+
function fileMode(path) {
|
|
2042
|
+
try {
|
|
2043
|
+
return `0${(statSync(path).mode & 511).toString(8)}`;
|
|
2044
|
+
} catch {
|
|
2045
|
+
return "missing";
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
function safeRemove(path) {
|
|
2049
|
+
rmSync(path, {
|
|
2050
|
+
recursive: true,
|
|
2051
|
+
force: true
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
//#endregion
|
|
2055
|
+
//#region packages/core/src/cloud.ts
|
|
2056
|
+
const CLOUD_URL = "https://seoskill.dev";
|
|
2057
|
+
const PRIVATE = 384;
|
|
2058
|
+
const EARLY = 6e4;
|
|
2059
|
+
const linkFile = () => join(getSeoCliPaths().configDir, "cloud.json");
|
|
2060
|
+
const pendingFile = () => join(getSeoCliPaths().configDir, "cloud-login.json");
|
|
2061
|
+
function cloudToken() {
|
|
2062
|
+
const fromEnv = process.env.SEO_TOKEN?.trim();
|
|
2063
|
+
if (fromEnv) return fromEnv;
|
|
2064
|
+
return readJsonFile(linkFile())?.token;
|
|
2065
|
+
}
|
|
2066
|
+
function cloudLinked() {
|
|
2067
|
+
return Boolean(cloudToken());
|
|
2068
|
+
}
|
|
2069
|
+
const CLOUD_SIGN_IN = "Run `seo login` to link this machine to your seoskill.dev account again.";
|
|
2070
|
+
async function call$1(path, init = {}) {
|
|
2071
|
+
const response = await fetch(`${CLOUD_URL}${path}`, {
|
|
2072
|
+
...init,
|
|
2073
|
+
headers: {
|
|
2074
|
+
"content-type": "application/json",
|
|
2075
|
+
...init.token ? { authorization: `Bearer ${init.token}` } : {}
|
|
2076
|
+
}
|
|
2077
|
+
}).catch(() => {
|
|
2078
|
+
throw new SeoError("PROVIDER_UNAVAILABLE", `Could not reach ${CLOUD_URL}. Check the network and try again.`);
|
|
2079
|
+
});
|
|
2080
|
+
const body = await readJson(response, "seoskill.dev").catch(() => ({}));
|
|
2081
|
+
return {
|
|
2082
|
+
ok: response.ok,
|
|
2083
|
+
status: response.status,
|
|
2084
|
+
body
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
let credentials;
|
|
2088
|
+
function expiresSoon(value) {
|
|
2089
|
+
return [...value.google, ...value.bing].some((item) => "expiresAt" in item && item.expiresAt - EARLY < Date.now());
|
|
2090
|
+
}
|
|
2091
|
+
async function cloudCredentials(options = {}) {
|
|
2092
|
+
const current = credentials ? await credentials.catch(() => null) : null;
|
|
2093
|
+
if (current && !expiresSoon(current) && !options.refreshClicky) return current;
|
|
2094
|
+
credentials = (async () => {
|
|
2095
|
+
const token = cloudToken();
|
|
2096
|
+
if (!token) throw new SeoError("AUTH_REQUIRED", notSignedIn());
|
|
2097
|
+
const result = await call$1(`/account/cli/credentials${options.refreshClicky ? "?clicky=refresh" : ""}`, { token });
|
|
2098
|
+
if (result.status === 401) throw new SeoError("AUTH_EXPIRED", `This machine is not linked to a seoskill.dev account any more. ${CLOUD_SIGN_IN}`);
|
|
2099
|
+
if (!result.ok) throw new SeoError("PROVIDER_UNAVAILABLE", `seoskill.dev returned ${result.status}. Try again in a minute.`);
|
|
2100
|
+
return result.body;
|
|
2101
|
+
})();
|
|
2102
|
+
return credentials;
|
|
2103
|
+
}
|
|
2104
|
+
function notSignedIn() {
|
|
2105
|
+
return "Not signed in. Run `seo login` to use your seoskill.dev account on this machine (it prints a link to approve), or `seo auth login` to sign in with Google on this machine only.";
|
|
2106
|
+
}
|
|
2107
|
+
function tokenOf(item, what) {
|
|
2108
|
+
if ("error" in item) throw new SeoError("AUTH_EXPIRED", `${what}: ${item.error}`);
|
|
2109
|
+
return item.token;
|
|
2110
|
+
}
|
|
2111
|
+
async function cloudGoogleToken(email) {
|
|
2112
|
+
const { google, account } = await cloudCredentials();
|
|
2113
|
+
const found = email ? google.find((item) => item.email.toLowerCase() === email.toLowerCase()) : google[0];
|
|
2114
|
+
if (!found) throw new SeoError("AUTH_REQUIRED", email ? `${email} is not linked to your seoskill.dev account. Add it at ${account}#google.` : `No Google account is linked to your seoskill.dev account. Add one at ${account}#google.`);
|
|
2115
|
+
return tokenOf(found, `Google sign-in for ${found.email}`);
|
|
2116
|
+
}
|
|
2117
|
+
async function cloudGoogleEmails() {
|
|
2118
|
+
return (await cloudCredentials()).google.map((item) => item.email);
|
|
2119
|
+
}
|
|
2120
|
+
let linked;
|
|
2121
|
+
async function cloudAccountFor(site) {
|
|
2122
|
+
if (!cloudLinked()) return void 0;
|
|
2123
|
+
linked ??= cloudGoogleEmails().then((emails) => new LinkedAccounts({
|
|
2124
|
+
google: emails.map((email) => ({
|
|
2125
|
+
email,
|
|
2126
|
+
token: () => cloudGoogleToken(email)
|
|
2127
|
+
})),
|
|
2128
|
+
bing: []
|
|
2129
|
+
}, {
|
|
2130
|
+
signIn: CLOUD_SIGN_IN,
|
|
2131
|
+
addGoogle: "Add it on your seoskill.dev account page."
|
|
2132
|
+
}));
|
|
2133
|
+
const accounts = await linked;
|
|
2134
|
+
if (accounts.google.length < 2) return accounts.google[0]?.email;
|
|
2135
|
+
return (await accounts.owner(site)).email;
|
|
2136
|
+
}
|
|
2137
|
+
async function cloudBing() {
|
|
2138
|
+
const { bing, account } = await cloudCredentials();
|
|
2139
|
+
return bingRouter(bing.map((_, index) => bingCall({ accessToken: async () => {
|
|
2140
|
+
const item = (await cloudCredentials()).bing[index];
|
|
2141
|
+
if (!item) throw new SeoError("AUTH_EXPIRED", CLOUD_SIGN_IN);
|
|
2142
|
+
return tokenOf(item, "Bing sign-in");
|
|
2143
|
+
} }, { signIn: `Reconnect Bing at ${account}.` })));
|
|
2144
|
+
}
|
|
2145
|
+
async function cloudKeys() {
|
|
2146
|
+
return (await cloudCredentials()).keys;
|
|
2147
|
+
}
|
|
2148
|
+
async function startCloudLogin() {
|
|
2149
|
+
const result = await call$1("/account/cli/start", {
|
|
2150
|
+
method: "POST",
|
|
2151
|
+
body: JSON.stringify({ label: `seo on ${hostname().split(".")[0] || "this machine"}`.slice(0, 60) })
|
|
2152
|
+
});
|
|
2153
|
+
const { device, code, url, expiresIn } = result.body;
|
|
2154
|
+
if (!result.ok || !device || !code || !url) throw new SeoError("PROVIDER_UNAVAILABLE", `seoskill.dev could not start a login (${result.status}). Try again.`);
|
|
2155
|
+
const pending = {
|
|
2156
|
+
device,
|
|
2157
|
+
code,
|
|
2158
|
+
url,
|
|
2159
|
+
expiresAt: Date.now() + (expiresIn ?? 600) * 1e3
|
|
2160
|
+
};
|
|
2161
|
+
ensureSeoCliDirs();
|
|
2162
|
+
writeJsonAtomic(pendingFile(), pending, PRIVATE);
|
|
2163
|
+
return pending;
|
|
2164
|
+
}
|
|
2165
|
+
function pendingCloudLogin() {
|
|
2166
|
+
const pending = readJsonFile(pendingFile());
|
|
2167
|
+
if (!pending || pending.expiresAt < Date.now()) return null;
|
|
2168
|
+
return pending;
|
|
2169
|
+
}
|
|
2170
|
+
async function finishCloudLogin() {
|
|
2171
|
+
const pending = pendingCloudLogin();
|
|
2172
|
+
if (!pending) return "expired";
|
|
2173
|
+
const result = await call$1("/account/cli/token", {
|
|
2174
|
+
method: "POST",
|
|
2175
|
+
body: JSON.stringify({ device: pending.device })
|
|
2176
|
+
});
|
|
2177
|
+
if (result.body.error === "pending") return "pending";
|
|
2178
|
+
safeRemove(pendingFile());
|
|
2179
|
+
if (!result.ok || !result.body.token) return "expired";
|
|
2180
|
+
writeJsonAtomic(linkFile(), { token: result.body.token }, PRIVATE);
|
|
2181
|
+
credentials = void 0;
|
|
2182
|
+
return "linked";
|
|
2183
|
+
}
|
|
2184
|
+
async function cloudLogout() {
|
|
2185
|
+
const token = readJsonFile(linkFile())?.token;
|
|
2186
|
+
if (!token) return false;
|
|
2187
|
+
await call$1("/account/cli/logout", {
|
|
2188
|
+
method: "POST",
|
|
2189
|
+
token
|
|
2190
|
+
}).catch(() => void 0);
|
|
2191
|
+
safeRemove(linkFile());
|
|
2192
|
+
credentials = void 0;
|
|
2193
|
+
return true;
|
|
2194
|
+
}
|
|
2195
|
+
async function openCloudLink(url) {
|
|
2196
|
+
await open(url).catch(() => void 0);
|
|
2197
|
+
}
|
|
2198
|
+
//#endregion
|
|
1695
2199
|
//#region packages/core/src/types/auth.ts
|
|
1696
2200
|
const tokenSchema = z.object({
|
|
1697
2201
|
provider: z.literal("google"),
|
|
@@ -1749,76 +2253,37 @@ const analyticsExtensionAccountSchema = z.record(z.string().min(1).max(64).regex
|
|
|
1749
2253
|
"constructor",
|
|
1750
2254
|
"prototype",
|
|
1751
2255
|
"__proto__"
|
|
1752
|
-
].includes(value)), z.string().trim().min(1).max(1e3)).refine((value) => Object.keys(value).length <= 16, { message: "An analytics provider account can have at most 16 fields." });
|
|
1753
|
-
const providerConnectionAccountsSchema = z.record(providerIdSchema, analyticsExtensionAccountSchema).refine((value) => Object.keys(value).length <= 20, { message: "There can be at most 20 provider connection records." });
|
|
1754
|
-
const analyticsConnectionSchema = z.discriminatedUnion("provider", [
|
|
1755
|
-
z.object({
|
|
1756
|
-
provider: z.literal("google"),
|
|
1757
|
-
propertyId: z.string(),
|
|
1758
|
-
accountEmail: z.string().email().optional()
|
|
1759
|
-
}),
|
|
1760
|
-
z.object({
|
|
1761
|
-
provider: z.literal("clicky"),
|
|
1762
|
-
siteId: z.string().regex(/^\d{1,30}$/u)
|
|
1763
|
-
}),
|
|
1764
|
-
z.object({
|
|
1765
|
-
provider: z.literal("extension"),
|
|
1766
|
-
providerId: analyticsExtensionIdSchema,
|
|
1767
|
-
account: analyticsExtensionAccountSchema
|
|
1768
|
-
})
|
|
1769
|
-
]);
|
|
1770
|
-
const configSchema = z.looseObject({
|
|
1771
|
-
providers: z.object({
|
|
1772
|
-
dataForSeoLogin: z.string().optional(),
|
|
1773
|
-
dataForSeoPassword: z.string().optional(),
|
|
1774
|
-
prefer: providerPreferenceSchema.default("cheap"),
|
|
1775
|
-
connections: providerConnectionAccountsSchema.optional()
|
|
1776
|
-
}).default({ prefer: "cheap" }),
|
|
1777
|
-
security: z.object({ useKeychain: z.boolean().default(true) }).default({ useKeychain: true }),
|
|
1778
|
-
auth: z.object({
|
|
1779
|
-
sharedClientId: z.string().optional(),
|
|
1780
|
-
sharedClientSecret: z.string().optional()
|
|
1781
|
-
}).default({})
|
|
1782
|
-
});
|
|
1783
|
-
//#endregion
|
|
1784
|
-
//#region packages/core/src/storage/files.ts
|
|
1785
|
-
function ensureParentDir(path, mode = 448) {
|
|
1786
|
-
mkdirSync(dirname(path), {
|
|
1787
|
-
recursive: true,
|
|
1788
|
-
mode
|
|
1789
|
-
});
|
|
1790
|
-
}
|
|
1791
|
-
function readJsonFile(path) {
|
|
1792
|
-
try {
|
|
1793
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
1794
|
-
} catch {
|
|
1795
|
-
return;
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
function writeJsonAtomic(path, data, mode) {
|
|
1799
|
-
ensureParentDir(path);
|
|
1800
|
-
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
1801
|
-
writeFileSync(tmp, JSON.stringify(data, null, 2), {
|
|
1802
|
-
encoding: "utf8",
|
|
1803
|
-
mode
|
|
1804
|
-
});
|
|
1805
|
-
chmodSync(tmp, mode);
|
|
1806
|
-
renameSync(tmp, path);
|
|
1807
|
-
chmodSync(path, mode);
|
|
1808
|
-
}
|
|
1809
|
-
function fileMode(path) {
|
|
1810
|
-
try {
|
|
1811
|
-
return `0${(statSync(path).mode & 511).toString(8)}`;
|
|
1812
|
-
} catch {
|
|
1813
|
-
return "missing";
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
function safeRemove(path) {
|
|
1817
|
-
rmSync(path, {
|
|
1818
|
-
recursive: true,
|
|
1819
|
-
force: true
|
|
1820
|
-
});
|
|
1821
|
-
}
|
|
2256
|
+
].includes(value)), z.string().trim().min(1).max(1e3)).refine((value) => Object.keys(value).length <= 16, { message: "An analytics provider account can have at most 16 fields." });
|
|
2257
|
+
const providerConnectionAccountsSchema = z.record(providerIdSchema, analyticsExtensionAccountSchema).refine((value) => Object.keys(value).length <= 20, { message: "There can be at most 20 provider connection records." });
|
|
2258
|
+
const analyticsConnectionSchema = z.discriminatedUnion("provider", [
|
|
2259
|
+
z.object({
|
|
2260
|
+
provider: z.literal("google"),
|
|
2261
|
+
propertyId: z.string(),
|
|
2262
|
+
accountEmail: z.string().email().optional()
|
|
2263
|
+
}),
|
|
2264
|
+
z.object({
|
|
2265
|
+
provider: z.literal("clicky"),
|
|
2266
|
+
siteId: z.string().regex(/^\d{1,30}$/u)
|
|
2267
|
+
}),
|
|
2268
|
+
z.object({
|
|
2269
|
+
provider: z.literal("extension"),
|
|
2270
|
+
providerId: analyticsExtensionIdSchema,
|
|
2271
|
+
account: analyticsExtensionAccountSchema
|
|
2272
|
+
})
|
|
2273
|
+
]);
|
|
2274
|
+
const configSchema = z.looseObject({
|
|
2275
|
+
providers: z.object({
|
|
2276
|
+
dataForSeoLogin: z.string().optional(),
|
|
2277
|
+
dataForSeoPassword: z.string().optional(),
|
|
2278
|
+
prefer: providerPreferenceSchema.default("cheap"),
|
|
2279
|
+
connections: providerConnectionAccountsSchema.optional()
|
|
2280
|
+
}).default({ prefer: "cheap" }),
|
|
2281
|
+
security: z.object({ useKeychain: z.boolean().default(true) }).default({ useKeychain: true }),
|
|
2282
|
+
auth: z.object({
|
|
2283
|
+
sharedClientId: z.string().optional(),
|
|
2284
|
+
sharedClientSecret: z.string().optional()
|
|
2285
|
+
}).default({})
|
|
2286
|
+
});
|
|
1822
2287
|
let loadedKeyring;
|
|
1823
2288
|
async function resolveKeyring() {
|
|
1824
2289
|
if (!loadedKeyring) loadedKeyring = import("@napi-rs/keyring/keytar.js").then((module) => module);
|
|
@@ -2407,7 +2872,7 @@ async function exchangeGoogleCode(input, fetchImpl = fetch) {
|
|
|
2407
2872
|
//#region packages/core/src/gsc/auth/authorized-client.ts
|
|
2408
2873
|
const REFRESH_BUFFER_MS = 6e4;
|
|
2409
2874
|
function authRequired(accountEmail) {
|
|
2410
|
-
return new SeoError("AUTH_REQUIRED", accountEmail ? `Google account ${accountEmail} is not connected. Run \`seo auth login\` first.` :
|
|
2875
|
+
return new SeoError("AUTH_REQUIRED", accountEmail ? `Google account ${accountEmail} is not connected. Run \`seo auth login\` first.` : notSignedIn());
|
|
2411
2876
|
}
|
|
2412
2877
|
function authExpired() {
|
|
2413
2878
|
return new SeoError("AUTH_EXPIRED", "Google refresh token is no longer valid. Run `seo auth login` again.");
|
|
@@ -2506,6 +2971,18 @@ async function createGoogleAccessTokenClient(accountEmail) {
|
|
|
2506
2971
|
accountEmail: serviceAccount.clientEmail
|
|
2507
2972
|
}
|
|
2508
2973
|
};
|
|
2974
|
+
if (cloudLinked()) {
|
|
2975
|
+
const email = accountEmail ?? (await cloudGoogleEmails())[0] ?? "";
|
|
2976
|
+
return {
|
|
2977
|
+
client: { getAccessToken: () => cloudGoogleToken(email || void 0) },
|
|
2978
|
+
mode: "cloud",
|
|
2979
|
+
identity: email,
|
|
2980
|
+
quotaIdentity: {
|
|
2981
|
+
clientId: "seoskill.dev",
|
|
2982
|
+
accountEmail: email
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
}
|
|
2509
2986
|
const { client, tokens } = await createAuthorizedClient(accountEmail);
|
|
2510
2987
|
const clientConfig = requireClientConfig(tokens);
|
|
2511
2988
|
return {
|
|
@@ -6696,7 +7173,7 @@ function inspectionResponseError(response, input) {
|
|
|
6696
7173
|
}
|
|
6697
7174
|
async function inspectUrl(input) {
|
|
6698
7175
|
const inspectionUrl = assertUrlMatchesGscProperty(input.siteUrl, input.inspectionUrl);
|
|
6699
|
-
const { client, quotaIdentity } = await getAuthorized(input.accountEmail);
|
|
7176
|
+
const { client, quotaIdentity } = await getAuthorized(input.accountEmail ?? await cloudAccountFor(input.siteUrl));
|
|
6700
7177
|
const reservation = reserveUrlInspectionQuota({
|
|
6701
7178
|
credentialKey: credentialKey(quotaIdentity),
|
|
6702
7179
|
property: input.siteUrl,
|
|
@@ -6746,36 +7223,6 @@ async function inspectUrl(input) {
|
|
|
6746
7223
|
return await readJson(response, "URL Inspection");
|
|
6747
7224
|
}
|
|
6748
7225
|
//#endregion
|
|
6749
|
-
//#region packages/core/src/gsc/dates.ts
|
|
6750
|
-
const GSC_TIME_ZONE = "America/Los_Angeles";
|
|
6751
|
-
const GSC_FINAL_DATA_LAG_DAYS = 4;
|
|
6752
|
-
function dateInTimeZone(now, timeZone) {
|
|
6753
|
-
const parts = Object.fromEntries(new Intl.DateTimeFormat("en-US", {
|
|
6754
|
-
timeZone,
|
|
6755
|
-
year: "numeric",
|
|
6756
|
-
month: "2-digit",
|
|
6757
|
-
day: "2-digit"
|
|
6758
|
-
}).formatToParts(now).map((part) => [part.type, part.value]));
|
|
6759
|
-
return new Date(Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day)));
|
|
6760
|
-
}
|
|
6761
|
-
function shiftIsoDate(date, days) {
|
|
6762
|
-
const value = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
6763
|
-
value.setUTCDate(value.getUTCDate() + days);
|
|
6764
|
-
return value.toISOString().slice(0, 10);
|
|
6765
|
-
}
|
|
6766
|
-
function latestFinalGscDate(now = /* @__PURE__ */ new Date()) {
|
|
6767
|
-
const pacificToday = dateInTimeZone(now, GSC_TIME_ZONE);
|
|
6768
|
-
pacificToday.setUTCDate(pacificToday.getUTCDate() - 4);
|
|
6769
|
-
return pacificToday.toISOString().slice(0, 10);
|
|
6770
|
-
}
|
|
6771
|
-
function finalGscDateRange(days = 28, now = /* @__PURE__ */ new Date()) {
|
|
6772
|
-
const endDate = latestFinalGscDate(now);
|
|
6773
|
-
return {
|
|
6774
|
-
startDate: shiftIsoDate(endDate, -(days - 1)),
|
|
6775
|
-
endDate
|
|
6776
|
-
};
|
|
6777
|
-
}
|
|
6778
|
-
//#endregion
|
|
6779
7226
|
//#region packages/core/src/gsc/client/search-analytics.ts
|
|
6780
7227
|
function searchDateRange(days) {
|
|
6781
7228
|
return finalGscDateRange(days);
|
|
@@ -6784,7 +7231,7 @@ function resolveSearchDateWindow(input = 28) {
|
|
|
6784
7231
|
return typeof input === "number" ? searchDateRange(input) : input;
|
|
6785
7232
|
}
|
|
6786
7233
|
async function querySearchAnalytics(site, body, opts = {}) {
|
|
6787
|
-
const { client, identity } = await getAuthorized(opts.accountEmail);
|
|
7234
|
+
const { client, identity } = await getAuthorized(opts.accountEmail ?? await cloudAccountFor(site));
|
|
6788
7235
|
const db = getDb();
|
|
6789
7236
|
const queryHash = hashKey([
|
|
6790
7237
|
site,
|
|
@@ -6943,12 +7390,21 @@ async function queryPagesTopQueriesBatch(site, pageUrls, range = 28) {
|
|
|
6943
7390
|
//#endregion
|
|
6944
7391
|
//#region packages/core/src/gsc/client/sites.ts
|
|
6945
7392
|
async function listSites(_refresh = false, accountEmail) {
|
|
6946
|
-
const
|
|
6947
|
-
const entries = await listSearchConsoleSites(googleFetchFor(client), LOCAL_SIGN_IN);
|
|
7393
|
+
const entries = !accountEmail && cloudLinked() ? await allCloudSites() : await listSearchConsoleSites(googleFetchFor((await getAuthorized(accountEmail)).client), LOCAL_SIGN_IN);
|
|
6948
7394
|
const insert = getDb().prepare("INSERT OR REPLACE INTO sites (site_url, display_name, permission, added_at, is_default) VALUES (?, ?, ?, ?, COALESCE((SELECT is_default FROM sites WHERE site_url = ?), 0))");
|
|
6949
7395
|
for (const site of entries) insert.run(site.siteUrl, site.siteUrl, site.permissionLevel ?? null, Date.now(), site.siteUrl);
|
|
6950
7396
|
return entries;
|
|
6951
7397
|
}
|
|
7398
|
+
async function allCloudSites() {
|
|
7399
|
+
const lists = await Promise.all((await cloudGoogleEmails()).map(async (email) => listSearchConsoleSites(googleFetchFor((await getAuthorized(email)).client), LOCAL_SIGN_IN)));
|
|
7400
|
+
const best = /* @__PURE__ */ new Map();
|
|
7401
|
+
const rank = (site) => PERMISSION_RANK[site.permissionLevel ?? ""] ?? 3;
|
|
7402
|
+
for (const site of lists.flat()) {
|
|
7403
|
+
const found = best.get(site.siteUrl);
|
|
7404
|
+
if (!found || rank(site) < rank(found)) best.set(site.siteUrl, site);
|
|
7405
|
+
}
|
|
7406
|
+
return [...best.values()];
|
|
7407
|
+
}
|
|
6952
7408
|
//#endregion
|
|
6953
7409
|
//#region packages/core/src/analyze/page-technical-signals.ts
|
|
6954
7410
|
function normalizePageUrl(value) {
|
|
@@ -7654,7 +8110,7 @@ function jaccard(a, b) {
|
|
|
7654
8110
|
const union = /* @__PURE__ */ new Set([...setA, ...setB]);
|
|
7655
8111
|
return union.size === 0 ? 0 : intersection.length / union.size;
|
|
7656
8112
|
}
|
|
7657
|
-
function defaultDateRange$
|
|
8113
|
+
function defaultDateRange$1(days = 28, now = /* @__PURE__ */ new Date()) {
|
|
7658
8114
|
return finalGscDateRange(days, now);
|
|
7659
8115
|
}
|
|
7660
8116
|
//#endregion
|
|
@@ -8746,7 +9202,7 @@ function reportCaveats$1(input) {
|
|
|
8746
9202
|
async function pageOpportunitiesReport(input, dependencies = defaultDependencies$12) {
|
|
8747
9203
|
const url = assertUrlMatchesGscProperty(input.site, input.url);
|
|
8748
9204
|
const days = reportDays$1(input.days);
|
|
8749
|
-
const range = defaultDateRange$
|
|
9205
|
+
const range = defaultDateRange$1(days);
|
|
8750
9206
|
const targetResult = await dependencies.searchAnalytics(input.site, {
|
|
8751
9207
|
...range,
|
|
8752
9208
|
dimensions: ["query", "page"],
|
|
@@ -12725,37 +13181,10 @@ function collectCrawlAiSignals(input) {
|
|
|
12725
13181
|
]);
|
|
12726
13182
|
}
|
|
12727
13183
|
//#endregion
|
|
12728
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins-report-input.ts
|
|
12729
|
-
function integerOption$1(input) {
|
|
12730
|
-
if (input.value === void 0) return input.fallback;
|
|
12731
|
-
if (!Number.isInteger(input.value) || input.value < input.minimum || input.value > input.maximum) throw new SeoError("INVALID_INPUT", `${input.label} must be a whole number between ${input.minimum} and ${input.maximum}.`);
|
|
12732
|
-
return input.value;
|
|
12733
|
-
}
|
|
12734
|
-
function defaultDateRange$1(days, now) {
|
|
12735
|
-
return finalGscDateRange(days, now);
|
|
12736
|
-
}
|
|
12737
|
-
function explicitDateRange(input, maximumDays) {
|
|
12738
|
-
if (!input.startDate && !input.endDate) return void 0;
|
|
12739
|
-
if (!input.startDate || !input.endDate) throw new SeoError("INVALID_INPUT", "Start date and end date must be provided together.");
|
|
12740
|
-
const pattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
12741
|
-
const start = /* @__PURE__ */ new Date(`${input.startDate}T00:00:00.000Z`);
|
|
12742
|
-
const end = /* @__PURE__ */ new Date(`${input.endDate}T00:00:00.000Z`);
|
|
12743
|
-
if (!pattern.test(input.startDate) || !pattern.test(input.endDate) || Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()) || start.toISOString().slice(0, 10) !== input.startDate || end.toISOString().slice(0, 10) !== input.endDate || start > end) throw new SeoError("INVALID_INPUT", "Start date and end date must be valid YYYY-MM-DD values in ascending order.");
|
|
12744
|
-
const days = Math.floor((end.getTime() - start.getTime()) / 864e5) + 1;
|
|
12745
|
-
if (days > maximumDays) throw new SeoError("INVALID_INPUT", `Date range must not exceed ${maximumDays} days.`);
|
|
12746
|
-
return {
|
|
12747
|
-
range: {
|
|
12748
|
-
startDate: input.startDate,
|
|
12749
|
-
endDate: input.endDate
|
|
12750
|
-
},
|
|
12751
|
-
days
|
|
12752
|
-
};
|
|
12753
|
-
}
|
|
12754
|
-
//#endregion
|
|
12755
13184
|
//#region packages/core/src/analyze/crawler/site-crawl-providers.ts
|
|
12756
13185
|
function crawlMetricsWindow(now) {
|
|
12757
13186
|
return {
|
|
12758
|
-
...defaultDateRange$
|
|
13187
|
+
...defaultDateRange$2(28, now),
|
|
12759
13188
|
days: 28
|
|
12760
13189
|
};
|
|
12761
13190
|
}
|
|
@@ -14089,6 +14518,9 @@ async function storedSiteKeys() {
|
|
|
14089
14518
|
source: credential.source
|
|
14090
14519
|
};
|
|
14091
14520
|
}
|
|
14521
|
+
async function readClickySiteKeys() {
|
|
14522
|
+
return (await storedSiteKeys()).siteKeys;
|
|
14523
|
+
}
|
|
14092
14524
|
async function readClickySiteKey(siteIdValue, input = {}) {
|
|
14093
14525
|
const siteId = normalizeClickySiteId(siteIdValue);
|
|
14094
14526
|
const environmentKey = (input.env ?? process.env)[CLICKY_SITEKEY_ENV]?.trim();
|
|
@@ -16279,7 +16711,7 @@ async function ctrUnderperformersReportWith(input, dependencies) {
|
|
|
16279
16711
|
label: "Days"
|
|
16280
16712
|
});
|
|
16281
16713
|
const now = dependencies.now();
|
|
16282
|
-
const range = explicitRange?.range ?? defaultDateRange$
|
|
16714
|
+
const range = explicitRange?.range ?? defaultDateRange$2(days, now);
|
|
16283
16715
|
const source = await dependencies.searchAnalytics(input.site, {
|
|
16284
16716
|
...range,
|
|
16285
16717
|
dimensions: ["query", "page"],
|
|
@@ -16721,7 +17153,7 @@ function analyzeSegmentRows(input) {
|
|
|
16721
17153
|
//#region packages/core/src/analyze/segment-impact-input.ts
|
|
16722
17154
|
const DAY_MS = 864e5;
|
|
16723
17155
|
const MAX_WINDOW_DAYS = 240;
|
|
16724
|
-
function subtractDays
|
|
17156
|
+
function subtractDays(value, days) {
|
|
16725
17157
|
const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
16726
17158
|
date.setUTCDate(date.getUTCDate() - days);
|
|
16727
17159
|
return date.toISOString().slice(0, 10);
|
|
@@ -16730,9 +17162,9 @@ function segmentRangeDays(range) {
|
|
|
16730
17162
|
return Math.floor((Date.parse(`${range.endDate}T00:00:00.000Z`) - Date.parse(`${range.startDate}T00:00:00.000Z`)) / DAY_MS) + 1;
|
|
16731
17163
|
}
|
|
16732
17164
|
function segmentComparisonRange(after, compareDays) {
|
|
16733
|
-
const endDate = subtractDays
|
|
17165
|
+
const endDate = subtractDays(after.startDate, 1);
|
|
16734
17166
|
return {
|
|
16735
|
-
startDate: subtractDays
|
|
17167
|
+
startDate: subtractDays(endDate, compareDays - 1),
|
|
16736
17168
|
endDate
|
|
16737
17169
|
};
|
|
16738
17170
|
}
|
|
@@ -16740,7 +17172,7 @@ function validateSegmentRanges(input) {
|
|
|
16740
17172
|
const before = explicitDateRange(input.before, MAX_WINDOW_DAYS);
|
|
16741
17173
|
const after = explicitDateRange(input.after, MAX_WINDOW_DAYS);
|
|
16742
17174
|
if (!before || !after) throw new SeoError("INVALID_INPUT", "Both segment ranges are required.");
|
|
16743
|
-
if (subtractDays
|
|
17175
|
+
if (subtractDays(input.after.startDate, 1) !== input.before.endDate) throw new SeoError("INVALID_INPUT", "Segment comparison windows must be adjacent.");
|
|
16744
17176
|
if (before.days !== after.days) throw new SeoError("INVALID_INPUT", "Segment comparison windows must have equal lengths.");
|
|
16745
17177
|
return {
|
|
16746
17178
|
beforeDays: before.days,
|
|
@@ -16755,7 +17187,7 @@ function resolveSegmentRanges(input) {
|
|
|
16755
17187
|
maximum: MAX_WINDOW_DAYS,
|
|
16756
17188
|
label: "days"
|
|
16757
17189
|
});
|
|
16758
|
-
const after = explicitDateRange(input, MAX_WINDOW_DAYS)?.range ?? defaultDateRange$
|
|
17190
|
+
const after = explicitDateRange(input, MAX_WINDOW_DAYS)?.range ?? defaultDateRange$2(days, input.now);
|
|
16759
17191
|
const compareDays = integerOption$1({
|
|
16760
17192
|
value: input.compareDays,
|
|
16761
17193
|
fallback: segmentRangeDays(after),
|
|
@@ -16774,63 +17206,6 @@ function validateSegmentSite(site) {
|
|
|
16774
17206
|
if (!site.trim()) throw new SeoError("INVALID_INPUT", "site must not be empty.");
|
|
16775
17207
|
}
|
|
16776
17208
|
//#endregion
|
|
16777
|
-
//#region packages/core/src/analyze/site-diagnostics/decay-report-input.ts
|
|
16778
|
-
function decayNumberOption(input) {
|
|
16779
|
-
const value = input.value ?? input.fallback;
|
|
16780
|
-
if (!Number.isFinite(value) || value < input.minimum || value > input.maximum) throw new SeoError("INVALID_INPUT", `${input.label} must be between ${input.minimum} and ${input.maximum}.`);
|
|
16781
|
-
return value;
|
|
16782
|
-
}
|
|
16783
|
-
function rangeDays$1(range) {
|
|
16784
|
-
return Math.floor(((/* @__PURE__ */ new Date(`${range.endDate}T00:00:00.000Z`)).getTime() - (/* @__PURE__ */ new Date(`${range.startDate}T00:00:00.000Z`)).getTime()) / 864e5) + 1;
|
|
16785
|
-
}
|
|
16786
|
-
function dateString(date) {
|
|
16787
|
-
return date.toISOString().slice(0, 10);
|
|
16788
|
-
}
|
|
16789
|
-
function subtractDays(value, days) {
|
|
16790
|
-
const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
16791
|
-
date.setUTCDate(date.getUTCDate() - days);
|
|
16792
|
-
return dateString(date);
|
|
16793
|
-
}
|
|
16794
|
-
function priorYearDate(value) {
|
|
16795
|
-
const source = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
16796
|
-
const year = source.getUTCFullYear() - 1;
|
|
16797
|
-
const month = source.getUTCMonth();
|
|
16798
|
-
const day = source.getUTCDate();
|
|
16799
|
-
const candidate = new Date(Date.UTC(year, month, day));
|
|
16800
|
-
if (candidate.getUTCMonth() !== month) return dateString(new Date(Date.UTC(year, month + 1, 0)));
|
|
16801
|
-
return dateString(candidate);
|
|
16802
|
-
}
|
|
16803
|
-
function subtractCalendarMonths(date, months) {
|
|
16804
|
-
const day = date.getUTCDate();
|
|
16805
|
-
const target = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() - months, 1));
|
|
16806
|
-
const lastDay = new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0)).getUTCDate();
|
|
16807
|
-
target.setUTCDate(Math.min(day, lastDay));
|
|
16808
|
-
return dateString(target);
|
|
16809
|
-
}
|
|
16810
|
-
function validateDecayRanges(input) {
|
|
16811
|
-
const latestFinalDate = defaultDateRange$1(1, input.now).endDate;
|
|
16812
|
-
if (input.current.endDate > latestFinalDate) throw new SeoError("INVALID_INPUT", `Decay end date must be ${latestFinalDate} or earlier so both GSC windows use final data.`);
|
|
16813
|
-
const pacificToday = /* @__PURE__ */ new Date(`${latestFinalDate}T00:00:00.000Z`);
|
|
16814
|
-
pacificToday.setUTCDate(pacificToday.getUTCDate() + 4);
|
|
16815
|
-
const retentionStart = subtractCalendarMonths(pacificToday, 16);
|
|
16816
|
-
if (input.previous.startDate < retentionStart) throw new SeoError("INVALID_INPUT", `Comparison range starts before Search Console's rolling 16-month API history (${retentionStart}). Choose a shorter range or use previously stored data.`);
|
|
16817
|
-
}
|
|
16818
|
-
function decayComparisonRange(current, comparison = "previous-period") {
|
|
16819
|
-
const days = rangeDays$1(current);
|
|
16820
|
-
if (comparison === "year-over-year") {
|
|
16821
|
-
const endDate = priorYearDate(current.endDate);
|
|
16822
|
-
return {
|
|
16823
|
-
startDate: subtractDays(endDate, days - 1),
|
|
16824
|
-
endDate
|
|
16825
|
-
};
|
|
16826
|
-
}
|
|
16827
|
-
const endDate = subtractDays(current.startDate, 1);
|
|
16828
|
-
return {
|
|
16829
|
-
startDate: subtractDays(endDate, days - 1),
|
|
16830
|
-
endDate
|
|
16831
|
-
};
|
|
16832
|
-
}
|
|
16833
|
-
//#endregion
|
|
16834
17209
|
//#region packages/core/src/analyze/segment-impact-fallback.ts
|
|
16835
17210
|
function unavailableSegmentImpactReport(input) {
|
|
16836
17211
|
const days = segmentRangeDays(input.after);
|
|
@@ -18691,7 +19066,7 @@ async function cannibalReportWith(input, dependencies) {
|
|
|
18691
19066
|
});
|
|
18692
19067
|
const now = dependencies.now();
|
|
18693
19068
|
const explicit = explicitDateRange(input, 548);
|
|
18694
|
-
const range = explicit?.range ?? defaultDateRange$
|
|
19069
|
+
const range = explicit?.range ?? defaultDateRange$2(days, now);
|
|
18695
19070
|
const rangeDays = explicit?.days ?? days;
|
|
18696
19071
|
const requestBase = {
|
|
18697
19072
|
...range,
|
|
@@ -19149,7 +19524,7 @@ async function decayingReportWith(input, dependencies) {
|
|
|
19149
19524
|
});
|
|
19150
19525
|
const now = dependencies.now();
|
|
19151
19526
|
const explicit = explicitDateRange(input, 548);
|
|
19152
|
-
const currentRange = explicit?.range ?? defaultDateRange$
|
|
19527
|
+
const currentRange = explicit?.range ?? defaultDateRange$2(days, now);
|
|
19153
19528
|
const comparison = input.comparison ?? "previous-period";
|
|
19154
19529
|
const previousRange = decayComparisonRange(currentRange, comparison);
|
|
19155
19530
|
validateDecayRanges({
|
|
@@ -19622,7 +19997,7 @@ async function quickWinsReportWith(input, dependencies) {
|
|
|
19622
19997
|
label: "Verification limit"
|
|
19623
19998
|
});
|
|
19624
19999
|
const generatedAt = dependencies.now().toISOString();
|
|
19625
|
-
const range = explicitRange?.range ?? defaultDateRange$
|
|
20000
|
+
const range = explicitRange?.range ?? defaultDateRange$2(days, new Date(generatedAt));
|
|
19626
20001
|
const source = await dependencies.searchAnalytics(input.site, {
|
|
19627
20002
|
...range,
|
|
19628
20003
|
dimensions: ["query", "page"],
|
|
@@ -20046,7 +20421,7 @@ function reportDays(value) {
|
|
|
20046
20421
|
if (!Number.isInteger(value) || value < 1 || value > MAX_DAYS) throw new SeoError("INVALID_INPUT", `Days must be a whole number between 1 and ${MAX_DAYS}.`);
|
|
20047
20422
|
return value;
|
|
20048
20423
|
}
|
|
20049
|
-
function dateRange
|
|
20424
|
+
function dateRange(days, now) {
|
|
20050
20425
|
return finalGscDateRange(days, now);
|
|
20051
20426
|
}
|
|
20052
20427
|
function verifiedRecommendation(coverage) {
|
|
@@ -20086,7 +20461,7 @@ async function strikingDistanceWith(input, dependencies) {
|
|
|
20086
20461
|
const explicitRange = explicitDateRange(input, MAX_DAYS);
|
|
20087
20462
|
const days = explicitRange?.days ?? reportDays(input.days);
|
|
20088
20463
|
const generatedAt = dependencies.now().toISOString();
|
|
20089
|
-
const range = explicitRange?.range ?? dateRange
|
|
20464
|
+
const range = explicitRange?.range ?? dateRange(days, new Date(generatedAt));
|
|
20090
20465
|
const result = await dependencies.searchAnalytics(input.site, {
|
|
20091
20466
|
...range,
|
|
20092
20467
|
dimensions: ["query", "page"],
|
|
@@ -20198,44 +20573,6 @@ function strikingDistance(input, dependencies = defaultDependencies$5) {
|
|
|
20198
20573
|
return strikingDistanceWith(input, dependencies);
|
|
20199
20574
|
}
|
|
20200
20575
|
//#endregion
|
|
20201
|
-
//#region packages/core/src/updates/search-updates.ts
|
|
20202
|
-
function classifyUpdate(name, product) {
|
|
20203
|
-
const lower = `${name} ${product}`.toLowerCase();
|
|
20204
|
-
if (lower.includes("core")) return "core";
|
|
20205
|
-
if (lower.includes("spam")) return "spam";
|
|
20206
|
-
if (lower.includes("discover")) return "discover";
|
|
20207
|
-
if (lower.includes("ranking")) return "ranking";
|
|
20208
|
-
if (lower.includes("index")) return "indexing";
|
|
20209
|
-
if (lower.includes("serving")) return "serving";
|
|
20210
|
-
return "other";
|
|
20211
|
-
}
|
|
20212
|
-
function searchUpdatesFromIncidents(incidents, input = {}) {
|
|
20213
|
-
const product = input.product?.toLowerCase();
|
|
20214
|
-
return incidents.filter((incident) => !product || incident.service_name.toLowerCase() === product).map((incident) => {
|
|
20215
|
-
const status = incident.end ? "complete" : incident.status_impact === "SERVICE_INFORMATION" ? "rolling-out" : "incident";
|
|
20216
|
-
return {
|
|
20217
|
-
id: incident.id,
|
|
20218
|
-
name: incident.external_desc,
|
|
20219
|
-
type: classifyUpdate(incident.external_desc, incident.service_name),
|
|
20220
|
-
product: incident.service_name,
|
|
20221
|
-
start: incident.begin,
|
|
20222
|
-
end: incident.end,
|
|
20223
|
-
status,
|
|
20224
|
-
sourceUrl: `https://status.search.google.com/${incident.uri}`
|
|
20225
|
-
};
|
|
20226
|
-
}).slice(0, input.limit ?? 25);
|
|
20227
|
-
}
|
|
20228
|
-
function findOverlappingSearchUpdates(input) {
|
|
20229
|
-
const paddingMs = (input.paddingDays ?? 3) * 864e5;
|
|
20230
|
-
const windowStart = new Date(input.startDate).getTime() - paddingMs;
|
|
20231
|
-
const windowEnd = new Date(input.endDate).getTime() + paddingMs;
|
|
20232
|
-
return input.updates.filter((update) => {
|
|
20233
|
-
const updateStart = new Date(update.start).getTime();
|
|
20234
|
-
const updateEnd = update.end ? new Date(update.end).getTime() : Date.now();
|
|
20235
|
-
return updateStart <= windowEnd && updateEnd >= windowStart;
|
|
20236
|
-
});
|
|
20237
|
-
}
|
|
20238
|
-
//#endregion
|
|
20239
20576
|
//#region packages/core/src/updates/search-status.ts
|
|
20240
20577
|
const INCIDENTS_URL = "https://status.search.google.com/incidents.json";
|
|
20241
20578
|
async function fetchSearchStatusIncidents() {
|
|
@@ -20612,7 +20949,7 @@ async function trafficAnomalyWith(input, dependencies) {
|
|
|
20612
20949
|
const range = input.startDate && input.endDate ? {
|
|
20613
20950
|
startDate: input.startDate,
|
|
20614
20951
|
endDate: input.endDate
|
|
20615
|
-
} : defaultDateRange$
|
|
20952
|
+
} : defaultDateRange$1(days, dependencies.now());
|
|
20616
20953
|
const result = await dependencies.searchAnalytics(input.site, {
|
|
20617
20954
|
...range,
|
|
20618
20955
|
dimensions: ["date"],
|
|
@@ -20704,7 +21041,7 @@ function fallbackRange(input) {
|
|
|
20704
21041
|
return input.startDate && input.endDate ? {
|
|
20705
21042
|
startDate: input.startDate,
|
|
20706
21043
|
endDate: input.endDate
|
|
20707
|
-
} : defaultDateRange$
|
|
21044
|
+
} : defaultDateRange$1(input.days ?? 28);
|
|
20708
21045
|
}
|
|
20709
21046
|
function emptyAnomaly(input) {
|
|
20710
21047
|
return {
|
|
@@ -20909,7 +21246,7 @@ function emptyStriking(input) {
|
|
|
20909
21246
|
return {
|
|
20910
21247
|
site: input.site,
|
|
20911
21248
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20912
|
-
range: defaultDateRange$
|
|
21249
|
+
range: defaultDateRange$1(input.days ?? 28),
|
|
20913
21250
|
rangeDays: input.days ?? 28,
|
|
20914
21251
|
source: {
|
|
20915
21252
|
provider: "google-search-console",
|
|
@@ -22826,7 +23163,7 @@ function rowMetrics(row) {
|
|
|
22826
23163
|
}
|
|
22827
23164
|
async function linkRecover(input) {
|
|
22828
23165
|
const days = input.days ?? 90;
|
|
22829
|
-
const range = defaultDateRange$
|
|
23166
|
+
const range = defaultDateRange$1(days);
|
|
22830
23167
|
const result = await querySearchAnalytics(input.site, {
|
|
22831
23168
|
...range,
|
|
22832
23169
|
dimensions: ["page"],
|
|
@@ -23990,7 +24327,7 @@ async function queryClusterReport(input) {
|
|
|
23990
24327
|
maximum: 548,
|
|
23991
24328
|
label: "days"
|
|
23992
24329
|
});
|
|
23993
|
-
const range = defaultDateRange$
|
|
24330
|
+
const range = defaultDateRange$1(days);
|
|
23994
24331
|
const filters = input.scope ? [{
|
|
23995
24332
|
groupType: "and",
|
|
23996
24333
|
filters: [{
|
|
@@ -25309,7 +25646,7 @@ async function refreshPrioritiesWorkflow(input) {
|
|
|
25309
25646
|
verifyContent: input.verifyContent ?? true,
|
|
25310
25647
|
verifyLimit: input.verifyContent === false ? void 0 : input.verifyLimit ?? Math.min(input.limit ?? 5, 5)
|
|
25311
25648
|
});
|
|
25312
|
-
const range = defaultDateRange$
|
|
25649
|
+
const range = defaultDateRange$1(input.days ?? 28);
|
|
25313
25650
|
const analytics = await fetchLandingPageValues({
|
|
25314
25651
|
connection: input.analyticsConnection,
|
|
25315
25652
|
propertyId: input.googleAnalyticsPropertyId,
|
|
@@ -25840,67 +26177,6 @@ async function detectBrandTerms(input) {
|
|
|
25840
26177
|
candidates: ranked
|
|
25841
26178
|
};
|
|
25842
26179
|
}
|
|
25843
|
-
//#endregion
|
|
25844
|
-
//#region packages/core/src/bing/api.ts
|
|
25845
|
-
const BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
25846
|
-
const MAX_BYTES$1 = 8e6;
|
|
25847
|
-
function bingCall(auth, options) {
|
|
25848
|
-
return async (method, params = {}) => {
|
|
25849
|
-
const url = new URL(`${BASE}/${method}`);
|
|
25850
|
-
for (const [name, value] of Object.entries(params)) url.searchParams.set(name, value);
|
|
25851
|
-
const headers = { accept: "application/json" };
|
|
25852
|
-
if ("apiKey" in auth) url.searchParams.set("apikey", auth.apiKey);
|
|
25853
|
-
else headers.authorization = `Bearer ${await auth.accessToken()}`;
|
|
25854
|
-
let response;
|
|
25855
|
-
try {
|
|
25856
|
-
response = await fetch(url, {
|
|
25857
|
-
headers,
|
|
25858
|
-
signal: AbortSignal.timeout(3e4)
|
|
25859
|
-
});
|
|
25860
|
-
} catch (error) {
|
|
25861
|
-
throw new SeoError("OPTIONAL_PROVIDER_UNAVAILABLE", `Bing Webmaster did not respond: ${error instanceof Error ? error.message : String(error)}`);
|
|
25862
|
-
}
|
|
25863
|
-
if (!response.ok) {
|
|
25864
|
-
const detail = (await response.text().catch(() => "")).slice(0, 200);
|
|
25865
|
-
if (response.status === 401 || /invalidapikey/i.test(detail)) throw new SeoError("AUTH_REQUIRED", `Bing Webmaster rejected the sign-in. ${options.signIn}`);
|
|
25866
|
-
if (response.status === 403 || /notauthorized/i.test(detail)) throw new SeoError("ACCESS_DENIED", "Bing Webmaster denied access to this site. It must be verified in the connected Bing account.");
|
|
25867
|
-
if (/ThrottleIP/i.test(detail)) throw new SeoError("RATE_LIMITED", "Bing Webmaster is blocking requests from this network (ThrottleIP). Run the same report with the CLI on your own machine, where Bing answers.");
|
|
25868
|
-
if (response.status === 429 || /throttle/i.test(detail)) throw new SeoError("RATE_LIMITED", "Bing Webmaster is limiting requests. Try again in a few minutes.");
|
|
25869
|
-
throw new SeoError("OPTIONAL_PROVIDER_UNAVAILABLE", `Bing Webmaster answered ${response.status}. ${detail}`.trim());
|
|
25870
|
-
}
|
|
25871
|
-
return (await readJson(response, "Bing Webmaster", { maxBytes: MAX_BYTES$1 })).d;
|
|
25872
|
-
};
|
|
25873
|
-
}
|
|
25874
|
-
//#endregion
|
|
25875
|
-
//#region packages/core/src/commands/types.ts
|
|
25876
|
-
var CommandError = class extends Error {
|
|
25877
|
-
code;
|
|
25878
|
-
constructor(code, message) {
|
|
25879
|
-
super(message);
|
|
25880
|
-
this.code = code;
|
|
25881
|
-
}
|
|
25882
|
-
};
|
|
25883
|
-
const ADVICE_KEYS = /^(recommendations?|actions?|advice|suggested[A-Z]\w*|nextSteps?)$/;
|
|
25884
|
-
function factsOnly(value) {
|
|
25885
|
-
if (Array.isArray(value)) return value.map(factsOnly);
|
|
25886
|
-
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).filter(([key]) => !ADVICE_KEYS.test(key)).map(([key, child]) => [key, factsOnly(child)]));
|
|
25887
|
-
return value;
|
|
25888
|
-
}
|
|
25889
|
-
function matchProperty(names, site) {
|
|
25890
|
-
if (names.includes(site)) return site;
|
|
25891
|
-
let host;
|
|
25892
|
-
let origin;
|
|
25893
|
-
try {
|
|
25894
|
-
const url = new URL(site.includes("://") ? site : `https://${site.replace(/^sc-domain:/, "")}`);
|
|
25895
|
-
host = url.hostname.replace(/^www\./, "");
|
|
25896
|
-
origin = url.origin;
|
|
25897
|
-
} catch {
|
|
25898
|
-
return null;
|
|
25899
|
-
}
|
|
25900
|
-
const domain = names.filter((name) => name.startsWith("sc-domain:")).map((name) => name.slice(10)).filter((name) => host === name || host.endsWith(`.${name}`)).sort((a, b) => b.length - a.length)[0];
|
|
25901
|
-
if (domain) return `sc-domain:${domain}`;
|
|
25902
|
-
return names.find((name) => name === `${origin}/` || name === `${origin.replace("://", "://www.")}/` || name === `${origin.replace("://www.", "://")}/`) ?? null;
|
|
25903
|
-
}
|
|
25904
26180
|
const DIMENSIONS = [
|
|
25905
26181
|
"query",
|
|
25906
26182
|
"page",
|
|
@@ -25910,6 +26186,9 @@ const DIMENSIONS = [
|
|
|
25910
26186
|
"searchAppearance"
|
|
25911
26187
|
];
|
|
25912
26188
|
//#endregion
|
|
26189
|
+
//#region packages/core/src/commands/clicky.ts
|
|
26190
|
+
const CLICKY_KEY_PREFIX = "x-clicky-";
|
|
26191
|
+
//#endregion
|
|
25913
26192
|
//#region packages/core/src/commands/page-facts.ts
|
|
25914
26193
|
const MAX_PAGES = 20;
|
|
25915
26194
|
const TIMEOUT_MS = 6e3;
|
|
@@ -26038,12 +26317,6 @@ const SECTIONS = {
|
|
|
26038
26317
|
};
|
|
26039
26318
|
const SECTION_IDS = Object.keys(SECTIONS);
|
|
26040
26319
|
//#endregion
|
|
26041
|
-
//#region packages/core/src/commands/research-providers.ts
|
|
26042
|
-
const KEY_HEADERS = {
|
|
26043
|
-
dataforseo: "X-DataForSEO-Login and X-DataForSEO-Password",
|
|
26044
|
-
ahrefs: "X-Ahrefs-Key"
|
|
26045
|
-
};
|
|
26046
|
-
//#endregion
|
|
26047
26320
|
//#region packages/core/src/commands/research.ts
|
|
26048
26321
|
const RESEARCH_ACTIONS = [
|
|
26049
26322
|
"keyword_metrics",
|
|
@@ -26067,7 +26340,7 @@ function indexStatusFrom(result) {
|
|
|
26067
26340
|
user_canonical: text(status.userCanonical)
|
|
26068
26341
|
};
|
|
26069
26342
|
}
|
|
26070
|
-
const LOST_PAGES_RULE = `Pages that had at least 3 clicks in the previous 28 days and kept ${.2 * 100}% or less of them in the last 28 days, with a live check of the top 20 and Google’s index status for the top 10.`;
|
|
26343
|
+
const LOST_PAGES_RULE = `Pages that had at least 3 clicks in the previous 28 days and kept ${.2 * 100}% or less of them in the last 28 days, with a live check of the top 20 and Google’s index status for the top 10 and the home page.`;
|
|
26071
26344
|
//#endregion
|
|
26072
26345
|
//#region packages/core/src/commands/tools.ts
|
|
26073
26346
|
const site = z.string().min(1).describe("Search Console property or site, such as sc-domain:example.com, https://example.com/ or example.com.");
|
|
@@ -26086,7 +26359,8 @@ const detail = {
|
|
|
26086
26359
|
const DATA_SOURCES = [
|
|
26087
26360
|
"search_console",
|
|
26088
26361
|
"google_analytics",
|
|
26089
|
-
"bing"
|
|
26362
|
+
"bing",
|
|
26363
|
+
"clicky"
|
|
26090
26364
|
];
|
|
26091
26365
|
z.object({}), `${Object.entries(SECTIONS).map(([id, text]) => `- ${id}: ${text}`).join("\n")}`, z.object({
|
|
26092
26366
|
site,
|
|
@@ -26104,7 +26378,7 @@ z.object({}), `${Object.entries(SECTIONS).map(([id, text]) => `- ${id}: ${text}`
|
|
|
26104
26378
|
}), `${LOST_PAGES_RULE}`, z.object({
|
|
26105
26379
|
site,
|
|
26106
26380
|
...dates
|
|
26107
|
-
}),
|
|
26381
|
+
}), z.object({
|
|
26108
26382
|
provider: z.enum(RESEARCH_PROVIDERS),
|
|
26109
26383
|
action: z.enum(RESEARCH_ACTIONS),
|
|
26110
26384
|
keywords: z.array(z.string().min(1)).max(100).default([]),
|
|
@@ -26113,7 +26387,7 @@ z.object({}), `${Object.entries(SECTIONS).map(([id, text]) => `- ${id}: ${text}`
|
|
|
26113
26387
|
...detail
|
|
26114
26388
|
}), z.object({
|
|
26115
26389
|
source: z.enum(DATA_SOURCES).default("search_console"),
|
|
26116
|
-
site: z.string().min(1).describe("search_console and bing: the property or site. google_analytics: numeric property id from seo_sites."),
|
|
26390
|
+
site: z.string().min(1).describe("search_console and bing: the property or site. google_analytics: numeric property id from seo_sites. clicky: the site or its Clicky site ID."),
|
|
26117
26391
|
dimensions: z.array(z.enum(DIMENSIONS)).max(4).default(["query"]).describe("search_console; bing uses the first, query or page."),
|
|
26118
26392
|
page: z.string().min(1).optional().describe("search_console only: URL text, such as /blog/."),
|
|
26119
26393
|
query: z.string().min(1).optional().describe("search_console only: query text."),
|
|
@@ -26332,15 +26606,34 @@ function nodeCommandDeps(options) {
|
|
|
26332
26606
|
bingConnect: LOCAL_BING_CONNECT
|
|
26333
26607
|
};
|
|
26334
26608
|
}
|
|
26335
|
-
const LOCAL_BING_CONNECT = "
|
|
26609
|
+
const LOCAL_BING_CONNECT = "Connect Bing on your seoskill.dev account and run `seo login`, or run `seo providers bing connect` with an API key from Bing Webmaster Tools (Settings, API access).";
|
|
26336
26610
|
let bing;
|
|
26337
26611
|
function localBing() {
|
|
26338
|
-
bing ??= readBingApiKey().catch(() => void 0).then((key) => key ? bingCall({ apiKey: key.apiKey }, { signIn: LOCAL_BING_CONNECT }) : null);
|
|
26612
|
+
bing ??= readBingApiKey().catch(() => void 0).then((key) => key ? bingCall({ apiKey: key.apiKey }, { signIn: LOCAL_BING_CONNECT }) : cloudLinked() ? cloudBing() : null);
|
|
26339
26613
|
return bing;
|
|
26340
26614
|
}
|
|
26615
|
+
async function localAccounts() {
|
|
26616
|
+
if (!cloudLinked()) return [{
|
|
26617
|
+
email: null,
|
|
26618
|
+
google: await localGoogleFetch()
|
|
26619
|
+
}];
|
|
26620
|
+
return (await cloudGoogleEmails()).map((email) => ({
|
|
26621
|
+
email,
|
|
26622
|
+
google: bearerFetch(() => cloudGoogleToken(email))
|
|
26623
|
+
}));
|
|
26624
|
+
}
|
|
26341
26625
|
async function localResearchKeys() {
|
|
26342
|
-
const [dataForSeo, ahrefs] = await Promise.all([
|
|
26343
|
-
|
|
26626
|
+
const [dataForSeo, ahrefs, clicky, saved] = await Promise.all([
|
|
26627
|
+
readDataForSeoCredentials().catch(() => void 0),
|
|
26628
|
+
readAhrefsApiKey().catch(() => void 0),
|
|
26629
|
+
readClickySiteKeys().catch(() => ({})),
|
|
26630
|
+
cloudLinked() ? cloudKeys().catch(() => ({})) : {}
|
|
26631
|
+
]);
|
|
26632
|
+
const keys = new Headers(saved);
|
|
26633
|
+
for (const [siteId, sitekey] of Object.entries(clicky)) {
|
|
26634
|
+
const name = `${CLICKY_KEY_PREFIX}${siteId}`;
|
|
26635
|
+
if (!keys.has(name)) keys.set(name, sitekey);
|
|
26636
|
+
}
|
|
26344
26637
|
if (dataForSeo) {
|
|
26345
26638
|
keys.set("x-dataforseo-login", dataForSeo.login);
|
|
26346
26639
|
keys.set("x-dataforseo-password", dataForSeo.password);
|
|
@@ -26348,6 +26641,11 @@ async function localResearchKeys() {
|
|
|
26348
26641
|
if (ahrefs?.apiKey) keys.set("x-ahrefs-key", ahrefs.apiKey);
|
|
26349
26642
|
return keys;
|
|
26350
26643
|
}
|
|
26644
|
+
async function localRefreshClicky() {
|
|
26645
|
+
if (!cloudLinked()) return null;
|
|
26646
|
+
await cloudCredentials({ refreshClicky: true });
|
|
26647
|
+
return localResearchKeys();
|
|
26648
|
+
}
|
|
26351
26649
|
async function resolveLocalSite(site, options = {}) {
|
|
26352
26650
|
const found = matchProperty((await listSites(options.refresh, options.account)).map((entry) => entry.siteUrl), site);
|
|
26353
26651
|
if (!found) throw new CommandError("NO_ACCESS", `This Google login has no Search Console property for ${site}. Run \`seo sites\` to list the properties it can read.`);
|
|
@@ -28653,6 +28951,6 @@ function isoDate(daysAgo = 0) {
|
|
|
28653
28951
|
return date.toISOString().slice(0, 10);
|
|
28654
28952
|
}
|
|
28655
28953
|
//#endregion
|
|
28656
|
-
export { AHREFS_API_KEY_ENV, AHREFS_API_KEY_SECRET, AI_REFERRAL_SOURCES, AI_REFERRAL_SOURCES_VERSION, ANALYTICS_PROVIDER_IDS, AUDIT_PAGE_RULE_IDS, AhrefsClient, BING_API_KEY_ENV, BING_API_KEY_SECRET, BROWSER_USER_AGENT, CANNIBAL_MAXIMUM_DOMINANT_SHARE, CANNIBAL_MINIMUM_PAGE_SHARE, CLICKY_SITEKEY_ENV, CLICKY_SITE_KEYS_SECRET, CRAWL_SKIP_REASONS, CTR_DEFAULT_LIMIT, CTR_MAX_LIMIT, CTR_MAX_MIN_IMPRESSIONS, DATAFORSEO_CREDENTIAL_SECRET, DATAFORSEO_LOGIN_ENV, DATAFORSEO_PASSWORD_ENV, DEFAULT_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, DEFAULT_UNREACHED_PAGE_LIMIT, DataForSeoClient, GSC_FINAL_DATA_LAG_DAYS, GSC_TIME_ZONE, GoogleApiError, INCIDENTS_URL, INTERNAL_LINK_LEXICAL_TARGET_LIMIT, INTERNAL_LINK_MATCH_EVIDENCE_LIMIT, JsonFileStorageAdapter, LOCAL_BING_CONNECT, LOCAL_SIGN_IN, LOG_MAX_AGE_MS, LOG_MAX_FILE_BYTES, LOG_MAX_TOTAL_BYTES, LocalCredentialsProvider, MANAGED_PROVIDER_SECRET_NAMES, MAX_CRAWL_CONCURRENCY, MAX_CRAWL_DEPTH, MAX_CRAWL_PAGES, MAX_CRAWL_TIMEOUT_MS, MAX_SEARCH_CONSOLE_EXPORT_FILES, MAX_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, PROVIDER_SECRET_NAMES, ProviderError, ProviderExtensionRegistry, RESEARCH_IMPORT_COLUMN_KEYS, ResponseSizeLimitError, SEO_CRAWLER_IDENTITY, SEO_CRAWLER_TOKEN, SEO_CRAWLER_USER_AGENT, SEO_PROVIDER_API_VERSION, SEO_VERSION, SERP_TITLE_REFERENCE_WIDTH_PX, SERP_TITLE_WIDTH_PROFILE, STEP_CHANGE_RULES, SeoError, ServiceAccountAccessTokenClient, SessionLedger, TELEMETRY_AGENTS, TELEMETRY_ENDPOINT, TELEMETRY_ERROR_CATEGORIES, TELEMETRY_EVENTS, TELEMETRY_FAILURE_CONTEXTS, TELEMETRY_FAILURE_REASONS, TELEMETRY_NOTICE, TELEMETRY_OPERATIONS, TELEMETRY_REPORTS, TELEMETRY_SCHEMA_VERSION, TELEMETRY_TIMEOUT_MS, agentActionsView, aiReferralSourceForValue, aiReferralsReport, analyticsConnectionLabel, analyticsConnectionProviderId, analyticsConnectionSchema, analyticsProviderDetails, analyticsProviderSchema, analyzeAiReferralRows, analyzeCannibalImportRows, analyzeCannibalRows, analyzeCtrUnderperformersFromRows, analyzeDecay, analyzeIndexCoverageSignals, analyzeInternalLinksFromRows, analyzePageOpportunitiesFromRows, analyzeQueryClustersFromRows, analyzeQuickWinsFromRows, analyzeSecondPageRows, analyzeSegmentRows, analyzeStrikingDistanceRows, analyzeTrafficAnomalyRows, assertCrawlConfigLimits, assertUrlMatchesGscProperty, auditCrawlPages, auditCrawlRequests, auditPage, authStatus, bearerFetch, boundedSitemapInventory, buildAgentFindings, buildQueryClusterReportFromRows, buildTelemetryPayload, cannibalImportReport, cannibalReport, cannibalReportWith, changeWindow, checkDatabaseReadiness, classifyTelemetryFailure, clearCache, clusterQueryRows, combineRobotsValues, compareSegmentRows, configSchema, contentCoverageRecommendation, contentCoverageType, contentOptimizationFromPageOpportunities, contentOptimizationReport, countLabel, crawlConfigHash, crawlConfigSchema, crawlDefinitionId, crawlFixToQueueItem, crawlImplementationQueueWorkflow, crawlIssueGroupSchema, crawlIssueSchema, crawlOne, crawlPageSnapshotSchema, crawlReportSchema, crawlReportSummarySchema, crawlRequestObservationSchema, crawlRunId, crawlSite, crawlSkipImpact, crawlSkipReasonCountsFromRecord, crawlStatusOnly, crawlTopFixSchema, crawlerJsonSchemas, crawlerRuleCategorySchema, crawlerRuleIdSchema, crawlerRuleInfoSchema, crawlerRuleRecommendationSchema, crawlerRuleSeveritySchema, crawlerSchemas, createAuthorizedClient, createCrawlReport, createCtrBenchmarkContext, createGoogleAccessTokenClient, createLogger, createPageRenderer, ctrUnderperformersReport, ctrUnderperformersReportWith, decayClusterDrafts, decayComparisonRange, decayingReport, decayingReportWith, deleteAhrefsApiKey, deleteBingApiKey, deleteClickySiteKey, deleteDataForSeoCredentials, deleteManagedProviderSecrets, deleteProviderExtensionAccount, deleteProviderExtensionCredentials, deleteProviderSecret, deleteTokens, deriveBrandTerms, describeInstalledProviderExtension, detectBrandTerms, detectTelemetryAgent, diagnoseCsvFiles, diagnoseProperty, diagnosePropertyWorkflow, effectiveRobotsDirectives, effectiveSnippetControl, ensurePrivateDatabaseFiles, ensureSeoCliDirs, estimateSerpTitleWidth, expectedCtrForPosition, explainRule, fetchCruxFieldData, fetchPage, fetchPageStatus, fetchSearchStatusIncidents, fetchSitemapUrls, fileSize, finalGscDateRange, findOverlappingSearchUpdates, formatRelativeExpiry, ga4MatchReason, ga4PropertyIdFromName, ga4ReportQualityWarnings, ga4RequestCanUseCache, ga4RowsToObjects, getAuthModeStatus, getCacheStats, getClientConfig, getDb, getPrivacySnapshot, getSeoCliPaths, getServiceAccountConfig, getServiceAccountStatus, getTelemetryStatus, getTokenStorageStatus, googleJson, groupCrawlIssues, hasMetaRobotsDirective, hasRule, hasXRobotsDirective, hashKey, indexCoverageSignals, inferTemplateMovement, initializeTelemetry, inspectProviderPackage, inspectSearchConsoleUrl, inspectUrl, installProviderPackage, installedProviderPackageSchema, installedProviderPackagesSchema, internalLinksReport, interpretUpdateCorrelation, isBrandQuery, isSkippableReportError, isTelemetryCi, isTelemetryOperation, isTelemetryReport, isoDate, isoWeek, latestFinalGscDate, linkRecover, linkRecoverRecommendation, listAnalyticsAccounts, listGa4AccountSummaries, listGa4DataStreams, listGoogleAccounts, listInstalledProviderExtensions, listRules, listSearchConsoleSites, listSearchUpdates, listSites, loadInstalledProviderExtensions, loadProviderExtensions, loadProviderPackage, loadSearchConsoleExport, localBing, localCredentialsProvider, localError, localGoogleFetch, localResearchKeys, loginWithLoopback, makeRunId, matchGa4WebStreams, measureCoverage, metaRobotsDirectives, migrateLegacyMacConfig, missingOAuthClientMessage, missingSharedClientLoginMessage, nodeCommandDeps, normalizeBrandText, normalizeClickySiteId, normalizeClickySiteKey, normalizeCrawlConfig, normalizeCrawlSkipReasonCounts, normalizeForCoverage, normalizeHttpUrl, normalizeJavaScriptRenderingMode, normalizeLoadedCrawlReport, normalizeProviderExtensionAccount, normalizedResearchColumnName, noteCacheWrite, pageEvidenceRecommendation, pageOpportunitiesReport, parseOlderThan, performanceAudit, performanceReportIsCacheable, plural, providerCapabilitySchema, providerConnectionFieldSchema, providerExactVersionSchema, providerExtensionIdSchema, providerIdSchema, providerIntegritySchema, providerKindSchema, providerPackageManifestSchema, providerPackageNameSchema, providerPreferenceSchema, providerRegistrationSchema, providerRequestJson, providerRequestText, pruneLogs, publicHttpFetch, queryClusterReport, queryClusterTokens, queryContentCoverageFromPage, queryContentFieldsFromPage, queryFieldCoverage, queryOpportunityRecommendation, queryPageMetrics, queryPageTopQuery, queryPagesMetrics, queryPagesMetricsBatch, queryPagesTopQueries, queryPagesTopQueriesBatch, querySearchAnalytics, quickWinsCsvRows, quickWinsReport, quickWinsReportWith, readAhrefsApiKey, readAllTokens, readBingApiKey, readBoundedResponseText, readClickySiteKey, readConfig, readDataForSeoCredentials, readInstalledProviderPackages, readJson, readJsonOrUndefined, readOauthClient, readProviderExtensionAccount, readProviderExtensionCredentials, readProviderSecret, readTokens, recommendationForRule, reconcileExportPagesWithCrawl, redirectTrace, refreshAuthToken, refreshPrioritiesCsvFiles, refreshPrioritiesWorkflow, registrableDomain, removeInstalledProviderPackage, renderCrawlCsv, renderCrawlHtml, renderCrawlJunit, renderCrawlMarkdownTickets, renderCrawlPagesCsv, renderCrawlPretty, renderCsv, renderPresentationTablesMarkdown, renderWorkflowMarkdown, reportNarrative, requestHeaders, requirePageVerification, researchImportColumns, resetSeoData, resolveLocalSite, resolveSegmentRanges, resolveSeoCliPaths, reviewObservations, roundedPosition, runAnalyticsProviderLandingPages, runAnalyticsReport, runDoctor, runGa4Report, runProviderExtensionAction, runtimeForProvider, safeProviderAdapterError, saveInstalledProviderPackage, searchAnalyticsRows, searchUpdatesFromIncidents, secondPage, secondPageWith, segmentComparisonRange, segmentImpact, segmentRangeDays, seoErrorEnvelope, sessionSourceInListFilter, setActiveGoogleAccount, setTelemetryEnabled, setTokenStorageMode, shiftIsoDate, shouldExcludeBrandQuery, strikingDistance, strikingDistanceWith, summarizeCrawlReport, pageTechnicalSignals as technicalPageSignals, telemetryErrorCategory, toSeoError, tokenSchema, tokenStoreSchema, topFixes, trackTelemetryCommandFailed, trackTelemetryReportComplete, trackTelemetryReportFailed, trackTelemetryReportStart, trackTelemetrySetupComplete, trafficAnomaly, trafficAnomalyWith, unavailableSegmentImpactReport, unicodeTokens, uninstallProviderExtension, updateCorrelation, updatePostmortemCsvFiles, updatePostmortemWorkflow, validateResearchImportColumnSources, verifyAnalyticsProvider, verifyProviderExtension, verifyQueryContent, verifyQueryContentWith, withAgentReportContract, workflowPresentation, writeAhrefsApiKey, writeBingApiKey, writeClickySiteKey, writeConfig, writeDataForSeoCredentials, writeInstalledProviderPackages, writeOauthClient, writeProviderExtensionAccount, writeProviderExtensionCredentials, writeProviderSecret, writeTokens, xRobotsDirectives };
|
|
28954
|
+
export { AHREFS_API_KEY_ENV, AHREFS_API_KEY_SECRET, AI_REFERRAL_SOURCES, AI_REFERRAL_SOURCES_VERSION, ANALYTICS_PROVIDER_IDS, AUDIT_PAGE_RULE_IDS, AhrefsClient, BING_API_KEY_ENV, BING_API_KEY_SECRET, BROWSER_USER_AGENT, CANNIBAL_MAXIMUM_DOMINANT_SHARE, CANNIBAL_MINIMUM_PAGE_SHARE, CLICKY_SITEKEY_ENV, CLICKY_SITE_KEYS_SECRET, CLOUD_SIGN_IN, CLOUD_URL, CRAWL_SKIP_REASONS, CTR_DEFAULT_LIMIT, CTR_MAX_LIMIT, CTR_MAX_MIN_IMPRESSIONS, DATAFORSEO_CREDENTIAL_SECRET, DATAFORSEO_LOGIN_ENV, DATAFORSEO_PASSWORD_ENV, DEFAULT_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, DEFAULT_UNREACHED_PAGE_LIMIT, DataForSeoClient, GSC_FINAL_DATA_LAG_DAYS, GSC_TIME_ZONE, GoogleApiError, INCIDENTS_URL, INTERNAL_LINK_LEXICAL_TARGET_LIMIT, INTERNAL_LINK_MATCH_EVIDENCE_LIMIT, JsonFileStorageAdapter, LOCAL_BING_CONNECT, LOCAL_SIGN_IN, LOG_MAX_AGE_MS, LOG_MAX_FILE_BYTES, LOG_MAX_TOTAL_BYTES, LocalCredentialsProvider, MANAGED_PROVIDER_SECRET_NAMES, MAX_CRAWL_CONCURRENCY, MAX_CRAWL_DEPTH, MAX_CRAWL_PAGES, MAX_CRAWL_TIMEOUT_MS, MAX_SEARCH_CONSOLE_EXPORT_FILES, MAX_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, PROVIDER_SECRET_NAMES, ProviderError, ProviderExtensionRegistry, RESEARCH_IMPORT_COLUMN_KEYS, ResponseSizeLimitError, SEO_CRAWLER_IDENTITY, SEO_CRAWLER_TOKEN, SEO_CRAWLER_USER_AGENT, SEO_PROVIDER_API_VERSION, SEO_VERSION, SERP_TITLE_REFERENCE_WIDTH_PX, SERP_TITLE_WIDTH_PROFILE, STEP_CHANGE_RULES, SeoError, ServiceAccountAccessTokenClient, SessionLedger, TELEMETRY_AGENTS, TELEMETRY_ENDPOINT, TELEMETRY_ERROR_CATEGORIES, TELEMETRY_EVENTS, TELEMETRY_FAILURE_CONTEXTS, TELEMETRY_FAILURE_REASONS, TELEMETRY_NOTICE, TELEMETRY_OPERATIONS, TELEMETRY_REPORTS, TELEMETRY_SCHEMA_VERSION, TELEMETRY_TIMEOUT_MS, agentActionsView, aiReferralSourceForValue, aiReferralsReport, analyticsConnectionLabel, analyticsConnectionProviderId, analyticsConnectionSchema, analyticsProviderDetails, analyticsProviderSchema, analyzeAiReferralRows, analyzeCannibalImportRows, analyzeCannibalRows, analyzeCtrUnderperformersFromRows, analyzeDecay, analyzeIndexCoverageSignals, analyzeInternalLinksFromRows, analyzePageOpportunitiesFromRows, analyzeQueryClustersFromRows, analyzeQuickWinsFromRows, analyzeSecondPageRows, analyzeSegmentRows, analyzeStrikingDistanceRows, analyzeTrafficAnomalyRows, assertCrawlConfigLimits, assertUrlMatchesGscProperty, auditCrawlPages, auditCrawlRequests, auditPage, authStatus, bearerFetch, boundedSitemapInventory, buildAgentFindings, buildQueryClusterReportFromRows, buildTelemetryPayload, cannibalImportReport, cannibalReport, cannibalReportWith, changeWindow, checkDatabaseReadiness, classifyTelemetryFailure, clearCache, cloudAccountFor, cloudBing, cloudCredentials, cloudGoogleEmails, cloudGoogleToken, cloudKeys, cloudLinked, cloudLogout, clusterQueryRows, combineRobotsValues, compareSegmentRows, configSchema, contentCoverageRecommendation, contentCoverageType, contentOptimizationFromPageOpportunities, contentOptimizationReport, countLabel, crawlConfigHash, crawlConfigSchema, crawlDefinitionId, crawlFixToQueueItem, crawlImplementationQueueWorkflow, crawlIssueGroupSchema, crawlIssueSchema, crawlOne, crawlPageSnapshotSchema, crawlReportSchema, crawlReportSummarySchema, crawlRequestObservationSchema, crawlRunId, crawlSite, crawlSkipImpact, crawlSkipReasonCountsFromRecord, crawlStatusOnly, crawlTopFixSchema, crawlerJsonSchemas, crawlerRuleCategorySchema, crawlerRuleIdSchema, crawlerRuleInfoSchema, crawlerRuleRecommendationSchema, crawlerRuleSeveritySchema, crawlerSchemas, createAuthorizedClient, createCrawlReport, createCtrBenchmarkContext, createGoogleAccessTokenClient, createLogger, createPageRenderer, ctrUnderperformersReport, ctrUnderperformersReportWith, decayClusterDrafts, decayComparisonRange, decayingReport, decayingReportWith, deleteAhrefsApiKey, deleteBingApiKey, deleteClickySiteKey, deleteDataForSeoCredentials, deleteManagedProviderSecrets, deleteProviderExtensionAccount, deleteProviderExtensionCredentials, deleteProviderSecret, deleteTokens, deriveBrandTerms, describeInstalledProviderExtension, detectBrandTerms, detectTelemetryAgent, diagnoseCsvFiles, diagnoseProperty, diagnosePropertyWorkflow, effectiveRobotsDirectives, effectiveSnippetControl, ensurePrivateDatabaseFiles, ensureSeoCliDirs, estimateSerpTitleWidth, expectedCtrForPosition, explainRule, fetchCruxFieldData, fetchPage, fetchPageStatus, fetchSearchStatusIncidents, fetchSitemapUrls, fileSize, finalGscDateRange, findOverlappingSearchUpdates, finishCloudLogin, formatRelativeExpiry, ga4MatchReason, ga4PropertyIdFromName, ga4ReportQualityWarnings, ga4RequestCanUseCache, ga4RowsToObjects, getAuthModeStatus, getCacheStats, getClientConfig, getDb, getPrivacySnapshot, getSeoCliPaths, getServiceAccountConfig, getServiceAccountStatus, getTelemetryStatus, getTokenStorageStatus, googleJson, groupCrawlIssues, hasMetaRobotsDirective, hasRule, hasXRobotsDirective, hashKey, indexCoverageSignals, inferTemplateMovement, initializeTelemetry, inspectProviderPackage, inspectSearchConsoleUrl, inspectUrl, installProviderPackage, installedProviderPackageSchema, installedProviderPackagesSchema, internalLinksReport, interpretUpdateCorrelation, isBrandQuery, isSkippableReportError, isTelemetryCi, isTelemetryOperation, isTelemetryReport, isoDate, isoWeek, latestFinalGscDate, linkRecover, linkRecoverRecommendation, listAnalyticsAccounts, listGa4AccountSummaries, listGa4DataStreams, listGoogleAccounts, listInstalledProviderExtensions, listRules, listSearchConsoleSites, listSearchUpdates, listSites, loadInstalledProviderExtensions, loadProviderExtensions, loadProviderPackage, loadSearchConsoleExport, localAccounts, localBing, localCredentialsProvider, localError, localGoogleFetch, localRefreshClicky, localResearchKeys, loginWithLoopback, makeRunId, matchGa4WebStreams, measureCoverage, metaRobotsDirectives, migrateLegacyMacConfig, missingOAuthClientMessage, missingSharedClientLoginMessage, nodeCommandDeps, normalizeBrandText, normalizeClickySiteId, normalizeClickySiteKey, normalizeCrawlConfig, normalizeCrawlSkipReasonCounts, normalizeForCoverage, normalizeHttpUrl, normalizeJavaScriptRenderingMode, normalizeLoadedCrawlReport, normalizeProviderExtensionAccount, normalizedResearchColumnName, notSignedIn, noteCacheWrite, openCloudLink, pageEvidenceRecommendation, pageOpportunitiesReport, parseOlderThan, pendingCloudLogin, performanceAudit, performanceReportIsCacheable, plural, providerCapabilitySchema, providerConnectionFieldSchema, providerExactVersionSchema, providerExtensionIdSchema, providerIdSchema, providerIntegritySchema, providerKindSchema, providerPackageManifestSchema, providerPackageNameSchema, providerPreferenceSchema, providerRegistrationSchema, providerRequestJson, providerRequestText, pruneLogs, publicHttpFetch, queryClusterReport, queryClusterTokens, queryContentCoverageFromPage, queryContentFieldsFromPage, queryFieldCoverage, queryOpportunityRecommendation, queryPageMetrics, queryPageTopQuery, queryPagesMetrics, queryPagesMetricsBatch, queryPagesTopQueries, queryPagesTopQueriesBatch, querySearchAnalytics, quickWinsCsvRows, quickWinsReport, quickWinsReportWith, readAhrefsApiKey, readAllTokens, readBingApiKey, readBoundedResponseText, readClickySiteKey, readClickySiteKeys, readConfig, readDataForSeoCredentials, readInstalledProviderPackages, readJson, readJsonOrUndefined, readOauthClient, readProviderExtensionAccount, readProviderExtensionCredentials, readProviderSecret, readTokens, recommendationForRule, reconcileExportPagesWithCrawl, redirectTrace, refreshAuthToken, refreshPrioritiesCsvFiles, refreshPrioritiesWorkflow, registrableDomain, removeInstalledProviderPackage, renderCrawlCsv, renderCrawlHtml, renderCrawlJunit, renderCrawlMarkdownTickets, renderCrawlPagesCsv, renderCrawlPretty, renderCsv, renderPresentationTablesMarkdown, renderWorkflowMarkdown, reportNarrative, requestHeaders, requirePageVerification, researchImportColumns, resetSeoData, resolveLocalSite, resolveSegmentRanges, resolveSeoCliPaths, reviewObservations, roundedPosition, runAnalyticsProviderLandingPages, runAnalyticsReport, runDoctor, runGa4Report, runProviderExtensionAction, runtimeForProvider, safeProviderAdapterError, saveInstalledProviderPackage, searchAnalyticsRows, searchUpdatesFromIncidents, secondPage, secondPageWith, segmentComparisonRange, segmentImpact, segmentRangeDays, seoErrorEnvelope, sessionSourceInListFilter, setActiveGoogleAccount, setTelemetryEnabled, setTokenStorageMode, shiftIsoDate, shouldExcludeBrandQuery, startCloudLogin, strikingDistance, strikingDistanceWith, summarizeCrawlReport, pageTechnicalSignals as technicalPageSignals, telemetryErrorCategory, toSeoError, tokenSchema, tokenStoreSchema, topFixes, trackTelemetryCommandFailed, trackTelemetryReportComplete, trackTelemetryReportFailed, trackTelemetryReportStart, trackTelemetrySetupComplete, trafficAnomaly, trafficAnomalyWith, unavailableSegmentImpactReport, unicodeTokens, uninstallProviderExtension, updateCorrelation, updatePostmortemCsvFiles, updatePostmortemWorkflow, validateResearchImportColumnSources, verifyAnalyticsProvider, verifyProviderExtension, verifyQueryContent, verifyQueryContentWith, withAgentReportContract, workflowPresentation, writeAhrefsApiKey, writeBingApiKey, writeClickySiteKey, writeConfig, writeDataForSeoCredentials, writeInstalledProviderPackages, writeOauthClient, writeProviderExtensionAccount, writeProviderExtensionCredentials, writeProviderSecret, writeTokens, xRobotsDirectives };
|
|
28657
28955
|
|
|
28658
28956
|
//# sourceMappingURL=index.js.map
|