seo 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +75 -75
- package/dist/cli.js.map +1 -1
- package/dist/{dist-CLV_HXUd.js → dist-3A3Vzp7r.js} +1 -1
- package/dist/{dist-wzZsD_Fi.js → dist-rmavn5h6.js} +121 -121
- package/dist/dist-rmavn5h6.js.map +1 -0
- package/dist/{esm-BfNQ2FiC.js → esm-BIXGSxFJ.js} +2 -2
- package/dist/{esm-BfNQ2FiC.js.map → esm-BIXGSxFJ.js.map} +1 -1
- package/dist/index.d.ts +173 -171
- package/dist/index.js +129 -70
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +223 -94
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
- package/dist/dist-wzZsD_Fi.js.map +0 -1
package/dist/mcp.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
|
|
2
|
-
import { i as __toESM, n as version, r as __commonJSMin, t as load } from "./esm-
|
|
2
|
+
import { i as __toESM, n as version, r as __commonJSMin, t as load } from "./esm-BIXGSxFJ.js";
|
|
3
3
|
import { createHash, randomUUID, sign } from "node:crypto";
|
|
4
4
|
import { ZodOptional, z } from "zod";
|
|
5
5
|
import { arch, homedir, platform, tmpdir, totalmem } from "node:os";
|
|
@@ -6811,7 +6811,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
6811
6811
|
};
|
|
6812
6812
|
}));
|
|
6813
6813
|
//#endregion
|
|
6814
|
-
//#region node_modules/.pnpm/fast-uri@3.1.
|
|
6814
|
+
//#region node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/utils.js
|
|
6815
6815
|
var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
6816
6816
|
/** @type {(value: string) => boolean} */
|
|
6817
6817
|
const isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
@@ -7105,6 +7105,38 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
7105
7105
|
return output.join("");
|
|
7106
7106
|
}
|
|
7107
7107
|
/**
|
|
7108
|
+
* Removes dot segments from the path of a relative reference (a path that does
|
|
7109
|
+
* not start with "/") without changing what it resolves to against any base.
|
|
7110
|
+
* Unlike `removeDotSegments`, leading ".." segments that cannot be collapsed
|
|
7111
|
+
* are kept, the result never becomes root-relative, and a result that would be
|
|
7112
|
+
* empty or start with an empty segment is prefixed with "." (an empty path
|
|
7113
|
+
* means "same document" and a leading "//" would introduce an authority).
|
|
7114
|
+
*
|
|
7115
|
+
* @param {string} path
|
|
7116
|
+
* @returns {string}
|
|
7117
|
+
*/
|
|
7118
|
+
function removeDotSegmentsRelative(path) {
|
|
7119
|
+
if (path === "" || path[0] === "/") return removeDotSegments(path);
|
|
7120
|
+
const segments = path.split("/");
|
|
7121
|
+
const last = segments.length - 1;
|
|
7122
|
+
/** @type {string[]} */
|
|
7123
|
+
const output = [];
|
|
7124
|
+
let trailingDirectory = false;
|
|
7125
|
+
for (let i = 0; i <= last; i++) {
|
|
7126
|
+
const segment = segments[i];
|
|
7127
|
+
if (segment === "." || segment === "..") {
|
|
7128
|
+
if (segment === "..") {
|
|
7129
|
+
if (output.length !== 0 && output[output.length - 1] !== "..") output.pop();
|
|
7130
|
+
else output.push("..");
|
|
7131
|
+
}
|
|
7132
|
+
trailingDirectory = i === last;
|
|
7133
|
+
} else output.push(segment);
|
|
7134
|
+
}
|
|
7135
|
+
if (trailingDirectory && output[output.length - 1] !== "..") output.push("");
|
|
7136
|
+
if (output.length === 0 || output[0] === "" || output[0].includes(":")) output.unshift(".");
|
|
7137
|
+
return output.join("/");
|
|
7138
|
+
}
|
|
7139
|
+
/**
|
|
7108
7140
|
* Re-escape RFC 3986 gen-delims that must not appear literally in the host.
|
|
7109
7141
|
* After the URI regex parses, these characters cannot be literal in the host
|
|
7110
7142
|
* field, so any that appear after decoding came from percent-encoding and
|
|
@@ -7408,6 +7440,7 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
7408
7440
|
encodeFragment,
|
|
7409
7441
|
escapePreservingEscapes,
|
|
7410
7442
|
removeDotSegments,
|
|
7443
|
+
removeDotSegmentsRelative,
|
|
7411
7444
|
isIPv4,
|
|
7412
7445
|
isUUID,
|
|
7413
7446
|
normalizeIPv6,
|
|
@@ -7415,7 +7448,7 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
7415
7448
|
};
|
|
7416
7449
|
}));
|
|
7417
7450
|
//#endregion
|
|
7418
|
-
//#region node_modules/.pnpm/fast-uri@3.1.
|
|
7451
|
+
//#region node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/schemes.js
|
|
7419
7452
|
var require_schemes = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7420
7453
|
const { isUUID } = require_utils();
|
|
7421
7454
|
const URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
|
|
@@ -7599,20 +7632,26 @@ var require_schemes = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
7599
7632
|
};
|
|
7600
7633
|
}));
|
|
7601
7634
|
//#endregion
|
|
7602
|
-
//#region node_modules/.pnpm/fast-uri@3.1.
|
|
7635
|
+
//#region node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/index.js
|
|
7603
7636
|
var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7604
|
-
const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
7637
|
+
const { normalizeIPv6, removeDotSegments, removeDotSegmentsRelative, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
7605
7638
|
const { SCHEMES, getSchemeHandler } = require_schemes();
|
|
7606
7639
|
const VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
|
|
7607
7640
|
const MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
|
|
7641
|
+
const MALFORMED_HOST_ERROR = "URI host is malformed.";
|
|
7642
|
+
const FORBIDDEN_DOMAIN_HOST_CHARACTER = /[\0-\x20"#/:<>?@[\\\]^`{|}\x7F]/u;
|
|
7608
7643
|
/**
|
|
7644
|
+
* RFC 3986 section 3.1 does not allow percent-encoding in a scheme, so the
|
|
7645
|
+
* scheme is validated as-is and never decoded. Decoding would let inert input
|
|
7646
|
+
* such as `%6Aavascript:` become a live `javascript:` scheme.
|
|
7647
|
+
*
|
|
7609
7648
|
* @param {string} scheme
|
|
7610
7649
|
* @returns {string}
|
|
7611
7650
|
*/
|
|
7612
|
-
function
|
|
7613
|
-
const
|
|
7614
|
-
if (!VALID_SCHEME.test(
|
|
7615
|
-
return
|
|
7651
|
+
function validateScheme(scheme) {
|
|
7652
|
+
const stringScheme = String(scheme);
|
|
7653
|
+
if (!VALID_SCHEME.test(stringScheme)) throw new TypeError(MALFORMED_SCHEME_ERROR);
|
|
7654
|
+
return stringScheme;
|
|
7616
7655
|
}
|
|
7617
7656
|
/**
|
|
7618
7657
|
* @template {import('./types/index').URIComponent|string} T
|
|
@@ -7640,6 +7679,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
7640
7679
|
const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
|
|
7641
7680
|
const resolvedHost = resolved.host;
|
|
7642
7681
|
const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
|
|
7682
|
+
if (hasForbiddenDomainHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)) throw new Error(MALFORMED_HOST_ERROR);
|
|
7643
7683
|
canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
|
|
7644
7684
|
const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
|
|
7645
7685
|
if (resolved.error && !encodedASCIIHost) throw new Error(resolved.error);
|
|
@@ -7683,10 +7723,11 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
7683
7723
|
} else {
|
|
7684
7724
|
if (relative.path[0] === "/") target.path = removeDotSegments(relative.path);
|
|
7685
7725
|
else {
|
|
7686
|
-
|
|
7726
|
+
const baseHasAuthority = base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0;
|
|
7727
|
+
if (baseHasAuthority && !base.path) target.path = "/" + relative.path;
|
|
7687
7728
|
else if (!base.path) target.path = relative.path;
|
|
7688
7729
|
else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
|
|
7689
|
-
target.path = removeDotSegments(target.path);
|
|
7730
|
+
target.path = base.scheme === void 0 && !baseHasAuthority && target.path[0] !== "/" ? removeDotSegmentsRelative(target.path) : removeDotSegments(target.path);
|
|
7690
7731
|
}
|
|
7691
7732
|
target.query = relative.query;
|
|
7692
7733
|
}
|
|
@@ -7734,17 +7775,18 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
7734
7775
|
};
|
|
7735
7776
|
const options = Object.assign({}, opts);
|
|
7736
7777
|
const uriTokens = [];
|
|
7737
|
-
if (component.scheme) component.scheme =
|
|
7778
|
+
if (component.scheme) component.scheme = validateScheme(component.scheme);
|
|
7738
7779
|
const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
|
|
7739
7780
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
|
|
7740
7781
|
const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
|
|
7741
|
-
const
|
|
7782
|
+
const schemelessReference = (component.scheme === void 0 || options.reference === "suffix") && !hasAuthority;
|
|
7783
|
+
const pathNoScheme = !options.skipEscape && schemelessReference;
|
|
7742
7784
|
if (component.path !== void 0) {
|
|
7743
7785
|
if (!options.skipEscape) component.path = serializePathEncoding(component.path, pathNoScheme);
|
|
7744
7786
|
else component.path = normalizePercentEncoding(component.path);
|
|
7745
7787
|
}
|
|
7746
7788
|
if (options.reference !== "suffix" && component.scheme) {
|
|
7747
|
-
component.scheme =
|
|
7789
|
+
component.scheme = validateScheme(component.scheme);
|
|
7748
7790
|
uriTokens.push(component.scheme, ":");
|
|
7749
7791
|
}
|
|
7750
7792
|
const authority = recomposeAuthority(component);
|
|
@@ -7755,8 +7797,8 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
7755
7797
|
}
|
|
7756
7798
|
if (component.path !== void 0) {
|
|
7757
7799
|
let s = component.path;
|
|
7758
|
-
if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) s = removeDotSegments(s);
|
|
7759
|
-
if (
|
|
7800
|
+
if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) s = schemelessReference && s[0] !== "/" ? removeDotSegmentsRelative(s) : removeDotSegments(s);
|
|
7801
|
+
if (schemelessReference) s = serializePathEncoding(s, true);
|
|
7760
7802
|
if (authority === void 0 && s[0] === "/" && s[1] === "/") s = "/%2F" + s.slice(2);
|
|
7761
7803
|
uriTokens.push(s);
|
|
7762
7804
|
}
|
|
@@ -7820,15 +7862,50 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
7820
7862
|
* @returns {boolean} whether host conversion failed
|
|
7821
7863
|
*/
|
|
7822
7864
|
function canonicalizeHost(parsed, options, schemeHandler, isIP) {
|
|
7823
|
-
if (
|
|
7824
|
-
parsed.
|
|
7825
|
-
} catch (e) {
|
|
7826
|
-
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
7865
|
+
if (hasForbiddenDomainHost(parsed, options, schemeHandler, isIP)) {
|
|
7866
|
+
parsed.error = parsed.error || MALFORMED_HOST_ERROR;
|
|
7827
7867
|
return true;
|
|
7828
7868
|
}
|
|
7869
|
+
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
7870
|
+
let url;
|
|
7871
|
+
try {
|
|
7872
|
+
url = new URL("http://" + parsed.host);
|
|
7873
|
+
} catch (e) {
|
|
7874
|
+
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
7875
|
+
return true;
|
|
7876
|
+
}
|
|
7877
|
+
if (!isHostOnlyURL(url)) {
|
|
7878
|
+
parsed.error = parsed.error || MALFORMED_HOST_ERROR;
|
|
7879
|
+
return true;
|
|
7880
|
+
}
|
|
7881
|
+
parsed.host = url.hostname;
|
|
7882
|
+
}
|
|
7829
7883
|
return false;
|
|
7830
7884
|
}
|
|
7831
7885
|
/**
|
|
7886
|
+
* Whether a domain host contains an ASCII character that RFC 3986 does not
|
|
7887
|
+
* allow in a reg-name (unreserved, sub-delims, or pct-encoded). Such a host is
|
|
7888
|
+
* rejected instead of being handed to the WHATWG URL parser, which would treat
|
|
7889
|
+
* "@" as a userinfo delimiter (silently dropping the data before it) and strip
|
|
7890
|
+
* TAB, LF, and CR. Non-ASCII characters remain allowed for IDNs.
|
|
7891
|
+
*
|
|
7892
|
+
* @param {import('./types/index').URIComponent} parsed
|
|
7893
|
+
* @param {import('./types/index').Options} options
|
|
7894
|
+
* @param {{ domainHost?: boolean }|undefined} schemeHandler
|
|
7895
|
+
* @param {boolean} isIP
|
|
7896
|
+
* @returns {boolean}
|
|
7897
|
+
*/
|
|
7898
|
+
function hasForbiddenDomainHost(parsed, options, schemeHandler, isIP) {
|
|
7899
|
+
return Boolean(parsed.host && isIP === false && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && FORBIDDEN_DOMAIN_HOST_CHARACTER.test(parsed.host));
|
|
7900
|
+
}
|
|
7901
|
+
/**
|
|
7902
|
+
* @param {URL} url
|
|
7903
|
+
* @returns {boolean}
|
|
7904
|
+
*/
|
|
7905
|
+
function isHostOnlyURL(url) {
|
|
7906
|
+
return url.username === "" && url.password === "" && url.port === "" && url.pathname === "/" && url.search === "" && url.hash === "" && url.hostname !== "";
|
|
7907
|
+
}
|
|
7908
|
+
/**
|
|
7832
7909
|
* @param {string} uri
|
|
7833
7910
|
* @param {import('./types/index').Options} [opts]
|
|
7834
7911
|
* @returns {{ parsed: import('./types/index').URIComponent, malformedAuthorityOrPort: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
|
|
@@ -7885,8 +7962,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
7885
7962
|
parsed.query = matches[7];
|
|
7886
7963
|
parsed.fragment = matches[8];
|
|
7887
7964
|
if (parsed.scheme !== void 0) {
|
|
7888
|
-
|
|
7889
|
-
if (VALID_SCHEME.test(decodedScheme)) parsed.scheme = decodedScheme.toLowerCase();
|
|
7965
|
+
if (VALID_SCHEME.test(parsed.scheme)) parsed.scheme = parsed.scheme.toLowerCase();
|
|
7890
7966
|
else {
|
|
7891
7967
|
parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
|
|
7892
7968
|
malformedScheme = true;
|
|
@@ -7909,7 +7985,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
7909
7985
|
malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
|
|
7910
7986
|
parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
|
|
7911
7987
|
if (malformedIPLiteral) {
|
|
7912
|
-
parsed.error = parsed.error ||
|
|
7988
|
+
parsed.error = parsed.error || MALFORMED_HOST_ERROR;
|
|
7913
7989
|
malformedAuthorityOrPort = true;
|
|
7914
7990
|
}
|
|
7915
7991
|
} else isIP = true;
|
|
@@ -7924,7 +8000,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
7924
8000
|
if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
|
|
7925
8001
|
let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
|
|
7926
8002
|
if (!isIP) host = normalizePercentEncoding(host.toLowerCase());
|
|
7927
|
-
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
8003
|
+
parsed.host = isIP || options.scheme !== "null" ? reescapeHostDelimiters(host, isIP) : host;
|
|
7928
8004
|
}
|
|
7929
8005
|
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
7930
8006
|
if (parsed.path) parsed.path = normalizePathEncoding(parsed.path);
|
|
@@ -13560,6 +13636,44 @@ function agentActionsView(report, options = {}) {
|
|
|
13560
13636
|
};
|
|
13561
13637
|
}
|
|
13562
13638
|
//#endregion
|
|
13639
|
+
//#region packages/core/dist/providers/errors.js
|
|
13640
|
+
const PROVIDER_ERROR_CODES = {
|
|
13641
|
+
configuration: "AUTH_CONFIG_REQUIRED",
|
|
13642
|
+
authentication: "AUTH_CONFIG_REQUIRED",
|
|
13643
|
+
"rate-limit": "RATE_LIMITED",
|
|
13644
|
+
timeout: "PROVIDER_UNAVAILABLE",
|
|
13645
|
+
"response-too-large": "PROVIDER_UNAVAILABLE",
|
|
13646
|
+
"invalid-response": "PROVIDER_UNAVAILABLE",
|
|
13647
|
+
"remote-error": "PROVIDER_UNAVAILABLE"
|
|
13648
|
+
};
|
|
13649
|
+
var ProviderError = class extends Error {
|
|
13650
|
+
provider;
|
|
13651
|
+
operation;
|
|
13652
|
+
code;
|
|
13653
|
+
status;
|
|
13654
|
+
retryable;
|
|
13655
|
+
constructor(input) {
|
|
13656
|
+
super(input.message, { cause: input.cause });
|
|
13657
|
+
this.name = "ProviderError";
|
|
13658
|
+
this.provider = input.provider;
|
|
13659
|
+
this.operation = input.operation;
|
|
13660
|
+
this.code = input.code;
|
|
13661
|
+
this.status = input.status ?? null;
|
|
13662
|
+
this.retryable = input.retryable ?? false;
|
|
13663
|
+
}
|
|
13664
|
+
toJSON() {
|
|
13665
|
+
return {
|
|
13666
|
+
name: this.name,
|
|
13667
|
+
provider: this.provider,
|
|
13668
|
+
operation: this.operation,
|
|
13669
|
+
code: this.code,
|
|
13670
|
+
message: this.message,
|
|
13671
|
+
status: this.status,
|
|
13672
|
+
retryable: this.retryable
|
|
13673
|
+
};
|
|
13674
|
+
}
|
|
13675
|
+
};
|
|
13676
|
+
//#endregion
|
|
13563
13677
|
//#region packages/core/dist/errors.js
|
|
13564
13678
|
const errorMetadata = {
|
|
13565
13679
|
ACCESS_DENIED: {
|
|
@@ -13625,6 +13739,7 @@ function errorMessage$1(error) {
|
|
|
13625
13739
|
function toSeoError(error) {
|
|
13626
13740
|
if (error instanceof SeoError) return error;
|
|
13627
13741
|
const message = errorMessage$1(error);
|
|
13742
|
+
if (error instanceof ProviderError) return new SeoError(PROVIDER_ERROR_CODES[error.code], message);
|
|
13628
13743
|
if (/not logged in/i.test(message)) return new SeoError("AUTH_REQUIRED", message);
|
|
13629
13744
|
if (/oauth client config.*missing|shared seo google app/i.test(message)) return new SeoError("AUTH_CONFIG_REQUIRED", message);
|
|
13630
13745
|
if (/invalid_grant|refresh token.*no longer valid/i.test(message)) return new SeoError("AUTH_EXPIRED", message);
|
|
@@ -14408,15 +14523,18 @@ function factsOnly(value) {
|
|
|
14408
14523
|
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).filter(([key]) => !ADVICE_KEYS.test(key)).map(([key, child]) => [key, factsOnly(child)]));
|
|
14409
14524
|
return value;
|
|
14410
14525
|
}
|
|
14526
|
+
function realIsoDate(value) {
|
|
14527
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(value) && (/* @__PURE__ */ new Date(`${value}T00:00:00Z`)).toISOString().startsWith(value);
|
|
14528
|
+
}
|
|
14411
14529
|
function dateWindow(input, fallbackDays) {
|
|
14412
|
-
if (input.startDate
|
|
14413
|
-
|
|
14414
|
-
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14530
|
+
if (!input.startDate && !input.endDate) return finalGscDateRange(fallbackDays);
|
|
14531
|
+
const endDate = input.endDate ?? latestFinalGscDate();
|
|
14532
|
+
const startDate = input.startDate ?? shiftIsoDate(endDate, 1 - fallbackDays);
|
|
14533
|
+
if (!realIsoDate(startDate) || !realIsoDate(endDate) || startDate > endDate) throw new CommandError("INVALID_INPUT", `Send real YYYY-MM-DD dates with the start before the end (got ${startDate} to ${endDate}).`);
|
|
14534
|
+
return {
|
|
14535
|
+
startDate,
|
|
14536
|
+
endDate
|
|
14537
|
+
};
|
|
14420
14538
|
}
|
|
14421
14539
|
function earlierRange(range) {
|
|
14422
14540
|
return decayComparisonRange(range, "previous-period");
|
|
@@ -14500,20 +14618,38 @@ function listOfUpdates(value, checked) {
|
|
|
14500
14618
|
if (value.length === 0) return `Google updates ${where}: none.`;
|
|
14501
14619
|
return `Google updates ${where} (timing only, not cause): ${value.map((update) => `${update.name} (${update.start.slice(0, 10)} to ${update.end?.slice(0, 10) ?? "ongoing"})`).join("; ")}.`;
|
|
14502
14620
|
}
|
|
14621
|
+
function readable(names, who = "it") {
|
|
14622
|
+
if (!names.length) return `Search Console properties ${who} can read: none.`;
|
|
14623
|
+
const more = names.length > 20 ? `, and ${names.length - 20} more` : "";
|
|
14624
|
+
return `Search Console properties ${who} can read: ${names.slice(0, 20).join(", ")}${more}.`;
|
|
14625
|
+
}
|
|
14503
14626
|
function matchProperty(names, site) {
|
|
14504
|
-
|
|
14505
|
-
|
|
14506
|
-
let
|
|
14627
|
+
const value = site.trim();
|
|
14628
|
+
if (names.includes(value)) return value;
|
|
14629
|
+
let url;
|
|
14507
14630
|
try {
|
|
14508
|
-
|
|
14509
|
-
host = url.hostname.replace(/^www\./, "");
|
|
14510
|
-
origin = url.origin;
|
|
14631
|
+
url = new URL(value.includes("://") ? value : `https://${value.replace(/^sc-domain:/i, "")}`);
|
|
14511
14632
|
} catch {
|
|
14512
14633
|
return null;
|
|
14513
14634
|
}
|
|
14514
|
-
const
|
|
14515
|
-
|
|
14516
|
-
|
|
14635
|
+
const bare = (name) => name.replace(/^www\./, "");
|
|
14636
|
+
const host = bare(url.hostname);
|
|
14637
|
+
const domain = names.filter((name) => name.startsWith("sc-domain:")).filter((name) => {
|
|
14638
|
+
const own = name.slice(10);
|
|
14639
|
+
return own.startsWith("www.") ? bare(own) === host : host === own || host.endsWith(`.${own}`);
|
|
14640
|
+
}).sort((a, b) => b.length - a.length || a.localeCompare(b))[0];
|
|
14641
|
+
if (domain) return domain;
|
|
14642
|
+
const schemes = value.includes("://") ? [url.protocol] : ["https:", "http:"];
|
|
14643
|
+
return names.map((name) => {
|
|
14644
|
+
try {
|
|
14645
|
+
return {
|
|
14646
|
+
name,
|
|
14647
|
+
url: new URL(name)
|
|
14648
|
+
};
|
|
14649
|
+
} catch {
|
|
14650
|
+
return null;
|
|
14651
|
+
}
|
|
14652
|
+
}).filter((entry) => entry !== null && schemes.includes(entry.url.protocol) && bare(entry.url.hostname) === host && `${url.pathname}/`.startsWith(entry.url.pathname)).sort((a, b) => b.url.pathname.length - a.url.pathname.length || schemes.indexOf(a.url.protocol) - schemes.indexOf(b.url.protocol) || Number(b.url.hostname === url.hostname) - Number(a.url.hostname === url.hostname) || a.name.localeCompare(b.name))[0]?.name ?? null;
|
|
14517
14653
|
}
|
|
14518
14654
|
//#endregion
|
|
14519
14655
|
//#region packages/core/dist/commands/present.js
|
|
@@ -14935,9 +15071,10 @@ var LinkedAccounts = class {
|
|
|
14935
15071
|
}
|
|
14936
15072
|
async resolve(site) {
|
|
14937
15073
|
if (this.google.length === 0) throw new CommandError("NOT_CONNECTED", `No Google account is connected. ${this.hint.signIn}`);
|
|
14938
|
-
const
|
|
15074
|
+
const names = await this.properties();
|
|
15075
|
+
const found = matchProperty(names, site);
|
|
14939
15076
|
if (found) return found;
|
|
14940
|
-
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}`);
|
|
15077
|
+
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}. ${readable(names, "they")} ${this.hint.addGoogle}`);
|
|
14941
15078
|
}
|
|
14942
15079
|
async owner(site) {
|
|
14943
15080
|
const owner = (await this.ownerMap()).get(await this.resolve(site));
|
|
@@ -15286,6 +15423,10 @@ function classify(error) {
|
|
|
15286
15423
|
errorCategory: "crawl_timeout",
|
|
15287
15424
|
failureReason: "crawl_timeout"
|
|
15288
15425
|
};
|
|
15426
|
+
const provider = signals.find((signal) => signal.name === "ProviderError");
|
|
15427
|
+
const providerCode = PROVIDER_ERROR_CODES[provider?.code];
|
|
15428
|
+
const fromProvider = providerCode && PRODUCT_FAILURES[providerCode];
|
|
15429
|
+
if (fromProvider) return fromProvider;
|
|
15289
15430
|
for (const code of codes) {
|
|
15290
15431
|
const product = PRODUCT_FAILURES[code];
|
|
15291
15432
|
if (product) return product;
|
|
@@ -20540,7 +20681,7 @@ async function queryPagesTopQueriesBatch(site, pageUrls, range = 28) {
|
|
|
20540
20681
|
}
|
|
20541
20682
|
//#endregion
|
|
20542
20683
|
//#region packages/core/dist/gsc/client/sites.js
|
|
20543
|
-
async function listSites(_refresh = false, accountEmail) {
|
|
20684
|
+
async function listSites$1(_refresh = false, accountEmail) {
|
|
20544
20685
|
const entries = !accountEmail && cloudLinked() ? await allCloudSites() : await listSearchConsoleSites(googleFetchFor((await getAuthorized(accountEmail)).client), LOCAL_SIGN_IN);
|
|
20545
20686
|
writeToCache((database) => {
|
|
20546
20687
|
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))");
|
|
@@ -26618,35 +26759,6 @@ async function fetchSitemapUrls(input) {
|
|
|
26618
26759
|
};
|
|
26619
26760
|
}
|
|
26620
26761
|
//#endregion
|
|
26621
|
-
//#region packages/core/dist/providers/errors.js
|
|
26622
|
-
var ProviderError = class extends Error {
|
|
26623
|
-
provider;
|
|
26624
|
-
operation;
|
|
26625
|
-
code;
|
|
26626
|
-
status;
|
|
26627
|
-
retryable;
|
|
26628
|
-
constructor(input) {
|
|
26629
|
-
super(input.message, { cause: input.cause });
|
|
26630
|
-
this.name = "ProviderError";
|
|
26631
|
-
this.provider = input.provider;
|
|
26632
|
-
this.operation = input.operation;
|
|
26633
|
-
this.code = input.code;
|
|
26634
|
-
this.status = input.status ?? null;
|
|
26635
|
-
this.retryable = input.retryable ?? false;
|
|
26636
|
-
}
|
|
26637
|
-
toJSON() {
|
|
26638
|
-
return {
|
|
26639
|
-
name: this.name,
|
|
26640
|
-
provider: this.provider,
|
|
26641
|
-
operation: this.operation,
|
|
26642
|
-
code: this.code,
|
|
26643
|
-
message: this.message,
|
|
26644
|
-
status: this.status,
|
|
26645
|
-
retryable: this.retryable
|
|
26646
|
-
};
|
|
26647
|
-
}
|
|
26648
|
-
};
|
|
26649
|
-
//#endregion
|
|
26650
26762
|
//#region packages/core/dist/providers/cache.js
|
|
26651
26763
|
function stableValue(value) {
|
|
26652
26764
|
if (Array.isArray(value)) return value.map(stableValue);
|
|
@@ -33081,7 +33193,7 @@ function analyzeTrafficAnomalyRows(input) {
|
|
|
33081
33193
|
const comparisonStart = shiftIsoDate(input.endDate, -(input.recentDays - 1));
|
|
33082
33194
|
const baselineEnd = shiftIsoDate(comparisonStart, -1);
|
|
33083
33195
|
const baselineExpectedDays = calendarDays(input.startDate, baselineEnd);
|
|
33084
|
-
if (comparisonStart < input.startDate || baselineExpectedDays < 14) throw new SeoError("INSUFFICIENT_DATA",
|
|
33196
|
+
if (comparisonStart < input.startDate || baselineExpectedDays < 14) throw new SeoError("INSUFFICIENT_DATA", `The date range needs at least ${input.recentDays + 14} days: 14 baseline days before the last ${input.recentDays}. Send a longer range, or leave the dates out for the default.`);
|
|
33085
33197
|
const aggregated = aggregateDailyRows({
|
|
33086
33198
|
rows: input.rows,
|
|
33087
33199
|
startDate: input.startDate,
|
|
@@ -33089,7 +33201,7 @@ function analyzeTrafficAnomalyRows(input) {
|
|
|
33089
33201
|
});
|
|
33090
33202
|
const baselineRows = aggregated.rows.filter((row) => (row.keys[0] ?? "") <= baselineEnd);
|
|
33091
33203
|
const comparisonRows = aggregated.rows.filter((row) => (row.keys[0] ?? "") >= comparisonStart);
|
|
33092
|
-
if (baselineRows.length < 14 || comparisonRows.length < 3) throw new SeoError("INSUFFICIENT_DATA", `Not enough
|
|
33204
|
+
if (baselineRows.length < 14 || comparisonRows.length < 3) throw new SeoError("INSUFFICIENT_DATA", `Not enough daily Search Console data to look for a drop: ${baselineRows.length} baseline days and ${comparisonRows.length} comparison days, and it needs 14 and 3. The site may be new or get too few clicks for daily rows.`);
|
|
33093
33205
|
const metrics = ["clicks", "impressions"];
|
|
33094
33206
|
const stepCaveats = [];
|
|
33095
33207
|
const anomalies = metrics.map((metric) => {
|
|
@@ -42673,10 +42785,10 @@ function markdownText(markdown, url) {
|
|
|
42673
42785
|
return `Main content as Markdown (live page${markdown.truncated ? `, cut to ${markdown.markdown.length.toLocaleString("en-US")} of ${markdown.characters.toLocaleString("en-US")} characters` : ""}):\n\n${markdown.markdown}`;
|
|
42674
42786
|
}
|
|
42675
42787
|
async function runCompare(input, deps) {
|
|
42676
|
-
if (
|
|
42788
|
+
if (!realIsoDate(input.date)) throw new CommandError("INVALID_INPUT", "The date must be a real day as YYYY-MM-DD.");
|
|
42677
42789
|
const last = latestFinalGscDate(deps.now?.() ?? /* @__PURE__ */ new Date());
|
|
42678
42790
|
const daysAfter = Math.round((Date.parse(last) - Date.parse(input.date)) / 864e5) + 1;
|
|
42679
|
-
if (daysAfter < 3) throw new CommandError("INVALID_INPUT", `Search Console has final data to ${last}
|
|
42791
|
+
if (daysAfter < 3) throw new CommandError("INVALID_INPUT", `Search Console has final data to ${last}, and a comparison needs 3 days after the change. The latest date that works is ${shiftIsoDate(last, -2)}.`);
|
|
42680
42792
|
const days = Math.min(28, daysAfter);
|
|
42681
42793
|
const after = {
|
|
42682
42794
|
startDate: input.date,
|
|
@@ -43048,8 +43160,15 @@ function weekMarkdown(data) {
|
|
|
43048
43160
|
out.push("", "## Ask your agent", "", "Paste this into your agent:", "", "```", prompt, "```");
|
|
43049
43161
|
return out.join("\n");
|
|
43050
43162
|
}
|
|
43051
|
-
async function runAllSites(deps) {
|
|
43163
|
+
async function runAllSites(deps, sites) {
|
|
43052
43164
|
const data = deps.week ? await deps.week() : null;
|
|
43165
|
+
if (!data && sites) {
|
|
43166
|
+
const list = await sites();
|
|
43167
|
+
return {
|
|
43168
|
+
text: `No site given. Pick one below and send it: ${deps.call("report", { site: "<site>" })}. The summary of every site is for accounts in the weekly site audit beta.\n\n${list.text}`,
|
|
43169
|
+
data: list.data
|
|
43170
|
+
};
|
|
43171
|
+
}
|
|
43053
43172
|
if (!data) throw new CommandError("INVALID_INPUT", `No site given. Name one: ${deps.call("report", { site: "<site>" })}. The summary of every site is for accounts in the weekly site audit beta.`);
|
|
43054
43173
|
const startMs = Date.parse(`${data.start}T00:00:00Z`);
|
|
43055
43174
|
const endMs = Date.parse(`${data.end}T00:00:00Z`) + DAY_MS;
|
|
@@ -43065,8 +43184,8 @@ async function runAllSites(deps) {
|
|
|
43065
43184
|
//#region packages/core/dist/commands/tools.js
|
|
43066
43185
|
const site = z.string().min(1).describe("Search Console property or site, such as sc-domain:example.com, https://example.com/ or example.com.");
|
|
43067
43186
|
const dates = {
|
|
43068
|
-
start_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD. Optional;
|
|
43069
|
-
end_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD. Optional
|
|
43187
|
+
start_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD. Optional; without end_date it runs to the latest final day."),
|
|
43188
|
+
end_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD. Optional.")
|
|
43070
43189
|
};
|
|
43071
43190
|
const detail = {
|
|
43072
43191
|
detail: z.enum([
|
|
@@ -43085,6 +43204,14 @@ const DATA_SOURCES = [
|
|
|
43085
43204
|
function tool(spec) {
|
|
43086
43205
|
return spec;
|
|
43087
43206
|
}
|
|
43207
|
+
const listSites = async (surface) => runSites({
|
|
43208
|
+
accounts: await surface.accounts(),
|
|
43209
|
+
keys: await surface.keys(),
|
|
43210
|
+
clicky: clickySites(await surface.keys()).map(clickyLabel),
|
|
43211
|
+
signIn: surface.signIn,
|
|
43212
|
+
bing: surface.deps.bing ? await surface.deps.bing() : void 0,
|
|
43213
|
+
connect: surface.connect
|
|
43214
|
+
});
|
|
43088
43215
|
const SHARED_TOOLS = [
|
|
43089
43216
|
tool({
|
|
43090
43217
|
name: "seo_sites",
|
|
@@ -43092,20 +43219,13 @@ const SHARED_TOOLS = [
|
|
|
43092
43219
|
title: "List sites",
|
|
43093
43220
|
description: "Lists the Search Console and Google Analytics properties the connected Google accounts can read, the Bing Webmaster site for each, and which research keys are set. Call it first.",
|
|
43094
43221
|
input: z.object({}),
|
|
43095
|
-
run:
|
|
43096
|
-
accounts: await surface.accounts(),
|
|
43097
|
-
keys: await surface.keys(),
|
|
43098
|
-
clicky: clickySites(await surface.keys()).map(clickyLabel),
|
|
43099
|
-
signIn: surface.signIn,
|
|
43100
|
-
bing: surface.deps.bing ? await surface.deps.bing() : void 0,
|
|
43101
|
-
connect: surface.connect
|
|
43102
|
-
})
|
|
43222
|
+
run: listSites
|
|
43103
43223
|
}),
|
|
43104
43224
|
tool({
|
|
43105
43225
|
name: "seo_report",
|
|
43106
43226
|
command: "report",
|
|
43107
43227
|
title: "Search Console report",
|
|
43108
|
-
description: `Start here. Without site: a short summary of every site in the person's weekly site audit (beta) for last week, with what needs a look and the next step for each, ending with the site to ask about next; an account outside that beta
|
|
43228
|
+
description: `Start here. Without site: a short summary of every site in the person's weekly site audit (beta) for last week, with what needs a look and the next step for each, ending with the site to ask about next; an account outside that beta gets its site list to pick from. With site: the main report for that Search Console property, the overview plus each section's row count and top rows. With site and section: that section's rows; page sections also check their top 20 URLs live. Sections:\n${Object.entries(SECTIONS).map(([id, text]) => `- ${id}: ${text}`).join("\n")}`,
|
|
43109
43229
|
input: z.object({
|
|
43110
43230
|
site: site.optional().describe("Leave it out for the summary of every audited site (weekly site audit beta). Otherwise a Search Console property or site, such as sc-domain:example.com, https://example.com/ or example.com."),
|
|
43111
43231
|
section: z.enum(SECTION_IDS).optional().describe("Optional, needs site. Leave it out first; then ask for one section."),
|
|
@@ -43118,7 +43238,7 @@ const SHARED_TOOLS = [
|
|
|
43118
43238
|
site: "<site>",
|
|
43119
43239
|
section: input.section
|
|
43120
43240
|
})}.`);
|
|
43121
|
-
return runAllSites(surface.deps);
|
|
43241
|
+
return runAllSites(surface.deps, () => listSites(surface));
|
|
43122
43242
|
}
|
|
43123
43243
|
const target = input.site;
|
|
43124
43244
|
const resolved = await surface.resolveSite(target).catch((error) => error instanceof Error ? error : new Error(String(error)));
|
|
@@ -43674,8 +43794,9 @@ async function localRefreshClicky() {
|
|
|
43674
43794
|
return localResearchKeys();
|
|
43675
43795
|
}
|
|
43676
43796
|
async function resolveLocalSite(site, options = {}) {
|
|
43677
|
-
const
|
|
43678
|
-
|
|
43797
|
+
const names = (await listSites$1(options.refresh, options.account)).map((entry) => entry.siteUrl);
|
|
43798
|
+
const found = matchProperty(names, site);
|
|
43799
|
+
if (!found) throw new CommandError("NO_ACCESS", `This Google login has no Search Console property for ${site}. ${readable(names)}`);
|
|
43679
43800
|
return found;
|
|
43680
43801
|
}
|
|
43681
43802
|
const ERROR_CODES = {
|
|
@@ -43683,6 +43804,7 @@ const ERROR_CODES = {
|
|
|
43683
43804
|
NOT_CONNECTED: "AUTH_REQUIRED",
|
|
43684
43805
|
NO_ACCESS: "PROPERTY_NOT_FOUND",
|
|
43685
43806
|
NO_KEY: "AUTH_CONFIG_REQUIRED",
|
|
43807
|
+
NO_DATA: "INSUFFICIENT_DATA",
|
|
43686
43808
|
RATE_LIMITED: "RATE_LIMITED",
|
|
43687
43809
|
GOOGLE_ERROR: "PROVIDER_UNAVAILABLE",
|
|
43688
43810
|
PROVIDER_ERROR: "PROVIDER_UNAVAILABLE"
|
|
@@ -46607,13 +46729,20 @@ const REMOVED_REPORT_IDS = {
|
|
|
46607
46729
|
"server-log-analysis": null,
|
|
46608
46730
|
"technical-watch": "seo crawl"
|
|
46609
46731
|
};
|
|
46610
|
-
|
|
46732
|
+
const REPORT_ALIASES = {
|
|
46733
|
+
cannibalization: "cannibalisation",
|
|
46734
|
+
"content-decay": "decaying-pages",
|
|
46735
|
+
"low-ctr": "ctr-underperformers",
|
|
46736
|
+
"traffic-drop": "traffic-anomaly"
|
|
46737
|
+
};
|
|
46738
|
+
function activeReport(name) {
|
|
46739
|
+
const id = REPORT_ALIASES[name] ?? name;
|
|
46611
46740
|
if (Object.hasOwn(REMOVED_REPORT_IDS, id)) {
|
|
46612
46741
|
const replacement = REMOVED_REPORT_IDS[id];
|
|
46613
46742
|
throw new SeoError("INVALID_INPUT", replacement ? `The ${id} report was removed. Use \`${replacement}\` instead.` : `The ${id} report was removed. Run \`seo help\` for the commands.`);
|
|
46614
46743
|
}
|
|
46615
46744
|
const report = getReportDefinition(id);
|
|
46616
|
-
if (!report) throw new SeoError("INVALID_INPUT", `Unknown report: ${id}.`);
|
|
46745
|
+
if (!report) throw new SeoError("INVALID_INPUT", `Unknown report: ${id}. Reports: ${listReportDefinitions().map((item) => item.id).join(", ")}.`);
|
|
46617
46746
|
return report;
|
|
46618
46747
|
}
|
|
46619
46748
|
function listReports(category) {
|