seo 0.4.1 → 0.5.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 +21 -8
- package/dist/cli.js +78 -76
- package/dist/cli.js.map +1 -1
- package/dist/{dist-FO7n-64s.js → dist-BIlBtrjd.js} +1 -1
- package/dist/{dist-DjclF3Qk.js → dist-CN4VawW-.js} +84 -84
- package/dist/dist-CN4VawW-.js.map +1 -0
- package/dist/index.d.ts +91 -45
- package/dist/index.js +607 -326
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +5857 -5615
- package/dist/mcp.js.map +1 -1
- package/dist/{package-CeYsRRpB.js → package-CsRkLZGn.js} +2 -2
- package/dist/{package-CeYsRRpB.js.map → package-CsRkLZGn.js.map} +1 -1
- package/package.json +2 -2
- package/skills/seo/SKILL.md +17 -16
- package/dist/dist-DjclF3Qk.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-CsRkLZGn.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$1 = 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$1 })).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() {
|
|
2092
|
+
const current = credentials ? await credentials.catch(() => null) : null;
|
|
2093
|
+
if (current && !expiresSoon(current)) 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", { 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"),
|
|
@@ -1747,78 +2251,39 @@ const analyticsProviderSchema = z.enum(["google", "clicky"]);
|
|
|
1747
2251
|
const analyticsExtensionIdSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u);
|
|
1748
2252
|
const analyticsExtensionAccountSchema = z.record(z.string().min(1).max(64).regex(/^[A-Za-z][A-Za-z0-9-]*$/u).refine((value) => ![
|
|
1749
2253
|
"constructor",
|
|
1750
|
-
"prototype",
|
|
1751
|
-
"__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
|
-
}
|
|
2254
|
+
"prototype",
|
|
2255
|
+
"__proto__"
|
|
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
|
}
|
|
@@ -16279,7 +16708,7 @@ async function ctrUnderperformersReportWith(input, dependencies) {
|
|
|
16279
16708
|
label: "Days"
|
|
16280
16709
|
});
|
|
16281
16710
|
const now = dependencies.now();
|
|
16282
|
-
const range = explicitRange?.range ?? defaultDateRange$
|
|
16711
|
+
const range = explicitRange?.range ?? defaultDateRange$2(days, now);
|
|
16283
16712
|
const source = await dependencies.searchAnalytics(input.site, {
|
|
16284
16713
|
...range,
|
|
16285
16714
|
dimensions: ["query", "page"],
|
|
@@ -16721,7 +17150,7 @@ function analyzeSegmentRows(input) {
|
|
|
16721
17150
|
//#region packages/core/src/analyze/segment-impact-input.ts
|
|
16722
17151
|
const DAY_MS = 864e5;
|
|
16723
17152
|
const MAX_WINDOW_DAYS = 240;
|
|
16724
|
-
function subtractDays
|
|
17153
|
+
function subtractDays(value, days) {
|
|
16725
17154
|
const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
16726
17155
|
date.setUTCDate(date.getUTCDate() - days);
|
|
16727
17156
|
return date.toISOString().slice(0, 10);
|
|
@@ -16730,9 +17159,9 @@ function segmentRangeDays(range) {
|
|
|
16730
17159
|
return Math.floor((Date.parse(`${range.endDate}T00:00:00.000Z`) - Date.parse(`${range.startDate}T00:00:00.000Z`)) / DAY_MS) + 1;
|
|
16731
17160
|
}
|
|
16732
17161
|
function segmentComparisonRange(after, compareDays) {
|
|
16733
|
-
const endDate = subtractDays
|
|
17162
|
+
const endDate = subtractDays(after.startDate, 1);
|
|
16734
17163
|
return {
|
|
16735
|
-
startDate: subtractDays
|
|
17164
|
+
startDate: subtractDays(endDate, compareDays - 1),
|
|
16736
17165
|
endDate
|
|
16737
17166
|
};
|
|
16738
17167
|
}
|
|
@@ -16740,7 +17169,7 @@ function validateSegmentRanges(input) {
|
|
|
16740
17169
|
const before = explicitDateRange(input.before, MAX_WINDOW_DAYS);
|
|
16741
17170
|
const after = explicitDateRange(input.after, MAX_WINDOW_DAYS);
|
|
16742
17171
|
if (!before || !after) throw new SeoError("INVALID_INPUT", "Both segment ranges are required.");
|
|
16743
|
-
if (subtractDays
|
|
17172
|
+
if (subtractDays(input.after.startDate, 1) !== input.before.endDate) throw new SeoError("INVALID_INPUT", "Segment comparison windows must be adjacent.");
|
|
16744
17173
|
if (before.days !== after.days) throw new SeoError("INVALID_INPUT", "Segment comparison windows must have equal lengths.");
|
|
16745
17174
|
return {
|
|
16746
17175
|
beforeDays: before.days,
|
|
@@ -16755,7 +17184,7 @@ function resolveSegmentRanges(input) {
|
|
|
16755
17184
|
maximum: MAX_WINDOW_DAYS,
|
|
16756
17185
|
label: "days"
|
|
16757
17186
|
});
|
|
16758
|
-
const after = explicitDateRange(input, MAX_WINDOW_DAYS)?.range ?? defaultDateRange$
|
|
17187
|
+
const after = explicitDateRange(input, MAX_WINDOW_DAYS)?.range ?? defaultDateRange$2(days, input.now);
|
|
16759
17188
|
const compareDays = integerOption$1({
|
|
16760
17189
|
value: input.compareDays,
|
|
16761
17190
|
fallback: segmentRangeDays(after),
|
|
@@ -16774,63 +17203,6 @@ function validateSegmentSite(site) {
|
|
|
16774
17203
|
if (!site.trim()) throw new SeoError("INVALID_INPUT", "site must not be empty.");
|
|
16775
17204
|
}
|
|
16776
17205
|
//#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
17206
|
//#region packages/core/src/analyze/segment-impact-fallback.ts
|
|
16835
17207
|
function unavailableSegmentImpactReport(input) {
|
|
16836
17208
|
const days = segmentRangeDays(input.after);
|
|
@@ -18691,7 +19063,7 @@ async function cannibalReportWith(input, dependencies) {
|
|
|
18691
19063
|
});
|
|
18692
19064
|
const now = dependencies.now();
|
|
18693
19065
|
const explicit = explicitDateRange(input, 548);
|
|
18694
|
-
const range = explicit?.range ?? defaultDateRange$
|
|
19066
|
+
const range = explicit?.range ?? defaultDateRange$2(days, now);
|
|
18695
19067
|
const rangeDays = explicit?.days ?? days;
|
|
18696
19068
|
const requestBase = {
|
|
18697
19069
|
...range,
|
|
@@ -19149,7 +19521,7 @@ async function decayingReportWith(input, dependencies) {
|
|
|
19149
19521
|
});
|
|
19150
19522
|
const now = dependencies.now();
|
|
19151
19523
|
const explicit = explicitDateRange(input, 548);
|
|
19152
|
-
const currentRange = explicit?.range ?? defaultDateRange$
|
|
19524
|
+
const currentRange = explicit?.range ?? defaultDateRange$2(days, now);
|
|
19153
19525
|
const comparison = input.comparison ?? "previous-period";
|
|
19154
19526
|
const previousRange = decayComparisonRange(currentRange, comparison);
|
|
19155
19527
|
validateDecayRanges({
|
|
@@ -19622,7 +19994,7 @@ async function quickWinsReportWith(input, dependencies) {
|
|
|
19622
19994
|
label: "Verification limit"
|
|
19623
19995
|
});
|
|
19624
19996
|
const generatedAt = dependencies.now().toISOString();
|
|
19625
|
-
const range = explicitRange?.range ?? defaultDateRange$
|
|
19997
|
+
const range = explicitRange?.range ?? defaultDateRange$2(days, new Date(generatedAt));
|
|
19626
19998
|
const source = await dependencies.searchAnalytics(input.site, {
|
|
19627
19999
|
...range,
|
|
19628
20000
|
dimensions: ["query", "page"],
|
|
@@ -20046,7 +20418,7 @@ function reportDays(value) {
|
|
|
20046
20418
|
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
20419
|
return value;
|
|
20048
20420
|
}
|
|
20049
|
-
function dateRange
|
|
20421
|
+
function dateRange(days, now) {
|
|
20050
20422
|
return finalGscDateRange(days, now);
|
|
20051
20423
|
}
|
|
20052
20424
|
function verifiedRecommendation(coverage) {
|
|
@@ -20086,7 +20458,7 @@ async function strikingDistanceWith(input, dependencies) {
|
|
|
20086
20458
|
const explicitRange = explicitDateRange(input, MAX_DAYS);
|
|
20087
20459
|
const days = explicitRange?.days ?? reportDays(input.days);
|
|
20088
20460
|
const generatedAt = dependencies.now().toISOString();
|
|
20089
|
-
const range = explicitRange?.range ?? dateRange
|
|
20461
|
+
const range = explicitRange?.range ?? dateRange(days, new Date(generatedAt));
|
|
20090
20462
|
const result = await dependencies.searchAnalytics(input.site, {
|
|
20091
20463
|
...range,
|
|
20092
20464
|
dimensions: ["query", "page"],
|
|
@@ -20198,44 +20570,6 @@ function strikingDistance(input, dependencies = defaultDependencies$5) {
|
|
|
20198
20570
|
return strikingDistanceWith(input, dependencies);
|
|
20199
20571
|
}
|
|
20200
20572
|
//#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
20573
|
//#region packages/core/src/updates/search-status.ts
|
|
20240
20574
|
const INCIDENTS_URL = "https://status.search.google.com/incidents.json";
|
|
20241
20575
|
async function fetchSearchStatusIncidents() {
|
|
@@ -20612,7 +20946,7 @@ async function trafficAnomalyWith(input, dependencies) {
|
|
|
20612
20946
|
const range = input.startDate && input.endDate ? {
|
|
20613
20947
|
startDate: input.startDate,
|
|
20614
20948
|
endDate: input.endDate
|
|
20615
|
-
} : defaultDateRange$
|
|
20949
|
+
} : defaultDateRange$1(days, dependencies.now());
|
|
20616
20950
|
const result = await dependencies.searchAnalytics(input.site, {
|
|
20617
20951
|
...range,
|
|
20618
20952
|
dimensions: ["date"],
|
|
@@ -20704,7 +21038,7 @@ function fallbackRange(input) {
|
|
|
20704
21038
|
return input.startDate && input.endDate ? {
|
|
20705
21039
|
startDate: input.startDate,
|
|
20706
21040
|
endDate: input.endDate
|
|
20707
|
-
} : defaultDateRange$
|
|
21041
|
+
} : defaultDateRange$1(input.days ?? 28);
|
|
20708
21042
|
}
|
|
20709
21043
|
function emptyAnomaly(input) {
|
|
20710
21044
|
return {
|
|
@@ -20909,7 +21243,7 @@ function emptyStriking(input) {
|
|
|
20909
21243
|
return {
|
|
20910
21244
|
site: input.site,
|
|
20911
21245
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20912
|
-
range: defaultDateRange$
|
|
21246
|
+
range: defaultDateRange$1(input.days ?? 28),
|
|
20913
21247
|
rangeDays: input.days ?? 28,
|
|
20914
21248
|
source: {
|
|
20915
21249
|
provider: "google-search-console",
|
|
@@ -22826,7 +23160,7 @@ function rowMetrics(row) {
|
|
|
22826
23160
|
}
|
|
22827
23161
|
async function linkRecover(input) {
|
|
22828
23162
|
const days = input.days ?? 90;
|
|
22829
|
-
const range = defaultDateRange$
|
|
23163
|
+
const range = defaultDateRange$1(days);
|
|
22830
23164
|
const result = await querySearchAnalytics(input.site, {
|
|
22831
23165
|
...range,
|
|
22832
23166
|
dimensions: ["page"],
|
|
@@ -23990,7 +24324,7 @@ async function queryClusterReport(input) {
|
|
|
23990
24324
|
maximum: 548,
|
|
23991
24325
|
label: "days"
|
|
23992
24326
|
});
|
|
23993
|
-
const range = defaultDateRange$
|
|
24327
|
+
const range = defaultDateRange$1(days);
|
|
23994
24328
|
const filters = input.scope ? [{
|
|
23995
24329
|
groupType: "and",
|
|
23996
24330
|
filters: [{
|
|
@@ -25309,7 +25643,7 @@ async function refreshPrioritiesWorkflow(input) {
|
|
|
25309
25643
|
verifyContent: input.verifyContent ?? true,
|
|
25310
25644
|
verifyLimit: input.verifyContent === false ? void 0 : input.verifyLimit ?? Math.min(input.limit ?? 5, 5)
|
|
25311
25645
|
});
|
|
25312
|
-
const range = defaultDateRange$
|
|
25646
|
+
const range = defaultDateRange$1(input.days ?? 28);
|
|
25313
25647
|
const analytics = await fetchLandingPageValues({
|
|
25314
25648
|
connection: input.analyticsConnection,
|
|
25315
25649
|
propertyId: input.googleAnalyticsPropertyId,
|
|
@@ -25840,67 +26174,6 @@ async function detectBrandTerms(input) {
|
|
|
25840
26174
|
candidates: ranked
|
|
25841
26175
|
};
|
|
25842
26176
|
}
|
|
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
26177
|
const DIMENSIONS = [
|
|
25905
26178
|
"query",
|
|
25906
26179
|
"page",
|
|
@@ -26038,12 +26311,6 @@ const SECTIONS = {
|
|
|
26038
26311
|
};
|
|
26039
26312
|
const SECTION_IDS = Object.keys(SECTIONS);
|
|
26040
26313
|
//#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
26314
|
//#region packages/core/src/commands/research.ts
|
|
26048
26315
|
const RESEARCH_ACTIONS = [
|
|
26049
26316
|
"keyword_metrics",
|
|
@@ -26104,7 +26371,7 @@ z.object({}), `${Object.entries(SECTIONS).map(([id, text]) => `- ${id}: ${text}`
|
|
|
26104
26371
|
}), `${LOST_PAGES_RULE}`, z.object({
|
|
26105
26372
|
site,
|
|
26106
26373
|
...dates
|
|
26107
|
-
}),
|
|
26374
|
+
}), z.object({
|
|
26108
26375
|
provider: z.enum(RESEARCH_PROVIDERS),
|
|
26109
26376
|
action: z.enum(RESEARCH_ACTIONS),
|
|
26110
26377
|
keywords: z.array(z.string().min(1)).max(100).default([]),
|
|
@@ -26332,15 +26599,29 @@ function nodeCommandDeps(options) {
|
|
|
26332
26599
|
bingConnect: LOCAL_BING_CONNECT
|
|
26333
26600
|
};
|
|
26334
26601
|
}
|
|
26335
|
-
const LOCAL_BING_CONNECT = "
|
|
26602
|
+
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
26603
|
let bing;
|
|
26337
26604
|
function localBing() {
|
|
26338
|
-
bing ??= readBingApiKey().catch(() => void 0).then((key) => key ? bingCall({ apiKey: key.apiKey }, { signIn: LOCAL_BING_CONNECT }) : null);
|
|
26605
|
+
bing ??= readBingApiKey().catch(() => void 0).then((key) => key ? bingCall({ apiKey: key.apiKey }, { signIn: LOCAL_BING_CONNECT }) : cloudLinked() ? cloudBing() : null);
|
|
26339
26606
|
return bing;
|
|
26340
26607
|
}
|
|
26608
|
+
async function localAccounts() {
|
|
26609
|
+
if (!cloudLinked()) return [{
|
|
26610
|
+
email: null,
|
|
26611
|
+
google: await localGoogleFetch()
|
|
26612
|
+
}];
|
|
26613
|
+
return (await cloudGoogleEmails()).map((email) => ({
|
|
26614
|
+
email,
|
|
26615
|
+
google: bearerFetch(() => cloudGoogleToken(email))
|
|
26616
|
+
}));
|
|
26617
|
+
}
|
|
26341
26618
|
async function localResearchKeys() {
|
|
26342
|
-
const [dataForSeo, ahrefs] = await Promise.all([
|
|
26343
|
-
|
|
26619
|
+
const [dataForSeo, ahrefs, saved] = await Promise.all([
|
|
26620
|
+
readDataForSeoCredentials().catch(() => void 0),
|
|
26621
|
+
readAhrefsApiKey().catch(() => void 0),
|
|
26622
|
+
cloudLinked() ? cloudKeys().catch(() => ({})) : {}
|
|
26623
|
+
]);
|
|
26624
|
+
const keys = new Headers(saved);
|
|
26344
26625
|
if (dataForSeo) {
|
|
26345
26626
|
keys.set("x-dataforseo-login", dataForSeo.login);
|
|
26346
26627
|
keys.set("x-dataforseo-password", dataForSeo.password);
|
|
@@ -28653,6 +28934,6 @@ function isoDate(daysAgo = 0) {
|
|
|
28653
28934
|
return date.toISOString().slice(0, 10);
|
|
28654
28935
|
}
|
|
28655
28936
|
//#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 };
|
|
28937
|
+
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, 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, 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
28938
|
|
|
28658
28939
|
//# sourceMappingURL=index.js.map
|