seo 0.10.0 → 0.12.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 +22 -7
- package/README.md +1 -1
- package/dist/cli.js +77 -77
- package/dist/cli.js.map +1 -1
- package/dist/{dist-BvbFtizP.js → dist-B1bivk1S.js} +1 -1
- package/dist/{dist-CsWo-wJG.js → dist-Ban8uP1U.js} +117 -101
- package/dist/dist-Ban8uP1U.js.map +1 -0
- package/dist/{esm-CM7xTOjT.js → esm-CQJgJ8TD.js} +2 -2
- package/dist/{esm-CM7xTOjT.js.map → esm-CQJgJ8TD.js.map} +1 -1
- package/dist/index.d.ts +99 -45
- package/dist/index.js +378 -122
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +1022 -177
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
- package/skills/seo/SKILL.md +15 -1
- package/dist/dist-CsWo-wJG.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
|
|
2
|
-
import { n as version, t as load } from "./esm-
|
|
2
|
+
import { n as version, t as load } from "./esm-CQJgJ8TD.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";
|
|
@@ -27,6 +27,7 @@ import { TextDecoder as TextDecoder$1, promisify } from "node:util";
|
|
|
27
27
|
import { execFile } from "node:child_process";
|
|
28
28
|
import pino from "pino";
|
|
29
29
|
import pretty from "pino-pretty";
|
|
30
|
+
import { request } from "node:https";
|
|
30
31
|
//#region packages/core/src/analytics/providers.ts
|
|
31
32
|
const ANALYTICS_PROVIDER_IDS = ["google", "clicky"];
|
|
32
33
|
const ANALYTICS_PROVIDER_DETAILS = {
|
|
@@ -1613,7 +1614,7 @@ async function googleJson(gfetch, url, input) {
|
|
|
1613
1614
|
return readJson(response, input.label, { maxBytes: input.maxBytes });
|
|
1614
1615
|
}
|
|
1615
1616
|
const GSC = "https://www.googleapis.com/webmasters/v3";
|
|
1616
|
-
const PAGE_ROWS = 25e3;
|
|
1617
|
+
const PAGE_ROWS$1 = 25e3;
|
|
1617
1618
|
async function searchAnalyticsRows(gfetch, site, body, options) {
|
|
1618
1619
|
const rows = [];
|
|
1619
1620
|
const { maxRows: requested, ...apiBody } = body;
|
|
@@ -1624,7 +1625,7 @@ async function searchAnalyticsRows(gfetch, site, body, options) {
|
|
|
1624
1625
|
while (true) {
|
|
1625
1626
|
const remaining = typeof maxRows === "number" ? maxRows - rows.length : void 0;
|
|
1626
1627
|
if (typeof remaining === "number" && remaining <= 0) break;
|
|
1627
|
-
const rowLimit = Math.min(apiBody.rowLimit ?? PAGE_ROWS, remaining ?? PAGE_ROWS, PAGE_ROWS);
|
|
1628
|
+
const rowLimit = Math.min(apiBody.rowLimit ?? PAGE_ROWS$1, remaining ?? PAGE_ROWS$1, PAGE_ROWS$1);
|
|
1628
1629
|
const json = await googleJson(gfetch, url, {
|
|
1629
1630
|
...options,
|
|
1630
1631
|
label: "Search Console",
|
|
@@ -1701,7 +1702,7 @@ async function runAnalyticsReport(gfetch, propertyId, body, options) {
|
|
|
1701
1702
|
//#endregion
|
|
1702
1703
|
//#region packages/core/src/bing/api.ts
|
|
1703
1704
|
const BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
1704
|
-
const MAX_BYTES$
|
|
1705
|
+
const MAX_BYTES$4 = 8e6;
|
|
1705
1706
|
function bingCall(auth, options) {
|
|
1706
1707
|
return async (method, params = {}) => {
|
|
1707
1708
|
const url = new URL(`${BASE}/${method}`);
|
|
@@ -1726,7 +1727,7 @@ function bingCall(auth, options) {
|
|
|
1726
1727
|
if (response.status === 429 || /throttle/i.test(detail)) throw new SeoError("RATE_LIMITED", "Bing Webmaster is limiting requests. Try again in a few minutes.");
|
|
1727
1728
|
throw new SeoError("OPTIONAL_PROVIDER_UNAVAILABLE", `Bing Webmaster answered ${response.status}. ${detail}`.trim());
|
|
1728
1729
|
}
|
|
1729
|
-
return (await readJson(response, "Bing Webmaster", { maxBytes: MAX_BYTES$
|
|
1730
|
+
return (await readJson(response, "Bing Webmaster", { maxBytes: MAX_BYTES$4 })).d;
|
|
1730
1731
|
};
|
|
1731
1732
|
}
|
|
1732
1733
|
//#endregion
|
|
@@ -4262,6 +4263,7 @@ const CACHE_MAX_BYTES = 256 * MEBIBYTE$2;
|
|
|
4262
4263
|
const CACHE_MAX_AGE_MS = 720 * 60 * 60 * 1e3;
|
|
4263
4264
|
const CACHE_LIMITS = {
|
|
4264
4265
|
gsc_cache: 48 * MEBIBYTE$2,
|
|
4266
|
+
url_inspection_cache: 16 * MEBIBYTE$2,
|
|
4265
4267
|
ga4_cache: 24 * MEBIBYTE$2,
|
|
4266
4268
|
provider_cache: 32 * MEBIBYTE$2,
|
|
4267
4269
|
http_cache: 128 * MEBIBYTE$2,
|
|
@@ -4279,6 +4281,16 @@ const CACHE_POLICIES = [
|
|
|
4279
4281
|
"response_json"
|
|
4280
4282
|
]
|
|
4281
4283
|
},
|
|
4284
|
+
{
|
|
4285
|
+
table: "url_inspection_cache",
|
|
4286
|
+
keys: ["site_url", "url"],
|
|
4287
|
+
timestamp: "fetched_at",
|
|
4288
|
+
sizeColumns: [
|
|
4289
|
+
"site_url",
|
|
4290
|
+
"url",
|
|
4291
|
+
"status_json"
|
|
4292
|
+
]
|
|
4293
|
+
},
|
|
4282
4294
|
{
|
|
4283
4295
|
table: "ga4_cache",
|
|
4284
4296
|
keys: ["property_id", "query_hash"],
|
|
@@ -4582,6 +4594,17 @@ CREATE INDEX IF NOT EXISTS idx_url_inspection_reservations_minute
|
|
|
4582
4594
|
credential_key, property_site_url, created_at
|
|
4583
4595
|
);
|
|
4584
4596
|
|
|
4597
|
+
CREATE TABLE IF NOT EXISTS url_inspection_cache (
|
|
4598
|
+
site_url TEXT NOT NULL,
|
|
4599
|
+
url TEXT NOT NULL,
|
|
4600
|
+
status_json TEXT NOT NULL,
|
|
4601
|
+
fetched_at INTEGER NOT NULL,
|
|
4602
|
+
expires_at INTEGER NOT NULL,
|
|
4603
|
+
PRIMARY KEY(site_url, url)
|
|
4604
|
+
) WITHOUT ROWID;
|
|
4605
|
+
CREATE INDEX IF NOT EXISTS idx_url_inspection_cache_expires
|
|
4606
|
+
ON url_inspection_cache(expires_at);
|
|
4607
|
+
|
|
4585
4608
|
CREATE TABLE IF NOT EXISTS performance_reports (
|
|
4586
4609
|
id TEXT PRIMARY KEY,
|
|
4587
4610
|
url TEXT NOT NULL,
|
|
@@ -4614,10 +4637,12 @@ function initDb(database, isNewDatabase) {
|
|
|
4614
4637
|
database.exec("DROP TABLE IF EXISTS provider_spend_notices");
|
|
4615
4638
|
ensureColumn(database, "http_cache", "metadata_json", "TEXT");
|
|
4616
4639
|
}).immediate();
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4640
|
+
try {
|
|
4641
|
+
runCacheMaintenance(database, {
|
|
4642
|
+
compact: true,
|
|
4643
|
+
allowFullVacuum: true
|
|
4644
|
+
});
|
|
4645
|
+
} catch {}
|
|
4621
4646
|
}
|
|
4622
4647
|
function getDb() {
|
|
4623
4648
|
if (db) return db;
|
|
@@ -4628,12 +4653,41 @@ function getDb() {
|
|
|
4628
4653
|
mode: 448
|
|
4629
4654
|
});
|
|
4630
4655
|
ensurePrivateDatabaseFiles(path);
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4656
|
+
const opened = new SqliteDatabase(path);
|
|
4657
|
+
try {
|
|
4658
|
+
ensurePrivateDatabaseFiles(path);
|
|
4659
|
+
initDb(opened, isNewDatabase);
|
|
4660
|
+
ensurePrivateDatabaseFiles(path);
|
|
4661
|
+
} catch (error) {
|
|
4662
|
+
opened.close();
|
|
4663
|
+
throw error;
|
|
4664
|
+
}
|
|
4665
|
+
db = opened;
|
|
4635
4666
|
return db;
|
|
4636
4667
|
}
|
|
4668
|
+
let memoryDb;
|
|
4669
|
+
function memoryCache() {
|
|
4670
|
+
if (!memoryDb) {
|
|
4671
|
+
memoryDb = new SqliteDatabase(":memory:");
|
|
4672
|
+
memoryDb.exec(CREATE_SQL);
|
|
4673
|
+
}
|
|
4674
|
+
return memoryDb;
|
|
4675
|
+
}
|
|
4676
|
+
function cacheDb() {
|
|
4677
|
+
if (memoryDb) return memoryDb;
|
|
4678
|
+
try {
|
|
4679
|
+
return getDb();
|
|
4680
|
+
} catch {
|
|
4681
|
+
return memoryCache();
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
function writeToCache(write) {
|
|
4685
|
+
try {
|
|
4686
|
+
write(cacheDb());
|
|
4687
|
+
} catch {
|
|
4688
|
+
memoryCache();
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4637
4691
|
function noteCacheWrite(sizeBytes = 0, database = getDb()) {
|
|
4638
4692
|
const counter = cacheMaintenanceCounters.get(database) ?? {
|
|
4639
4693
|
writes: 0,
|
|
@@ -4662,6 +4716,7 @@ function getCacheStats() {
|
|
|
4662
4716
|
const counts = {
|
|
4663
4717
|
sites: database.prepare("SELECT COUNT(*) AS count FROM sites").get(),
|
|
4664
4718
|
gsc_cache: database.prepare("SELECT COUNT(*) AS count FROM gsc_cache").get(),
|
|
4719
|
+
url_inspection_cache: database.prepare("SELECT COUNT(*) AS count FROM url_inspection_cache").get(),
|
|
4665
4720
|
google_analytics_cache: database.prepare("SELECT COUNT(*) AS count FROM ga4_cache").get(),
|
|
4666
4721
|
provider_cache: { count: providerCount("dataforseo") },
|
|
4667
4722
|
ahrefs_cache: { count: providerCount("ahrefs") },
|
|
@@ -4693,8 +4748,9 @@ function clearCache(provider, olderThanMs) {
|
|
|
4693
4748
|
compactCacheDatabase(database, { allowFullVacuum: true });
|
|
4694
4749
|
return info.changes;
|
|
4695
4750
|
}
|
|
4696
|
-
const tables = provider === "gsc" ? ["gsc_cache"] : provider === "google-analytics" ? ["ga4_cache"] : provider === "http" ? ["http_cache"] : [
|
|
4751
|
+
const tables = provider === "gsc" ? ["gsc_cache", "url_inspection_cache"] : provider === "google-analytics" ? ["ga4_cache"] : provider === "http" ? ["http_cache"] : [
|
|
4697
4752
|
"gsc_cache",
|
|
4753
|
+
"url_inspection_cache",
|
|
4698
4754
|
"ga4_cache",
|
|
4699
4755
|
"provider_cache",
|
|
4700
4756
|
"http_cache"
|
|
@@ -4715,7 +4771,7 @@ function ga4RequestCanUseCache(body) {
|
|
|
4715
4771
|
}
|
|
4716
4772
|
async function runGa4Report(propertyId, body, opts = {}) {
|
|
4717
4773
|
const { client, identity } = await createGoogleAccessTokenClient(opts.accountEmail);
|
|
4718
|
-
const db =
|
|
4774
|
+
const db = cacheDb();
|
|
4719
4775
|
const queryHash = hashKey([
|
|
4720
4776
|
propertyId,
|
|
4721
4777
|
identity,
|
|
@@ -4728,10 +4784,12 @@ async function runGa4Report(propertyId, body, opts = {}) {
|
|
|
4728
4784
|
if (cacheable) {
|
|
4729
4785
|
const requestJson = JSON.stringify(body);
|
|
4730
4786
|
const responseJson = JSON.stringify(result);
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4787
|
+
writeToCache((database) => {
|
|
4788
|
+
database.prepare(`INSERT OR REPLACE INTO ga4_cache
|
|
4789
|
+
(property_id, query_hash, request_json, response_json, row_count, fetched_at, expires_at)
|
|
4790
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(propertyId, queryHash, requestJson, responseJson, result.rowCount ?? result.rows?.length ?? 0, Date.now(), Date.now() + 864e5);
|
|
4791
|
+
noteCacheWrite(Buffer.byteLength(requestJson) + Buffer.byteLength(responseJson), database);
|
|
4792
|
+
});
|
|
4735
4793
|
}
|
|
4736
4794
|
return result;
|
|
4737
4795
|
}
|
|
@@ -7190,7 +7248,7 @@ async function fetchRobotsSource(origin, refresh, options) {
|
|
|
7190
7248
|
const robotsUrl = new URL("/robots.txt", origin).toString();
|
|
7191
7249
|
const key = hashKey(["robots", robotsUrl]);
|
|
7192
7250
|
if (!refresh) {
|
|
7193
|
-
const cached =
|
|
7251
|
+
const cached = cacheDb().prepare("SELECT status, body_blob, expires_at FROM http_cache WHERE url_hash = ?").get(key);
|
|
7194
7252
|
if (cached?.body_blob && cached.status !== void 0 && cacheableStatus(cached.status) && cached.expires_at && cached.expires_at > Date.now()) return {
|
|
7195
7253
|
robotsUrl,
|
|
7196
7254
|
status: cached.status,
|
|
@@ -7209,12 +7267,12 @@ async function fetchRobotsSource(origin, refresh, options) {
|
|
|
7209
7267
|
signal: controller.signal
|
|
7210
7268
|
});
|
|
7211
7269
|
const text = await readBoundedResponseText(response, MAX_ROBOTS_RESPONSE_BYTES, "robots.txt response");
|
|
7212
|
-
if (options.writeCache !== false && cacheableStatus(response.status)) {
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
noteCacheWrite(Buffer.byteLength(text));
|
|
7217
|
-
}
|
|
7270
|
+
if (options.writeCache !== false && cacheableStatus(response.status)) writeToCache((database) => {
|
|
7271
|
+
database.prepare(`INSERT OR REPLACE INTO http_cache
|
|
7272
|
+
(url_hash, url, status, headers_json, body_blob, etag, fetched_at, expires_at)
|
|
7273
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(key, robotsUrl, response.status, "{}", Buffer.from(text), null, Date.now(), Date.now() + 864e5);
|
|
7274
|
+
noteCacheWrite(Buffer.byteLength(text), database);
|
|
7275
|
+
});
|
|
7218
7276
|
return {
|
|
7219
7277
|
robotsUrl,
|
|
7220
7278
|
status: response.status,
|
|
@@ -7321,7 +7379,6 @@ function decodePageFetchCacheEvidence(value) {
|
|
|
7321
7379
|
}
|
|
7322
7380
|
async function fetchPlain(url, refresh = false, timeoutMs = 2e4, rate, signal, respectRobots = false, writeCache = true) {
|
|
7323
7381
|
const startedAt = Date.now();
|
|
7324
|
-
const db = getDb();
|
|
7325
7382
|
const key = hashKey(["page", url]);
|
|
7326
7383
|
const host = new URL(url).host;
|
|
7327
7384
|
let robots = respectRobots ? await fetchRobots(new URL(url).origin, url, refresh, {
|
|
@@ -7329,7 +7386,7 @@ async function fetchPlain(url, refresh = false, timeoutMs = 2e4, rate, signal, r
|
|
|
7329
7386
|
signal
|
|
7330
7387
|
}) : void 0;
|
|
7331
7388
|
if (robots && robots.allowed !== true) throw new RobotsAccessError(robots.allowed === false ? "robots-disallowed" : "robots-deferred", robots);
|
|
7332
|
-
const cached =
|
|
7389
|
+
const cached = cacheDb().prepare("SELECT status, headers_json, body_blob, metadata_json, expires_at FROM http_cache WHERE url_hash = ? AND expires_at > ?").get(key, Date.now());
|
|
7333
7390
|
if (!refresh && cached) {
|
|
7334
7391
|
const evidence = decodePageFetchCacheEvidence(cached.metadata_json);
|
|
7335
7392
|
return {
|
|
@@ -7431,12 +7488,12 @@ async function fetchPlain(url, refresh = false, timeoutMs = 2e4, rate, signal, r
|
|
|
7431
7488
|
warnings: [...robots.allowed === null ? [`robots.txt availability is unknown${robots.status ? ` after HTTP ${robots.status}` : ""}; this fetch does not prove crawler access is allowed.`] : [], ...accessBlock ? [`${accessBlock.guidance.summary} Identify the crawler as ${accessBlock.crawler.userAgent}.`] : []],
|
|
7432
7489
|
robotsTxt: pageRobotsEvidence(robots)
|
|
7433
7490
|
};
|
|
7434
|
-
if (!accessBlock && writeCache) {
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
noteCacheWrite(Buffer.byteLength(html));
|
|
7439
|
-
}
|
|
7491
|
+
if (!accessBlock && writeCache) writeToCache((database) => {
|
|
7492
|
+
database.prepare(`INSERT OR REPLACE INTO http_cache
|
|
7493
|
+
(url_hash, url, status, headers_json, body_blob, metadata_json, etag, fetched_at, expires_at)
|
|
7494
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(key, url, response.response.status, JSON.stringify(headerMap), Buffer.from(html), encodePageFetchCacheEvidence(result), response.response.headers.get("etag"), Date.now(), Date.now() + 36e5);
|
|
7495
|
+
noteCacheWrite(Buffer.byteLength(html), database);
|
|
7496
|
+
});
|
|
7440
7497
|
return result;
|
|
7441
7498
|
}
|
|
7442
7499
|
//#endregion
|
|
@@ -8189,6 +8246,19 @@ function reserveUrlInspectionQuota(input) {
|
|
|
8189
8246
|
if ("error" in result) throw result.error;
|
|
8190
8247
|
return result.reservation;
|
|
8191
8248
|
}
|
|
8249
|
+
function readUrlInspectionQuota(input) {
|
|
8250
|
+
const database = input.database ?? getDb();
|
|
8251
|
+
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
8252
|
+
const bucket = database.prepare(`SELECT limit_count, used_count, reserved_count, blocked_until
|
|
8253
|
+
FROM url_inspection_quota_buckets
|
|
8254
|
+
WHERE credential_key = ? AND property_site_url = ? AND quota_date = ?`).get(input.credentialKey, input.property, urlInspectionQuotaDate(now));
|
|
8255
|
+
const limit = bucket?.limit_count ?? 2e3;
|
|
8256
|
+
return {
|
|
8257
|
+
used: (bucket?.blocked_until ?? 0) > now.getTime() ? limit : Math.min(limit, (bucket?.used_count ?? 0) + (bucket?.reserved_count ?? 0)),
|
|
8258
|
+
limit,
|
|
8259
|
+
resetAt: urlInspectionQuotaResetAt(now).toISOString()
|
|
8260
|
+
};
|
|
8261
|
+
}
|
|
8192
8262
|
function finalizeUrlInspectionQuota(input) {
|
|
8193
8263
|
const database = input.database ?? getDb();
|
|
8194
8264
|
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
@@ -8233,6 +8303,13 @@ function inspectionResponseError(response, input) {
|
|
|
8233
8303
|
if (response.status >= 500) return new SeoError("PROVIDER_UNAVAILABLE", `Google URL Inspection is unavailable (${response.status}). Try again later.`);
|
|
8234
8304
|
return new SeoError("PROVIDER_UNAVAILABLE", `URL Inspection failed with ${response.status}.`);
|
|
8235
8305
|
}
|
|
8306
|
+
async function urlInspectionQuota(input) {
|
|
8307
|
+
const { quotaIdentity } = await getAuthorized(input.accountEmail ?? await cloudAccountFor(input.siteUrl));
|
|
8308
|
+
return readUrlInspectionQuota({
|
|
8309
|
+
credentialKey: credentialKey(quotaIdentity),
|
|
8310
|
+
property: input.siteUrl
|
|
8311
|
+
});
|
|
8312
|
+
}
|
|
8236
8313
|
async function inspectUrl(input) {
|
|
8237
8314
|
const inspectionUrl = assertUrlMatchesGscProperty(input.siteUrl, input.inspectionUrl);
|
|
8238
8315
|
const { client, quotaIdentity } = await getAuthorized(input.accountEmail ?? await cloudAccountFor(input.siteUrl));
|
|
@@ -8294,7 +8371,7 @@ function resolveSearchDateWindow(input = 28) {
|
|
|
8294
8371
|
}
|
|
8295
8372
|
async function querySearchAnalytics(site, body, opts = {}) {
|
|
8296
8373
|
const { client, identity } = await getAuthorized(opts.accountEmail ?? await cloudAccountFor(site));
|
|
8297
|
-
const db =
|
|
8374
|
+
const db = cacheDb();
|
|
8298
8375
|
const queryHash = hashKey([
|
|
8299
8376
|
site,
|
|
8300
8377
|
identity,
|
|
@@ -8314,10 +8391,12 @@ async function querySearchAnalytics(site, body, opts = {}) {
|
|
|
8314
8391
|
const calls = result.calls;
|
|
8315
8392
|
const requestJson = JSON.stringify(body);
|
|
8316
8393
|
const responseJson = JSON.stringify(allRows);
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8394
|
+
writeToCache((database) => {
|
|
8395
|
+
database.prepare(`INSERT OR REPLACE INTO gsc_cache
|
|
8396
|
+
(site_url, query_hash, request_json, response_json, row_count, fetched_at, expires_at)
|
|
8397
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(site, queryHash, requestJson, responseJson, allRows.length, Date.now(), Date.now() + 864e5);
|
|
8398
|
+
noteCacheWrite(Buffer.byteLength(requestJson) + Buffer.byteLength(responseJson), database);
|
|
8399
|
+
});
|
|
8321
8400
|
return {
|
|
8322
8401
|
rows: allRows,
|
|
8323
8402
|
calls,
|
|
@@ -8453,8 +8532,10 @@ async function queryPagesTopQueriesBatch(site, pageUrls, range = 28) {
|
|
|
8453
8532
|
//#region packages/core/src/gsc/client/sites.ts
|
|
8454
8533
|
async function listSites(_refresh = false, accountEmail) {
|
|
8455
8534
|
const entries = !accountEmail && cloudLinked() ? await allCloudSites() : await listSearchConsoleSites(googleFetchFor((await getAuthorized(accountEmail)).client), LOCAL_SIGN_IN);
|
|
8456
|
-
|
|
8457
|
-
|
|
8535
|
+
writeToCache((database) => {
|
|
8536
|
+
const insert = database.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))");
|
|
8537
|
+
for (const site of entries) insert.run(site.siteUrl, site.siteUrl, site.permissionLevel ?? null, Date.now(), site.siteUrl);
|
|
8538
|
+
});
|
|
8458
8539
|
return entries;
|
|
8459
8540
|
}
|
|
8460
8541
|
async function allCloudSites() {
|
|
@@ -10132,7 +10213,7 @@ const actionPriority = new Map([
|
|
|
10132
10213
|
"unverified",
|
|
10133
10214
|
"covered"
|
|
10134
10215
|
].map((type, index) => [type, index]));
|
|
10135
|
-
function plural$
|
|
10216
|
+
function plural$7(count, singular, pluralLabel) {
|
|
10136
10217
|
return count === 1 ? singular : pluralLabel;
|
|
10137
10218
|
}
|
|
10138
10219
|
function actionable(item) {
|
|
@@ -10166,7 +10247,7 @@ function pageOpportunityVerdict(input) {
|
|
|
10166
10247
|
if (focus === "unverified") return "GSC visibility exists, but the page was not verified and no specific change is justified yet.";
|
|
10167
10248
|
if (focus === "covered") return "The verified page covers the sampled queries and CTR is not materially below its directional benchmark.";
|
|
10168
10249
|
const count = analysis.summary.opportunities;
|
|
10169
|
-
const prefix = `${count} ${plural$
|
|
10250
|
+
const prefix = `${count} ${plural$7(count, "actionable observation", "actionable observations")} found.`;
|
|
10170
10251
|
if (focus === "content-gap") return `${prefix} Verified body coverage is the main issue; confirm intent before expanding the page.`;
|
|
10171
10252
|
if (focus === "serp-framing") return `${prefix} The body is relevant, but its title, meta description, or H1 framing is weak.`;
|
|
10172
10253
|
if (focus === "ranking") return `${prefix} Most upside requires stronger relevance or authority; no CTR-only lift is claimed for page-two queries.`;
|
|
@@ -10181,7 +10262,7 @@ function pageOpportunityRecommendations(analysis) {
|
|
|
10181
10262
|
}
|
|
10182
10263
|
const recommendations = [`Start with "${top.query}". ${top.recommendation}`];
|
|
10183
10264
|
const technical = actions.filter((item) => item.opportunityType === "technical-check").length;
|
|
10184
|
-
if (technical) recommendations.push(`Resolve the technical evidence affecting ${technical} ${plural$
|
|
10265
|
+
if (technical) recommendations.push(`Resolve the technical evidence affecting ${technical} ${plural$7(technical, "row", "rows")} before content work.`);
|
|
10185
10266
|
const gaps = actions.filter((item) => item.opportunityType === "content-gap").length;
|
|
10186
10267
|
if (gaps >= 3) recommendations.push(`Group the ${gaps} verified content gaps into useful sections rather than adding one thin paragraph per query.`);
|
|
10187
10268
|
return recommendations;
|
|
@@ -14185,7 +14266,7 @@ function availabilityOf(status) {
|
|
|
14185
14266
|
function errorText(error) {
|
|
14186
14267
|
return error instanceof Error ? error.message : String(error);
|
|
14187
14268
|
}
|
|
14188
|
-
async function get(input, url, headers, readBody = true) {
|
|
14269
|
+
async function get$1(input, url, headers, readBody = true) {
|
|
14189
14270
|
const controller = abortController({
|
|
14190
14271
|
timeoutMs: input.timeoutMs,
|
|
14191
14272
|
signal: input.signal
|
|
@@ -14242,7 +14323,7 @@ function llmsLinks(text, base) {
|
|
|
14242
14323
|
return links;
|
|
14243
14324
|
}
|
|
14244
14325
|
async function checkLlmsTxt(input) {
|
|
14245
|
-
const result = await get(input, new URL("/llms.txt", input.url).toString());
|
|
14326
|
+
const result = await get$1(input, new URL("/llms.txt", input.url).toString());
|
|
14246
14327
|
const availability = result.availability === "available" && isHtml(result.contentType) ? "absent" : result.availability;
|
|
14247
14328
|
const exists = availability === "available";
|
|
14248
14329
|
const base = {
|
|
@@ -14259,7 +14340,7 @@ async function checkLlmsTxt(input) {
|
|
|
14259
14340
|
for (const url of found.slice(0, LLMS_LINK_LIMIT)) {
|
|
14260
14341
|
if (input.signal?.aborted) break;
|
|
14261
14342
|
checked += 1;
|
|
14262
|
-
const link = brokenLink(await get(input, url, void 0, false), false);
|
|
14343
|
+
const link = brokenLink(await get$1(input, url, void 0, false), false);
|
|
14263
14344
|
if (link) broken.push(link);
|
|
14264
14345
|
}
|
|
14265
14346
|
return {
|
|
@@ -14303,7 +14384,7 @@ function sitemapUrlsFromRobots(text) {
|
|
|
14303
14384
|
return [...urls];
|
|
14304
14385
|
}
|
|
14305
14386
|
async function checkRobotsAiAccess(input) {
|
|
14306
|
-
const result = await get(input, new URL("/robots.txt", input.url).toString());
|
|
14387
|
+
const result = await get$1(input, new URL("/robots.txt", input.url).toString());
|
|
14307
14388
|
const exists = result.availability === "available";
|
|
14308
14389
|
const unknown = result.availability === "rate-limited" || result.availability === "unreachable";
|
|
14309
14390
|
const declared = exists ? declaredUserAgents(result.text) : /* @__PURE__ */ new Set();
|
|
@@ -14332,7 +14413,7 @@ async function checkAgentResources(input) {
|
|
|
14332
14413
|
const resources = [];
|
|
14333
14414
|
for (const path of AGENT_RESOURCE_PATHS) {
|
|
14334
14415
|
if (input.signal?.aborted) break;
|
|
14335
|
-
const result = await get(input, new URL(path, input.url).toString());
|
|
14416
|
+
const result = await get$1(input, new URL(path, input.url).toString());
|
|
14336
14417
|
const exists = result.availability === "available" && !isHtml(result.contentType);
|
|
14337
14418
|
let validJson;
|
|
14338
14419
|
if (exists && /\bjson\b/i.test(result.contentType)) try {
|
|
@@ -14352,7 +14433,7 @@ async function checkAgentResources(input) {
|
|
|
14352
14433
|
return resources;
|
|
14353
14434
|
}
|
|
14354
14435
|
async function checkMarkdownNegotiation(input) {
|
|
14355
|
-
const result = await get(input, input.url, { accept: "text/markdown" }, false);
|
|
14436
|
+
const result = await get$1(input, input.url, { accept: "text/markdown" }, false);
|
|
14356
14437
|
const vary = result.headers?.get("vary") ?? "";
|
|
14357
14438
|
return {
|
|
14358
14439
|
url: result.url,
|
|
@@ -14399,7 +14480,7 @@ async function checkMarkdownCopies(input) {
|
|
|
14399
14480
|
const url = page.markdownAlternates?.[0];
|
|
14400
14481
|
if (!url || !input.allows(url)) continue;
|
|
14401
14482
|
checked += 1;
|
|
14402
|
-
const result = await get({
|
|
14483
|
+
const result = await get$1({
|
|
14403
14484
|
...input,
|
|
14404
14485
|
url
|
|
14405
14486
|
}, url);
|
|
@@ -15565,7 +15646,7 @@ function taskIds(value) {
|
|
|
15565
15646
|
}
|
|
15566
15647
|
}
|
|
15567
15648
|
function readProviderCache(key, schema, options = {}) {
|
|
15568
|
-
const database = options.database ??
|
|
15649
|
+
const database = options.database ?? cacheDb();
|
|
15569
15650
|
const now = options.now ?? Date.now();
|
|
15570
15651
|
const hash = requestHash(key, stableProviderRequestJson(key.request));
|
|
15571
15652
|
const row = database.prepare(`SELECT response_json, row_count, source_cost_micros, task_ids_json,
|
|
@@ -15586,9 +15667,11 @@ function readProviderCache(key, schema, options = {}) {
|
|
|
15586
15667
|
taskIds: taskIds(row.task_ids_json)
|
|
15587
15668
|
};
|
|
15588
15669
|
} catch {
|
|
15589
|
-
|
|
15590
|
-
|
|
15591
|
-
AND
|
|
15670
|
+
try {
|
|
15671
|
+
database.prepare(`DELETE FROM provider_cache
|
|
15672
|
+
WHERE provider = ? AND credential_scope = ? AND operation = ?
|
|
15673
|
+
AND request_hash = ?`).run(key.provider, key.credentialScope, key.operation, hash);
|
|
15674
|
+
} catch {}
|
|
15592
15675
|
return null;
|
|
15593
15676
|
}
|
|
15594
15677
|
}
|
|
@@ -15599,19 +15682,22 @@ function writeProviderCache(key, input, options = {}) {
|
|
|
15599
15682
|
code: "configuration",
|
|
15600
15683
|
message: `Provider cache TTL must be between 1 and ${CACHE_MAX_AGE_MS} milliseconds.`
|
|
15601
15684
|
});
|
|
15602
|
-
const database = options.database ?? getDb();
|
|
15603
15685
|
const now = options.now ?? Date.now();
|
|
15604
15686
|
const expiresAt = now + input.ttlMs;
|
|
15605
15687
|
const requestJson = stableProviderRequestJson(key.request);
|
|
15606
15688
|
const responseJson = JSON.stringify(input.data);
|
|
15607
15689
|
const normalizedTaskIds = [...new Set(input.taskIds)].filter((id) => id.length > 0 && id.length <= 100).sort((left, right) => left < right ? -1 : left > right ? 1 : 0).slice(0, 20);
|
|
15608
15690
|
const taskIdsJson = JSON.stringify(normalizedTaskIds);
|
|
15609
|
-
|
|
15610
|
-
|
|
15611
|
-
|
|
15612
|
-
|
|
15613
|
-
|
|
15614
|
-
|
|
15691
|
+
const save = (database) => {
|
|
15692
|
+
database.prepare(`INSERT OR REPLACE INTO provider_cache (
|
|
15693
|
+
provider, credential_scope, operation, request_hash, request_json,
|
|
15694
|
+
response_json, row_count, source_cost_micros, task_ids_json,
|
|
15695
|
+
fetched_at, expires_at
|
|
15696
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(key.provider, key.credentialScope, key.operation, requestHash(key, requestJson), requestJson, responseJson, input.rowCount, input.sourceCostMicros, taskIdsJson, now, expiresAt);
|
|
15697
|
+
noteCacheWrite(Buffer.byteLength(requestJson) + Buffer.byteLength(responseJson) + Buffer.byteLength(taskIdsJson), database);
|
|
15698
|
+
};
|
|
15699
|
+
if (options.database) save(options.database);
|
|
15700
|
+
else writeToCache(save);
|
|
15615
15701
|
return {
|
|
15616
15702
|
data: input.data,
|
|
15617
15703
|
storedAt: new Date(now).toISOString(),
|
|
@@ -16251,7 +16337,7 @@ const installedPackageJsonSchema = z.object({
|
|
|
16251
16337
|
function packageDirectory(packagesDir, packageName) {
|
|
16252
16338
|
return join(packagesDir, "node_modules", ...packageName.split("/"));
|
|
16253
16339
|
}
|
|
16254
|
-
function inside(parent, child) {
|
|
16340
|
+
function inside$1(parent, child) {
|
|
16255
16341
|
const path = relative(parent, child);
|
|
16256
16342
|
return path === "" || !path.startsWith("..") && !isAbsolute(path);
|
|
16257
16343
|
}
|
|
@@ -16283,14 +16369,14 @@ async function loadOne(installed, registry, options) {
|
|
|
16283
16369
|
async function loadProviderPackage(input, options) {
|
|
16284
16370
|
const importModule = options.importModule ?? ((url) => import(url));
|
|
16285
16371
|
const packageDir = realpathSync(packageDirectory(options.packagesDir, input.package));
|
|
16286
|
-
if (!inside(realpathSync(options.packagesDir), packageDir)) throw new Error("Installed provider package resolves outside the provider directory.");
|
|
16372
|
+
if (!inside$1(realpathSync(options.packagesDir), packageDir)) throw new Error("Installed provider package resolves outside the provider directory.");
|
|
16287
16373
|
const packageJsonPath = join(packageDir, "package.json");
|
|
16288
16374
|
const packageJson = installedPackageJsonSchema.parse(JSON.parse(readFileSync(packageJsonPath, "utf8")));
|
|
16289
16375
|
if (packageJson.name !== input.package || packageJson.version !== input.version) throw new Error("Installed provider package does not match its saved record.");
|
|
16290
16376
|
const packageRegistry = new ProviderExtensionRegistry();
|
|
16291
16377
|
for (const entry of packageJson.seo.providers) {
|
|
16292
16378
|
const entryPath = realpathSync(resolve(dirname(packageJsonPath), entry));
|
|
16293
|
-
if (!inside(packageDir, entryPath)) throw new Error("Provider entry point resolves outside its package.");
|
|
16379
|
+
if (!inside$1(packageDir, entryPath)) throw new Error("Provider entry point resolves outside its package.");
|
|
16294
16380
|
await providerActivate((await importModule(`${pathToFileURL(entryPath).href}?seo-provider=${encodeURIComponent(input.version)}`)).default)(packageRegistry.hostFor({
|
|
16295
16381
|
package: input.package,
|
|
16296
16382
|
version: input.version
|
|
@@ -16326,7 +16412,7 @@ async function loadInstalledProviderExtensions(options = {}) {
|
|
|
16326
16412
|
const DEFAULT_MAX_REQUESTS = 8;
|
|
16327
16413
|
const MAX_RESPONSE_BYTES$1 = 10 * 1024 * 1024;
|
|
16328
16414
|
const MAX_REQUEST_BODY_BYTES = 64 * 1024;
|
|
16329
|
-
const TIMEOUT_MS$
|
|
16415
|
+
const TIMEOUT_MS$3 = 15e3;
|
|
16330
16416
|
function normalizeProviderExtensionAccount(provider, account) {
|
|
16331
16417
|
const fields = provider.connection.fields.filter((field) => field.kind === "account");
|
|
16332
16418
|
const allowed = new Set(fields.map((field) => field.id));
|
|
@@ -16373,7 +16459,7 @@ function runtimeForProvider(provider, options = {}) {
|
|
|
16373
16459
|
},
|
|
16374
16460
|
fetch: providerFetch,
|
|
16375
16461
|
maxResponseBytes: MAX_RESPONSE_BYTES$1,
|
|
16376
|
-
timeoutMs: TIMEOUT_MS$
|
|
16462
|
+
timeoutMs: TIMEOUT_MS$3,
|
|
16377
16463
|
retry: (input.method ?? "GET") === "GET" ? "safe" : "never",
|
|
16378
16464
|
schema: z.unknown()
|
|
16379
16465
|
});
|
|
@@ -17954,7 +18040,7 @@ function analyzeCtrUnderperformersFromRows(input) {
|
|
|
17954
18040
|
const MAX_GSC_ROWS$3 = 1e5;
|
|
17955
18041
|
const DEFAULT_DAYS$3 = 28;
|
|
17956
18042
|
const MAX_DAYS$2 = 548;
|
|
17957
|
-
function plural$
|
|
18043
|
+
function plural$6(count, singular, pluralLabel = `${singular}s`) {
|
|
17958
18044
|
return count === 1 ? singular : pluralLabel;
|
|
17959
18045
|
}
|
|
17960
18046
|
async function ctrUnderperformersReportWith(input, dependencies) {
|
|
@@ -18057,11 +18143,11 @@ async function ctrUnderperformersReportWith(input, dependencies) {
|
|
|
18057
18143
|
minImpressions,
|
|
18058
18144
|
limit,
|
|
18059
18145
|
brandFiltering: input.includeBrand ? "included" : "excluded",
|
|
18060
|
-
verdict: top ? `${selection.eligibleUnderperformers} CTR ${plural$
|
|
18146
|
+
verdict: top ? `${selection.eligibleUnderperformers} CTR ${plural$6(selection.eligibleUnderperformers, "underperformer")} found in retained rows, with a calculated heuristic shortfall of ${totalClickShortfall.toFixed(0)} clicks for this window. Start review with "${top.query}" because it has the largest gap.` : dataStatus === "partial" ? "No material CTR underperformers remained in the validated retained rows, but partial evidence prevents an all-clear." : `No high-impression rows at average position below 4 in the retained rows are materially below the heuristic CTR benchmark.`
|
|
18061
18147
|
},
|
|
18062
18148
|
items,
|
|
18063
18149
|
caveats: [
|
|
18064
|
-
`Date window: ${range.startDate} to ${range.endDate} (${days} ${plural$
|
|
18150
|
+
`Date window: ${range.startDate} to ${range.endDate} (${days} ${plural$6(days, "day")}), using final GSC data where available.`,
|
|
18065
18151
|
`Brand queries: ${input.includeBrand ? "included" : "excluded"}. Brand terms used: ${brand.terms.join(", ") || "none"} (${brand.source === "provided" ? "provided" : "host forms, plus the bare domain stem only when Search Console shows the site is its navigational answer"}).`,
|
|
18066
18152
|
`Only rows at average position below 4 with at least ${minImpressions} impressions were checked. Rows at position 4 to 10 belong to quick-wins, which uses the same site CTR curve.`,
|
|
18067
18153
|
minImpressionsCaveat({
|
|
@@ -18069,13 +18155,13 @@ async function ctrUnderperformersReportWith(input, dependencies) {
|
|
|
18069
18155
|
belowMinimumRows: selection.belowMinimumRows
|
|
18070
18156
|
}),
|
|
18071
18157
|
"Expected CTR uses the site CTR curve from all page-one rows (75th percentile of qualified URL CTRs per position) when enough peer data exists, otherwise the fallback position curve.",
|
|
18072
|
-
...selection.fragmentRows ? [`${selection.fragmentRows} source ${plural$
|
|
18073
|
-
...selection.flaggedRows ? [`${selection.flaggedRows} eligible ${plural$
|
|
18158
|
+
...selection.fragmentRows ? [`${selection.fragmentRows} source ${plural$6(selection.fragmentRows, "row")} with a #fragment URL ${selection.fragmentRows === 1 ? "was" : "were"} merged into the base URL.`] : [],
|
|
18159
|
+
...selection.flaggedRows ? [`${selection.flaggedRows} eligible ${plural$6(selection.flaggedRows, "row")} carry heuristic query flags (likely-automated or navigational-other) and rank after unflagged rows.`] : [],
|
|
18074
18160
|
"CTR benchmarks and calculated click shortfalls are directional heuristics, not forecasts or proof that clicks are available. Validate the search intent and live SERP before editing.",
|
|
18075
|
-
...selection.invalidRows ? [`${selection.invalidRows} invalid provider ${plural$
|
|
18076
|
-
...selection.duplicateRows ? [`${selection.duplicateRows} repeated query/page ${plural$
|
|
18161
|
+
...selection.invalidRows ? [`${selection.invalidRows} invalid provider ${plural$6(selection.invalidRows, "row")} ${selection.invalidRows === 1 ? "was" : "were"} excluded, so this report is partial.`] : [],
|
|
18162
|
+
...selection.duplicateRows ? [`${selection.duplicateRows} repeated query/page ${plural$6(selection.duplicateRows, "row")} ${selection.duplicateRows === 1 ? "was" : "were"} aggregated before CTR and average position were calculated.`] : [],
|
|
18077
18163
|
...possiblyTruncated ? [`The retained-row limit of ${MAX_GSC_ROWS$3} was reached, so lower-ranked source rows may be absent.`] : [],
|
|
18078
|
-
...selection.limitedUnderperformers ? [`${selection.limitedUnderperformers} eligible ${plural$
|
|
18164
|
+
...selection.limitedUnderperformers ? [`${selection.limitedUnderperformers} eligible ${plural$6(selection.limitedUnderperformers, "underperformer")} ${selection.limitedUnderperformers === 1 ? "was" : "were"} omitted by the output limit.`] : []
|
|
18079
18165
|
],
|
|
18080
18166
|
recommendations: top ? [
|
|
18081
18167
|
`Review the live SERP and displayed snippet for "${top.query}" first. Test the title or meta description only if the observed framing does not match the query intent.`,
|
|
@@ -21910,7 +21996,7 @@ function anomalyEvidence(anomaly) {
|
|
|
21910
21996
|
if (anomaly.direction === "normal") return `${formatMetric(anomaly.metric)} changed ${changePhrase} (${anomaly.baselineMean.toLocaleString("en-GB")} -> ${anomaly.comparisonMean.toLocaleString("en-GB")} per observed day${anomaly.zScore === null ? "; the baseline had no observed variance" : `, z ${anomaly.zScore}`}).`;
|
|
21911
21997
|
return `${formatMetric(anomaly.metric)} ${direction} ${changePhrase} (${anomaly.baselineMean.toLocaleString("en-GB")} -> ${anomaly.comparisonMean.toLocaleString("en-GB")} per observed day${anomaly.zScore === null ? "; outside the flat observed baseline" : `, z ${anomaly.zScore}`}).`;
|
|
21912
21998
|
}
|
|
21913
|
-
function plural$
|
|
21999
|
+
function plural$5(count, singular, pluralLabel = `${singular}s`) {
|
|
21914
22000
|
return count === 1 ? singular : pluralLabel;
|
|
21915
22001
|
}
|
|
21916
22002
|
function strongestSignal(anomalies) {
|
|
@@ -21966,13 +22052,13 @@ function interpretUpdateCorrelation(input) {
|
|
|
21966
22052
|
summary,
|
|
21967
22053
|
evidence: [
|
|
21968
22054
|
...input.anomalies.map(anomalyEvidence),
|
|
21969
|
-
hasUpdate ? `${input.overlappingUpdates.length} official Google Ranking update ${plural$
|
|
22055
|
+
hasUpdate ? `${input.overlappingUpdates.length} official Google Ranking update ${plural$5(input.overlappingUpdates.length, "window")} overlapped the change date or comparison window: ${updateNames.join(", ")}.` : "No official Google Ranking update windows overlapped the change date or comparison window.",
|
|
21970
22056
|
largeMovement ? "Both clicks and impressions moved sharply in the same direction." : "Clicks and impressions did not both meet the sharp-movement evidence threshold.",
|
|
21971
22057
|
hasConfounders ? `Known overlapping site changes: ${confounders.map((confounder) => confounder.title).join("; ")}.` : "No overlapping site changes were supplied."
|
|
21972
22058
|
],
|
|
21973
22059
|
caveats: [
|
|
21974
22060
|
`GSC window: ${input.days} days; recent comparison window: ${input.recentDays} days.`,
|
|
21975
|
-
`Update overlap padding: ${input.paddingDays} ${plural$
|
|
22061
|
+
`Update overlap padding: ${input.paddingDays} ${plural$5(input.paddingDays, "day")} either side of the step-change date, or of the recent comparison window when no step change was found.`,
|
|
21976
22062
|
input.refresh ? "Data freshness: cache bypassed for GSC data." : "Data freshness: local GSC cache may have been used.",
|
|
21977
22063
|
"Source: official Google Search Status Dashboard Ranking incidents only; third-party volatility and unconfirmed chatter are not included.",
|
|
21978
22064
|
"An update-window overlap cannot establish causation, even when movement is large and no known site change was recorded.",
|
|
@@ -22140,7 +22226,7 @@ function analyzeTrafficAnomalyRows(input) {
|
|
|
22140
22226
|
if (step && changeDate) {
|
|
22141
22227
|
const previousDate = aggregated.rows[step.index - 1]?.keys[0];
|
|
22142
22228
|
const gap = previousDate ? calendarDays(previousDate, changeDate) - 2 : 0;
|
|
22143
|
-
stepCaveats.push(`${formatMetric(metric)} changed level on ${changeDate}, so its baseline is every observed day before that date and its comparison is every observed day from it.`, ...gap > 0 ? [`The ${metric} change date follows ${gap} missing ${plural$
|
|
22229
|
+
stepCaveats.push(`${formatMetric(metric)} changed level on ${changeDate}, so its baseline is every observed day before that date and its comparison is every observed day from it.`, ...gap > 0 ? [`The ${metric} change date follows ${gap} missing ${plural$5(gap, "day")}; the change may have started on one of them.`] : []);
|
|
22144
22230
|
return compareSegments({
|
|
22145
22231
|
site: input.site,
|
|
22146
22232
|
metric,
|
|
@@ -22171,9 +22257,9 @@ function analyzeTrafficAnomalyRows(input) {
|
|
|
22171
22257
|
const comparisonMissingDays = input.recentDays - comparisonRows.length;
|
|
22172
22258
|
const missingDays = expectedDays - aggregated.rows.length;
|
|
22173
22259
|
const caveats = [
|
|
22174
|
-
...missingDays > 0 ? [`${missingDays} requested calendar ${plural$
|
|
22175
|
-
...aggregated.invalidRows > 0 ? [`${aggregated.invalidRows} invalid provider ${plural$
|
|
22176
|
-
...aggregated.duplicateRows > 0 ? [`${aggregated.duplicateRows} duplicate date ${plural$
|
|
22260
|
+
...missingDays > 0 ? [`${missingDays} requested calendar ${plural$5(missingDays, "day")} had no returned date aggregate and ${missingDays === 1 ? "was" : "were"} not filled with zeros.`] : [],
|
|
22261
|
+
...aggregated.invalidRows > 0 ? [`${aggregated.invalidRows} invalid provider ${plural$5(aggregated.invalidRows, "row")} ${aggregated.invalidRows === 1 ? "was" : "were"} excluded.`] : [],
|
|
22262
|
+
...aggregated.duplicateRows > 0 ? [`${aggregated.duplicateRows} duplicate date ${plural$5(aggregated.duplicateRows, "row")} ${aggregated.duplicateRows === 1 ? "was" : "were"} aggregated before analysis.`] : [],
|
|
22177
22263
|
...stepCaveats
|
|
22178
22264
|
];
|
|
22179
22265
|
return {
|
|
@@ -23254,7 +23340,7 @@ const VOID = /* @__PURE__ */ new Set([
|
|
|
23254
23340
|
"col",
|
|
23255
23341
|
"embed"
|
|
23256
23342
|
]);
|
|
23257
|
-
const ENTITIES = {
|
|
23343
|
+
const ENTITIES$1 = {
|
|
23258
23344
|
amp: "&",
|
|
23259
23345
|
lt: "<",
|
|
23260
23346
|
gt: ">",
|
|
@@ -23279,7 +23365,7 @@ function decode(text) {
|
|
|
23279
23365
|
const point = Number.parseInt(code.slice(hex ? 2 : 1), hex ? 16 : 10);
|
|
23280
23366
|
return point > 0 && point <= 1114111 ? String.fromCodePoint(point) : "";
|
|
23281
23367
|
}
|
|
23282
|
-
return ENTITIES[code.toLowerCase()] ?? match;
|
|
23368
|
+
return ENTITIES$1[code.toLowerCase()] ?? match;
|
|
23283
23369
|
});
|
|
23284
23370
|
}
|
|
23285
23371
|
const clean$1 = (value) => value.split(A_OPEN).join("").replace(/\s+/g, " ").trim();
|
|
@@ -23520,7 +23606,7 @@ var SentenceStore = class SentenceStore {
|
|
|
23520
23606
|
});
|
|
23521
23607
|
}
|
|
23522
23608
|
};
|
|
23523
|
-
const bareHost = (host) => host.toLowerCase().replace(/^www\./, "");
|
|
23609
|
+
const bareHost$1 = (host) => host.toLowerCase().replace(/^www\./, "");
|
|
23524
23610
|
function crawlUrl(value, base) {
|
|
23525
23611
|
try {
|
|
23526
23612
|
const url = base ? new URL(value, base) : new URL(value);
|
|
@@ -23586,10 +23672,10 @@ async function crawlForLinks(input) {
|
|
|
23586
23672
|
async function crawl(input, robots, sentences, renderer, warnings) {
|
|
23587
23673
|
const start = crawlUrl(input.startUrl) ?? input.startUrl;
|
|
23588
23674
|
let origin = new URL(start).origin;
|
|
23589
|
-
const site = bareHost(new URL(start).host);
|
|
23675
|
+
const site = bareHost$1(new URL(start).host);
|
|
23590
23676
|
const sameSite = (url) => {
|
|
23591
23677
|
try {
|
|
23592
|
-
return bareHost(new URL(url).host) === site;
|
|
23678
|
+
return bareHost$1(new URL(url).host) === site;
|
|
23593
23679
|
} catch {
|
|
23594
23680
|
return false;
|
|
23595
23681
|
}
|
|
@@ -24083,15 +24169,15 @@ function internalLinksReportRange(input, fallbackDays, now) {
|
|
|
24083
24169
|
days: Math.round((Date.parse(`${endDate}T00:00:00Z`) - Date.parse(`${startDate}T00:00:00Z`)) / DAY$1) + 1
|
|
24084
24170
|
};
|
|
24085
24171
|
}
|
|
24086
|
-
const plural$
|
|
24172
|
+
const plural$4 = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
24087
24173
|
function internalLinksVerdict(report) {
|
|
24088
24174
|
const { counts } = report;
|
|
24089
24175
|
if (report.dataStatus === "blocked") return "robots.txt does not allow this crawler to read the site, so nothing was checked.";
|
|
24090
24176
|
if (report.dataStatus === "empty") return "No HTML page could be crawled, so there is nothing to link.";
|
|
24091
24177
|
if (report.dataStatus === "target-not-crawled") return "The target page could not be crawled as its own page (it failed, left the site, or names another page as canonical). Check it with seo page first.";
|
|
24092
24178
|
if (report.dataStatus === "target-not-linkable") return `The target is ${report.target?.noindex ? "noindex" : `a ${report.target?.type?.replaceAll("_", " ")} page`}, so no links to it are suggested.`;
|
|
24093
|
-
const judged = report.judge.status === "used" || report.judge.status === "partial" ? `${counts.strong} strong and ${counts.possible} possible link${counts.strong + counts.possible === 1 ? "" : "s"} judged by ${report.judge.model}${counts.heuristic ? `, plus ${plural$
|
|
24094
|
-
const scope = report.mode === "url" ? `to ${report.target?.url}` : `across ${plural$
|
|
24179
|
+
const judged = report.judge.status === "used" || report.judge.status === "partial" ? `${counts.strong} strong and ${counts.possible} possible link${counts.strong + counts.possible === 1 ? "" : "s"} judged by ${report.judge.model}${counts.heuristic ? `, plus ${plural$4(counts.heuristic, "heuristic link")} where a request failed` : ""}` : `${plural$4(counts.heuristic, "heuristic link")} (no decision model)`;
|
|
24180
|
+
const scope = report.mode === "url" ? `to ${report.target?.url}` : `across ${plural$4(counts.pagesCrawled, "page")}`;
|
|
24095
24181
|
const capped = report.crawl.capReached ? ` The crawl stopped at its limit of ${report.crawl.pageLimit} pages, so pages beyond it were not checked.` : "";
|
|
24096
24182
|
return `${judged} ${scope}${counts.heldBack ? `; ${counts.heldBack} held back by the evidence floors` : ""}.${capped}`;
|
|
24097
24183
|
}
|
|
@@ -25992,7 +26078,7 @@ function reportId(input) {
|
|
|
25992
26078
|
})).digest("hex").slice(0, 20)}`;
|
|
25993
26079
|
}
|
|
25994
26080
|
function cacheGet(id, now) {
|
|
25995
|
-
const row =
|
|
26081
|
+
const row = cacheDb().prepare("SELECT report_json FROM performance_reports WHERE id = ? AND expires_at > ?").get(id, now);
|
|
25996
26082
|
if (!row?.report_json) return void 0;
|
|
25997
26083
|
try {
|
|
25998
26084
|
const report = JSON.parse(row.report_json);
|
|
@@ -26007,10 +26093,12 @@ function cacheSet(report, now) {
|
|
|
26007
26093
|
raw: void 0
|
|
26008
26094
|
};
|
|
26009
26095
|
const reportJson = JSON.stringify(stored);
|
|
26010
|
-
|
|
26011
|
-
|
|
26012
|
-
|
|
26013
|
-
|
|
26096
|
+
writeToCache((database) => {
|
|
26097
|
+
database.prepare(`INSERT OR REPLACE INTO performance_reports
|
|
26098
|
+
(id, url, strategy, report_json, created_at, expires_at)
|
|
26099
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(report.id, report.url, report.strategy, reportJson, Date.parse(report.generatedAt), now + CACHE_TTL_MS);
|
|
26100
|
+
noteCacheWrite(Buffer.byteLength(reportJson), database);
|
|
26101
|
+
});
|
|
26014
26102
|
}
|
|
26015
26103
|
async function fallbackReport(input) {
|
|
26016
26104
|
const fetched = await fetchPage(input.url, {
|
|
@@ -26530,14 +26618,14 @@ function* retainedQueryClusterRows(input) {
|
|
|
26530
26618
|
};
|
|
26531
26619
|
}
|
|
26532
26620
|
}
|
|
26533
|
-
function plural$
|
|
26621
|
+
function plural$3(count, singular, pluralLabel = `${singular}s`) {
|
|
26534
26622
|
return count === 1 ? singular : pluralLabel;
|
|
26535
26623
|
}
|
|
26536
26624
|
function reportVerdict(input) {
|
|
26537
26625
|
if (!input.clusters.length) return "No query clusters were generated for this scope.";
|
|
26538
26626
|
if (input.highOpportunityClusters > 0) {
|
|
26539
26627
|
const verb = input.highOpportunityClusters === 1 ? "has" : "have";
|
|
26540
|
-
return `${input.highOpportunityClusters} ${plural$
|
|
26628
|
+
return `${input.highOpportunityClusters} ${plural$3(input.highOpportunityClusters, "cluster")} ${verb} enough demand and weak enough performance to review first.`;
|
|
26541
26629
|
}
|
|
26542
26630
|
return "No obvious high-opportunity clusters were found. Use this report for content structure and intent mapping rather than urgent fixes.";
|
|
26543
26631
|
}
|
|
@@ -26678,7 +26766,7 @@ function movementLine(report) {
|
|
|
26678
26766
|
}
|
|
26679
26767
|
function countPhrase(count, singular, pluralLabel = `${singular}s`) {
|
|
26680
26768
|
if (count === 0) return `no ${pluralLabel}`;
|
|
26681
|
-
return `${count} ${plural$
|
|
26769
|
+
return `${count} ${plural$2(count, singular, pluralLabel)}`;
|
|
26682
26770
|
}
|
|
26683
26771
|
function headlineLine(report) {
|
|
26684
26772
|
const parts = [
|
|
@@ -26733,7 +26821,7 @@ function decayDiagnosisLabel(diagnosis) {
|
|
|
26733
26821
|
if (diagnosis === "lost_clicks") return "clicks dropped without one dominant signal";
|
|
26734
26822
|
return diagnosis.replaceAll("_", " ");
|
|
26735
26823
|
}
|
|
26736
|
-
function plural$
|
|
26824
|
+
function plural$2(count, singular, pluralLabel = `${singular}s`) {
|
|
26737
26825
|
return count === 1 ? singular : pluralLabel;
|
|
26738
26826
|
}
|
|
26739
26827
|
function decayClusterLine(report) {
|
|
@@ -26741,7 +26829,7 @@ function decayClusterLine(report) {
|
|
|
26741
26829
|
if (!group) return "No material decay cluster stood out.";
|
|
26742
26830
|
const diagnosis = decayDiagnosisLabel(group.diagnosis);
|
|
26743
26831
|
const samples = group.sampleQueries.slice(0, 2).join("; ");
|
|
26744
|
-
return `Top decay cluster: ${group.count} ${plural$
|
|
26832
|
+
return `Top decay cluster: ${group.count} ${plural$2(group.count, "finding")} in the ${group.template.label} template (${diagnosis}), ${group.totalClickLoss.toFixed(0)} fewer observed clicks across rows retained in both windows. Action: ${group.recommendation}${samples ? ` Example queries: ${samples}.` : ""}`;
|
|
26745
26833
|
}
|
|
26746
26834
|
function cannibalSuppressionLine(report) {
|
|
26747
26835
|
const suppressed = report.cannibalization.selection.suppressedQueries;
|
|
@@ -26759,21 +26847,21 @@ function contentOpportunityBullets(report) {
|
|
|
26759
26847
|
if (unavailableSections.length) bullets.push(`Could not assess ${unavailableSections.join(", ")} opportunities in this run; see report caveats.`);
|
|
26760
26848
|
if (report.summary.decayItems) {
|
|
26761
26849
|
const top = report.decay.items[0];
|
|
26762
|
-
bullets.push(top ? `${report.summary.decayItems} observed retained query/page ${plural$
|
|
26850
|
+
bullets.push(top ? `${report.summary.decayItems} observed retained query/page ${plural$2(report.summary.decayItems, "decline")} need review. Start with "${top.query}" on ${top.url}; it had ${top.clickLoss.toFixed(0)} fewer clicks.` : `${report.summary.decayItems} observed retained query/page ${plural$2(report.summary.decayItems, "decline")} need review.`);
|
|
26763
26851
|
bullets.push(decayClusterLine(report));
|
|
26764
26852
|
}
|
|
26765
26853
|
if (report.summary.cannibalItems) {
|
|
26766
26854
|
const top = report.cannibalization.items[0];
|
|
26767
|
-
bullets.push(top ? `${report.summary.cannibalItems} multi-URL query ${plural$
|
|
26855
|
+
bullets.push(top ? `${report.summary.cannibalItems} multi-URL query ${plural$2(report.summary.cannibalItems, "candidate")} need intent and technical review. Start with "${top.query}" and decide whether ${top.pages.length} ranking URLs answer the same intent.` : `${report.summary.cannibalItems} multi-URL query ${plural$2(report.summary.cannibalItems, "candidate")} need intent and technical review.`);
|
|
26768
26856
|
}
|
|
26769
26857
|
if (report.cannibalization.selection.suppressedQueries) bullets.push(cannibalSuppressionLine(report));
|
|
26770
26858
|
if (report.summary.strikingDistanceItems) {
|
|
26771
26859
|
const top = report.strikingDistance.items[0];
|
|
26772
|
-
bullets.push(top ? `${countLabel(report.summary.strikingDistanceItems, "query/page candidate")} ${plural$
|
|
26860
|
+
bullets.push(top ? `${countLabel(report.summary.strikingDistanceItems, "query/page candidate")} ${plural$2(report.summary.strikingDistanceItems, "has", "have")} an average GSC position above 10 and at most 20. Start by investigating "${top.query}" on ${top.url}; it averages position ${top.position.toFixed(1)} with ${top.impressions.toFixed(0)} impressions.` : `${countLabel(report.summary.strikingDistanceItems, "query/page candidate")} ${plural$2(report.summary.strikingDistanceItems, "has", "have")} an average GSC position above 10 and at most 20.`);
|
|
26773
26861
|
}
|
|
26774
26862
|
if (!skippedReason(report, "quick-win opportunities") && report.summary.quickWinItems) {
|
|
26775
26863
|
const top = report.quickWins.items[0];
|
|
26776
|
-
bullets.push(top ? `${report.summary.quickWinItems} retained query/page ${plural$
|
|
26864
|
+
bullets.push(top ? `${report.summary.quickWinItems} retained query/page ${plural$2(report.summary.quickWinItems, "candidate")} fell below a site-peer or versioned heuristic CTR target. Start with "${top.query}" on ${top.url}; it had ${top.impressions.toFixed(0)} impressions at average position ${top.position.toFixed(1)}.` : `${report.summary.quickWinItems} CTR-target ${plural$2(report.summary.quickWinItems, "candidate")} need review.`);
|
|
26777
26865
|
}
|
|
26778
26866
|
const templateLine = templateOpportunityLine(report);
|
|
26779
26867
|
if (!templateLine.startsWith("No dominant")) bullets.push(templateLine);
|
|
@@ -28495,11 +28583,12 @@ function ga4MatchReason(match, site) {
|
|
|
28495
28583
|
//#endregion
|
|
28496
28584
|
//#region packages/core/src/commands/clicky.ts
|
|
28497
28585
|
const CLICKY_KEY_PREFIX = "x-clicky-";
|
|
28586
|
+
const MAX_INSPECT_LIMIT = 2e3;
|
|
28498
28587
|
//#endregion
|
|
28499
28588
|
//#region packages/core/src/commands/page-facts.ts
|
|
28500
28589
|
const MAX_PAGES = 20;
|
|
28501
|
-
const TIMEOUT_MS = 6e3;
|
|
28502
|
-
const MAX_BYTES = 1e6;
|
|
28590
|
+
const TIMEOUT_MS$2 = 6e3;
|
|
28591
|
+
const MAX_BYTES$2 = 1e6;
|
|
28503
28592
|
const clean = (value) => value?.trim().replace(/\s+/g, " ") || null;
|
|
28504
28593
|
async function boundedText(response) {
|
|
28505
28594
|
if (!response.body) return "";
|
|
@@ -28507,7 +28596,7 @@ async function boundedText(response) {
|
|
|
28507
28596
|
const decoder = new TextDecoder();
|
|
28508
28597
|
let text = "";
|
|
28509
28598
|
let total = 0;
|
|
28510
|
-
while (total < MAX_BYTES) {
|
|
28599
|
+
while (total < MAX_BYTES$2) {
|
|
28511
28600
|
const { done, value } = await reader.read();
|
|
28512
28601
|
if (done) return text + decoder.decode();
|
|
28513
28602
|
total += value.byteLength;
|
|
@@ -28587,12 +28676,14 @@ async function facts(url) {
|
|
|
28587
28676
|
"user-agent": SEO_CRAWLER_USER_AGENT,
|
|
28588
28677
|
accept: "text/html"
|
|
28589
28678
|
},
|
|
28590
|
-
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
28679
|
+
signal: AbortSignal.timeout(TIMEOUT_MS$2)
|
|
28591
28680
|
});
|
|
28592
28681
|
out.status = response.status;
|
|
28593
28682
|
const location = response.headers.get("location");
|
|
28594
28683
|
if (location) out.redirects_to = new URL(location, url).toString();
|
|
28595
28684
|
const headerRobots = response.headers.get("x-robots-tag");
|
|
28685
|
+
const auth = response.headers.get("www-authenticate");
|
|
28686
|
+
if (auth) out.www_authenticate = auth.slice(0, 200);
|
|
28596
28687
|
if (response.status !== 200 || !response.headers.get("content-type")?.includes("html")) {
|
|
28597
28688
|
out.robots = headerRobots;
|
|
28598
28689
|
await response.body?.cancel();
|
|
@@ -28609,6 +28700,37 @@ async function facts(url) {
|
|
|
28609
28700
|
function checkPages(urls) {
|
|
28610
28701
|
return Promise.all(urls.slice(0, MAX_PAGES).map(facts));
|
|
28611
28702
|
}
|
|
28703
|
+
const MAX_HOPS = 5;
|
|
28704
|
+
const redirectKind = (status) => status === 301 || status === 308 ? "permanent" : status === 302 || status === 303 || status === 307 ? "temporary" : null;
|
|
28705
|
+
async function liveCheck(url) {
|
|
28706
|
+
const first = await facts(url);
|
|
28707
|
+
const hops = [];
|
|
28708
|
+
const seen = /* @__PURE__ */ new Set([url]);
|
|
28709
|
+
let last = first;
|
|
28710
|
+
while (last.redirects_to && last.status !== null) {
|
|
28711
|
+
const kind = redirectKind(last.status);
|
|
28712
|
+
if (!kind) break;
|
|
28713
|
+
const to = last.redirects_to;
|
|
28714
|
+
hops.push({
|
|
28715
|
+
from: last.url,
|
|
28716
|
+
status: last.status,
|
|
28717
|
+
to,
|
|
28718
|
+
kind
|
|
28719
|
+
});
|
|
28720
|
+
if (seen.has(to) || hops.length > MAX_HOPS) return {
|
|
28721
|
+
first,
|
|
28722
|
+
hops,
|
|
28723
|
+
last: null
|
|
28724
|
+
};
|
|
28725
|
+
seen.add(to);
|
|
28726
|
+
last = await facts(to);
|
|
28727
|
+
}
|
|
28728
|
+
return {
|
|
28729
|
+
first,
|
|
28730
|
+
hops,
|
|
28731
|
+
last
|
|
28732
|
+
};
|
|
28733
|
+
}
|
|
28612
28734
|
//#endregion
|
|
28613
28735
|
//#region packages/core/src/commands/report.ts
|
|
28614
28736
|
const SECTIONS = {
|
|
@@ -28641,16 +28763,23 @@ const RESEARCH_HELP = `keyword_metrics (keywords: volume, difficulty, CPC), rank
|
|
|
28641
28763
|
function indexStatusFrom(result) {
|
|
28642
28764
|
const status = result?.inspectionResult?.indexStatusResult ?? result?.indexStatusResult ?? {};
|
|
28643
28765
|
const text = (value) => typeof value === "string" ? value : null;
|
|
28766
|
+
const list = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
28644
28767
|
return {
|
|
28645
28768
|
verdict: text(status.verdict),
|
|
28646
28769
|
coverage: text(status.coverageState),
|
|
28647
28770
|
indexing: text(status.indexingState),
|
|
28648
28771
|
last_crawl: text(status.lastCrawlTime),
|
|
28649
28772
|
google_canonical: text(status.googleCanonical),
|
|
28650
|
-
user_canonical: text(status.userCanonical)
|
|
28773
|
+
user_canonical: text(status.userCanonical),
|
|
28774
|
+
page_fetch: text(status.pageFetchState),
|
|
28775
|
+
robots_txt: text(status.robotsTxtState),
|
|
28776
|
+
crawled_as: text(status.crawledAs),
|
|
28777
|
+
referring_urls: list(status.referringUrls),
|
|
28778
|
+
sitemaps: list(status.sitemap)
|
|
28651
28779
|
};
|
|
28652
28780
|
}
|
|
28653
28781
|
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.`;
|
|
28782
|
+
const INDEXING_RULE = `Google’s indexing reasons for 100 known URLs a run in a fixed order (lost-click pages, Search Console pages by clicks then impressions, sitemap URLs, then the rest), each checked live and labelled fix, check, expected or indexed from the evidence, sorted by reason then URL.`;
|
|
28654
28783
|
//#endregion
|
|
28655
28784
|
//#region packages/core/src/commands/tools.ts
|
|
28656
28785
|
const site = z.string().min(1).describe("Search Console property or site, such as sc-domain:example.com, https://example.com/ or example.com.");
|
|
@@ -28685,9 +28814,15 @@ z.object({}), `${Object.entries(SECTIONS).map(([id, text]) => `- ${id}: ${text}`
|
|
|
28685
28814
|
site,
|
|
28686
28815
|
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("YYYY-MM-DD: the first day after the change."),
|
|
28687
28816
|
pages: z.string().min(1).optional().describe("Optional URL text, such as /blog/.")
|
|
28688
|
-
}), `${LOST_PAGES_RULE}`, z.object({
|
|
28817
|
+
}), `${INDEXING_RULE}${LOST_PAGES_RULE}`, z.object({
|
|
28689
28818
|
site,
|
|
28690
|
-
...dates
|
|
28819
|
+
...dates,
|
|
28820
|
+
...detail
|
|
28821
|
+
}), z.object({
|
|
28822
|
+
site,
|
|
28823
|
+
...dates,
|
|
28824
|
+
max_urls: z.number().int().min(1).max(MAX_INSPECT_LIMIT).optional().describe(`URLs to inspect with Google, 100 by default, up to the URL Inspection quota left today.`),
|
|
28825
|
+
...detail
|
|
28691
28826
|
}), `${RESEARCH_HELP}`, z.object({
|
|
28692
28827
|
provider: z.enum(RESEARCH_PROVIDERS),
|
|
28693
28828
|
action: z.enum(RESEARCH_ACTIONS),
|
|
@@ -28705,6 +28840,36 @@ z.object({}), `${Object.entries(SECTIONS).map(([id, text]) => `- ${id}: ${text}`
|
|
|
28705
28840
|
...detail
|
|
28706
28841
|
});
|
|
28707
28842
|
//#endregion
|
|
28843
|
+
//#region packages/core/src/gsc/client/inspection-cache.ts
|
|
28844
|
+
const INSPECTION_FRESH_MS = 7 * 864e5;
|
|
28845
|
+
function readSavedInspections(site, urls, now = Date.now()) {
|
|
28846
|
+
const found = /* @__PURE__ */ new Map();
|
|
28847
|
+
try {
|
|
28848
|
+
const statement = cacheDb().prepare(`SELECT status_json, fetched_at FROM url_inspection_cache
|
|
28849
|
+
WHERE site_url = ? AND url = ? AND fetched_at > ?`);
|
|
28850
|
+
for (const url of urls) {
|
|
28851
|
+
const row = statement.get(site, url, now - INSPECTION_FRESH_MS);
|
|
28852
|
+
if (!row) continue;
|
|
28853
|
+
found.set(url, {
|
|
28854
|
+
status: JSON.parse(row.status_json),
|
|
28855
|
+
inspectedAt: new Date(row.fetched_at).toISOString()
|
|
28856
|
+
});
|
|
28857
|
+
}
|
|
28858
|
+
} catch {
|
|
28859
|
+
return /* @__PURE__ */ new Map();
|
|
28860
|
+
}
|
|
28861
|
+
return found;
|
|
28862
|
+
}
|
|
28863
|
+
function saveInspection(site, url, status, now = Date.now()) {
|
|
28864
|
+
const json = JSON.stringify(status);
|
|
28865
|
+
writeToCache((database) => {
|
|
28866
|
+
database.prepare(`INSERT OR REPLACE INTO url_inspection_cache
|
|
28867
|
+
(site_url, url, status_json, fetched_at, expires_at)
|
|
28868
|
+
VALUES (?, ?, ?, ?, ?)`).run(site, url, json, now, now + INSPECTION_FRESH_MS);
|
|
28869
|
+
noteCacheWrite(Buffer.byteLength(json) + url.length, database);
|
|
28870
|
+
});
|
|
28871
|
+
}
|
|
28872
|
+
//#endregion
|
|
28708
28873
|
//#region packages/core/src/providers/api-key-credentials.ts
|
|
28709
28874
|
function createApiKeyCredentialStore(input) {
|
|
28710
28875
|
const normalize = (value) => {
|
|
@@ -28900,16 +29065,19 @@ function nodeCommandDeps(options) {
|
|
|
28900
29065
|
accountEmail: options.account
|
|
28901
29066
|
}),
|
|
28902
29067
|
checkPages,
|
|
28903
|
-
|
|
28904
|
-
|
|
28905
|
-
|
|
28906
|
-
|
|
28907
|
-
|
|
28908
|
-
|
|
28909
|
-
|
|
28910
|
-
|
|
28911
|
-
|
|
28912
|
-
|
|
29068
|
+
liveCheck,
|
|
29069
|
+
inspect: async (site, url) => indexStatusFrom(await inspectUrl({
|
|
29070
|
+
siteUrl: site,
|
|
29071
|
+
inspectionUrl: url,
|
|
29072
|
+
accountEmail: options.account
|
|
29073
|
+
})),
|
|
29074
|
+
inspectionQuota: (site) => urlInspectionQuota({
|
|
29075
|
+
siteUrl: site,
|
|
29076
|
+
accountEmail: options.account
|
|
29077
|
+
}),
|
|
29078
|
+
savedInspections: {
|
|
29079
|
+
read: (site, urls) => options.refresh ? /* @__PURE__ */ new Map() : readSavedInspections(site, urls),
|
|
29080
|
+
save: saveInspection
|
|
28913
29081
|
},
|
|
28914
29082
|
pageMarkdown: async (url) => extractMainMarkdown(await fetchPage(url, { refresh: options.refresh })),
|
|
28915
29083
|
call: options.call,
|
|
@@ -30564,6 +30732,94 @@ async function resetSeoData() {
|
|
|
30564
30732
|
});
|
|
30565
30733
|
}
|
|
30566
30734
|
//#endregion
|
|
30735
|
+
//#region packages/core/src/update-notice.ts
|
|
30736
|
+
const REGISTRY_URL = "https://registry.npmjs.org/seo/latest";
|
|
30737
|
+
const CHECK_EVERY_MS = 864e5;
|
|
30738
|
+
const TIMEOUT_MS = 2e3;
|
|
30739
|
+
const MAX_BYTES = 65536;
|
|
30740
|
+
let checking = false;
|
|
30741
|
+
function runByNpx(env, cliPath) {
|
|
30742
|
+
if (env.npm_command === "exec" || /npx/i.test(env.npm_execpath ?? "")) return true;
|
|
30743
|
+
if (!cliPath) return false;
|
|
30744
|
+
let path = cliPath;
|
|
30745
|
+
try {
|
|
30746
|
+
path = realpathSync(cliPath);
|
|
30747
|
+
} catch {}
|
|
30748
|
+
return /[\\/](?:_npx|dlx)[\\/]/.test(path);
|
|
30749
|
+
}
|
|
30750
|
+
function isNewerVersion(current, latest) {
|
|
30751
|
+
const parse = (value) => value.replace(/^v/, "").split("-")[0]?.split(".").map((part) => Number.parseInt(part, 10)) ?? [];
|
|
30752
|
+
const currentParts = parse(current);
|
|
30753
|
+
const latestParts = parse(latest);
|
|
30754
|
+
for (let index = 0; index < 3; index += 1) {
|
|
30755
|
+
const currentPart = currentParts[index] ?? 0;
|
|
30756
|
+
const latestPart = latestParts[index] ?? 0;
|
|
30757
|
+
if (!Number.isFinite(currentPart) || !Number.isFinite(latestPart)) return false;
|
|
30758
|
+
if (latestPart > currentPart) return true;
|
|
30759
|
+
if (latestPart < currentPart) return false;
|
|
30760
|
+
}
|
|
30761
|
+
return false;
|
|
30762
|
+
}
|
|
30763
|
+
function isLatestCache(value) {
|
|
30764
|
+
if (!value || typeof value !== "object") return false;
|
|
30765
|
+
const cache = value;
|
|
30766
|
+
return typeof cache.checkedAt === "number" && typeof cache.latest === "string" && /^\d+\.\d+\.\d+$/.test(cache.latest);
|
|
30767
|
+
}
|
|
30768
|
+
function checkRegistry(cacheFile, now) {
|
|
30769
|
+
if (checking) return;
|
|
30770
|
+
checking = true;
|
|
30771
|
+
const done = () => {
|
|
30772
|
+
checking = false;
|
|
30773
|
+
};
|
|
30774
|
+
const req = request(REGISTRY_URL, { headers: { accept: "application/json" } });
|
|
30775
|
+
const timer = setTimeout(() => req.destroy(), TIMEOUT_MS);
|
|
30776
|
+
timer.unref();
|
|
30777
|
+
req.on("socket", (socket) => socket.unref());
|
|
30778
|
+
req.on("error", () => void 0);
|
|
30779
|
+
req.on("close", () => {
|
|
30780
|
+
clearTimeout(timer);
|
|
30781
|
+
done();
|
|
30782
|
+
});
|
|
30783
|
+
req.on("response", (response) => {
|
|
30784
|
+
const chunks = [];
|
|
30785
|
+
let size = 0;
|
|
30786
|
+
response.on("error", () => void 0);
|
|
30787
|
+
response.on("data", (chunk) => {
|
|
30788
|
+
size += chunk.length;
|
|
30789
|
+
if (size > MAX_BYTES) req.destroy();
|
|
30790
|
+
else chunks.push(chunk);
|
|
30791
|
+
});
|
|
30792
|
+
response.on("end", () => {
|
|
30793
|
+
if (response.statusCode !== 200 || size > MAX_BYTES) return;
|
|
30794
|
+
readJson(new Response(Buffer.concat(chunks)), "The npm registry").then((body) => {
|
|
30795
|
+
const cache = {
|
|
30796
|
+
checkedAt: now,
|
|
30797
|
+
latest: body.version
|
|
30798
|
+
};
|
|
30799
|
+
if (isLatestCache(cache)) writeJsonAtomic(cacheFile, cache, 384);
|
|
30800
|
+
}).catch(() => void 0);
|
|
30801
|
+
});
|
|
30802
|
+
});
|
|
30803
|
+
req.end();
|
|
30804
|
+
}
|
|
30805
|
+
function updateNotice(options = {}) {
|
|
30806
|
+
try {
|
|
30807
|
+
const env = options.env ?? process.env;
|
|
30808
|
+
const argv = options.argv ?? process.argv;
|
|
30809
|
+
if (env.NO_UPDATE_NOTIFIER || argv.includes("--no-update-notifier") || isTelemetryCi(env)) return;
|
|
30810
|
+
const cacheFile = options.cacheFile ?? join(getSeoCliPaths().cacheDir, "latest-version.json");
|
|
30811
|
+
const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
30812
|
+
const stored = readJsonFile(cacheFile);
|
|
30813
|
+
const cache = isLatestCache(stored) ? stored : void 0;
|
|
30814
|
+
if (!cache || now - cache.checkedAt >= CHECK_EVERY_MS) checkRegistry(cacheFile, now);
|
|
30815
|
+
const current = options.version ?? SEO_VERSION;
|
|
30816
|
+
if (!cache || !isNewerVersion(current, cache.latest)) return void 0;
|
|
30817
|
+
return runByNpx(env, options.cliPath ?? argv[1]) ? `seo ${cache.latest} is out (you have ${current}). Run npx -y seo@latest; in an MCP config use seo@latest, then restart the client.` : `seo ${cache.latest} is out (you have ${current}). Update with: npm i -g seo`;
|
|
30818
|
+
} catch {
|
|
30819
|
+
return;
|
|
30820
|
+
}
|
|
30821
|
+
}
|
|
30822
|
+
//#endregion
|
|
30567
30823
|
//#region packages/core/src/utils/time.ts
|
|
30568
30824
|
function parseOlderThan(input) {
|
|
30569
30825
|
if (!input) return;
|
|
@@ -30598,6 +30854,6 @@ function isoDate(daysAgo = 0) {
|
|
|
30598
30854
|
return date.toISOString().slice(0, 10);
|
|
30599
30855
|
}
|
|
30600
30856
|
//#endregion
|
|
30601
|
-
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_MAX_PAGES, DEFAULT_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, DEFAULT_UNREACHED_PAGE_LIMIT, DataForSeoClient, DecisionModelError, DecisionRunner, GSC_FINAL_DATA_LAG_DAYS, GSC_TIME_ZONE, GoogleApiError, INCIDENTS_URL, JsonFileStorageAdapter, LINK_ROLES, 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_MAX_PAGES, MAX_SEARCH_CONSOLE_EXPORT_FILES, MAX_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, PAGE_TYPES, PROVIDER_SECRET_NAMES, ProviderError, ProviderExtensionRegistry, RESEARCH_IMPORT_COLUMN_KEYS, ResponseSizeLimitError, SEARCH_INTENTS, 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, TYPESAFE_API_KEY_ENV, agentActionsView, aiReferralSourceForValue, aiReferralsReport, analyticsConnectionLabel, analyticsConnectionProviderId, analyticsConnectionSchema, analyticsProviderDetails, analyticsProviderSchema, analyzeAiReferralRows, analyzeCannibalImportRows, analyzeCannibalRows, analyzeCtrUnderperformersFromRows, analyzeDecay, analyzeIndexCoverageSignals, 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, listAnalyticsDataStreams, listGa4AccountSummaries, listGa4DataStreams, listGoogleAccounts, listInstalledProviderExtensions, listRules, listSearchConsoleSites, listSearchUpdates, listSites, loadInstalledProviderExtensions, loadProviderExtensions, loadProviderPackage, loadSearchConsoleExport, localAccounts, localBing, localCredentialsProvider, localError, localGoogleFetch, localInternalLinksDependencies, 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, siteStartUrl, startCloudLogin, strikingDistance, strikingDistanceWith, summarizeCrawlReport, pageTechnicalSignals as technicalPageSignals, telemetryErrorCategory, toSeoError, tokenSchema, tokenStoreSchema, topFixes, trackTelemetryCommandFailed, trackTelemetryReportComplete, trackTelemetryReportFailed, trackTelemetryReportStart, trackTelemetrySetupComplete, trafficAnomaly, trafficAnomalyWith, typesafeDecisionModel, unavailableSegmentImpactReport, unicodeTokens, uninstallProviderExtension, updateCorrelation, updatePostmortemCsvFiles, updatePostmortemWorkflow, validateResearchImportColumnSources, verifyAnalyticsProvider, verifyProviderExtension, verifyQueryContent, verifyQueryContentWith, withAgentReportContract, withRankingHistory, workflowPresentation, writeAhrefsApiKey, writeBingApiKey, writeClickySiteKey, writeConfig, writeDataForSeoCredentials, writeInstalledProviderPackages, writeOauthClient, writeProviderExtensionAccount, writeProviderExtensionCredentials, writeProviderSecret, writeTokens, xRobotsDirectives };
|
|
30857
|
+
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_MAX_PAGES, DEFAULT_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, DEFAULT_UNREACHED_PAGE_LIMIT, DataForSeoClient, DecisionModelError, DecisionRunner, GSC_FINAL_DATA_LAG_DAYS, GSC_TIME_ZONE, GoogleApiError, INCIDENTS_URL, JsonFileStorageAdapter, LINK_ROLES, 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_MAX_PAGES, MAX_SEARCH_CONSOLE_EXPORT_FILES, MAX_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, PAGE_TYPES, PROVIDER_SECRET_NAMES, ProviderError, ProviderExtensionRegistry, RESEARCH_IMPORT_COLUMN_KEYS, ResponseSizeLimitError, SEARCH_INTENTS, 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, TYPESAFE_API_KEY_ENV, agentActionsView, aiReferralSourceForValue, aiReferralsReport, analyticsConnectionLabel, analyticsConnectionProviderId, analyticsConnectionSchema, analyticsProviderDetails, analyticsProviderSchema, analyzeAiReferralRows, analyzeCannibalImportRows, analyzeCannibalRows, analyzeCtrUnderperformersFromRows, analyzeDecay, analyzeIndexCoverageSignals, analyzePageOpportunitiesFromRows, analyzeQueryClustersFromRows, analyzeQuickWinsFromRows, analyzeSecondPageRows, analyzeSegmentRows, analyzeStrikingDistanceRows, analyzeTrafficAnomalyRows, assertCrawlConfigLimits, assertUrlMatchesGscProperty, auditCrawlPages, auditCrawlRequests, auditPage, authStatus, bearerFetch, boundedSitemapInventory, buildAgentFindings, buildQueryClusterReportFromRows, buildTelemetryPayload, cacheDb, 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, isNewerVersion, isSkippableReportError, isTelemetryCi, isTelemetryOperation, isTelemetryReport, isoDate, isoWeek, latestFinalGscDate, linkRecover, linkRecoverRecommendation, listAnalyticsAccounts, listAnalyticsDataStreams, listGa4AccountSummaries, listGa4DataStreams, listGoogleAccounts, listInstalledProviderExtensions, listRules, listSearchConsoleSites, listSearchUpdates, listSites, loadInstalledProviderExtensions, loadProviderExtensions, loadProviderPackage, loadSearchConsoleExport, localAccounts, localBing, localCredentialsProvider, localError, localGoogleFetch, localInternalLinksDependencies, 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, siteStartUrl, startCloudLogin, strikingDistance, strikingDistanceWith, summarizeCrawlReport, pageTechnicalSignals as technicalPageSignals, telemetryErrorCategory, toSeoError, tokenSchema, tokenStoreSchema, topFixes, trackTelemetryCommandFailed, trackTelemetryReportComplete, trackTelemetryReportFailed, trackTelemetryReportStart, trackTelemetrySetupComplete, trafficAnomaly, trafficAnomalyWith, typesafeDecisionModel, unavailableSegmentImpactReport, unicodeTokens, uninstallProviderExtension, updateCorrelation, updateNotice, updatePostmortemCsvFiles, updatePostmortemWorkflow, urlInspectionQuota, validateResearchImportColumnSources, verifyAnalyticsProvider, verifyProviderExtension, verifyQueryContent, verifyQueryContentWith, withAgentReportContract, withRankingHistory, workflowPresentation, writeAhrefsApiKey, writeBingApiKey, writeClickySiteKey, writeConfig, writeDataForSeoCredentials, writeInstalledProviderPackages, writeOauthClient, writeProviderExtensionAccount, writeProviderExtensionCredentials, writeProviderSecret, writeToCache, writeTokens, xRobotsDirectives };
|
|
30602
30858
|
|
|
30603
30859
|
//# sourceMappingURL=index.js.map
|