seo 0.2.5 → 0.2.7
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 +41 -4
- package/README.md +11 -3
- package/dist/cli.js +91 -88
- package/dist/cli.js.map +1 -1
- package/dist/{dist-oHPXlHac.js → dist-B6UxI6sc.js} +1 -1
- package/dist/{dist-oOrG0Vub.js → dist-C0LWlNAw.js} +70 -65
- package/dist/dist-C0LWlNAw.js.map +1 -0
- package/dist/index.d.ts +118 -11
- package/dist/index.js +445 -9
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +3 -1
- package/dist/mcp.js +404 -10
- package/dist/mcp.js.map +1 -1
- package/dist/{package-BF_XgK7-.js → package-BI-bLeV4.js} +2 -2
- package/dist/{package-BF_XgK7-.js.map → package-BI-bLeV4.js.map} +1 -1
- package/package.json +3 -4
- package/dist/dist-oOrG0Vub.js.map +0 -1
package/dist/mcp.d.ts
CHANGED
|
@@ -65,7 +65,9 @@ declare function executeReport(id: string, params?: Record<string, unknown>): Pr
|
|
|
65
65
|
declare function runReport(id: string, params?: Record<string, unknown>): Promise<ToolResult>;
|
|
66
66
|
//#endregion
|
|
67
67
|
//#region packages/mcp/src/index.d.ts
|
|
68
|
-
declare function createServer(
|
|
68
|
+
declare function createServer(options?: {
|
|
69
|
+
telemetry?: boolean;
|
|
70
|
+
}): McpServer;
|
|
69
71
|
declare function startMcpServer(opts?: {
|
|
70
72
|
test?: boolean;
|
|
71
73
|
}): Promise<void>;
|
package/dist/mcp.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
|
|
2
|
-
import { n as load, t as version } from "./package-
|
|
2
|
+
import { n as load, t as version } from "./package-BI-bLeV4.js";
|
|
3
3
|
import { Headers, fetch as fetch$1 } from "undici";
|
|
4
4
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { homedir } from "node:os";
|
|
5
|
+
import { arch, homedir, platform } from "node:os";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import lockfile from "proper-lockfile";
|
|
9
9
|
import { createHash, randomUUID, sign } from "node:crypto";
|
|
10
10
|
import "node:http";
|
|
11
11
|
import "open";
|
|
12
|
-
import
|
|
12
|
+
import Libsql from "libsql";
|
|
13
13
|
import { Readability } from "@mozilla/readability";
|
|
14
14
|
import Defuddle from "defuddle";
|
|
15
15
|
import { parseHTML } from "linkedom";
|
|
@@ -149,6 +149,7 @@ function resolveSeoCliPaths(input = {}) {
|
|
|
149
149
|
configFile: join(configDir, "config.json"),
|
|
150
150
|
tokensFile: join(configDir, "tokens.json"),
|
|
151
151
|
oauthClientFile: join(configDir, "oauth-client.json"),
|
|
152
|
+
telemetryStateFile: join(configDir, "telemetry.json"),
|
|
152
153
|
cacheDbFile: join(cacheDir, "cache.db")
|
|
153
154
|
};
|
|
154
155
|
}
|
|
@@ -762,6 +763,52 @@ async function createGoogleAccessTokenClient() {
|
|
|
762
763
|
};
|
|
763
764
|
}
|
|
764
765
|
//#endregion
|
|
766
|
+
//#region packages/core/dist/storage/sqlite.js
|
|
767
|
+
function withoutDriverMetadata(row) {
|
|
768
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return row;
|
|
769
|
+
const { _metadata: _driverMetadata, ...data } = row;
|
|
770
|
+
return data;
|
|
771
|
+
}
|
|
772
|
+
var Statement = class {
|
|
773
|
+
statement;
|
|
774
|
+
constructor(statement) {
|
|
775
|
+
this.statement = statement;
|
|
776
|
+
}
|
|
777
|
+
run(...params) {
|
|
778
|
+
return this.statement.run(...params);
|
|
779
|
+
}
|
|
780
|
+
get(...params) {
|
|
781
|
+
return withoutDriverMetadata(this.statement.get(...params));
|
|
782
|
+
}
|
|
783
|
+
all(...params) {
|
|
784
|
+
return this.statement.all(...params).map(withoutDriverMetadata);
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
var SqliteDatabase = class {
|
|
788
|
+
database;
|
|
789
|
+
constructor(filename) {
|
|
790
|
+
this.database = new Libsql(filename);
|
|
791
|
+
}
|
|
792
|
+
prepare(source) {
|
|
793
|
+
return new Statement(this.database.prepare(source));
|
|
794
|
+
}
|
|
795
|
+
transaction(fn) {
|
|
796
|
+
return this.database.transaction(fn);
|
|
797
|
+
}
|
|
798
|
+
exec(source) {
|
|
799
|
+
this.database.exec(source);
|
|
800
|
+
return this;
|
|
801
|
+
}
|
|
802
|
+
pragma(source) {
|
|
803
|
+
const result = this.database.pragma(source);
|
|
804
|
+
return Array.isArray(result) ? result.map(withoutDriverMetadata) : withoutDriverMetadata(result);
|
|
805
|
+
}
|
|
806
|
+
close() {
|
|
807
|
+
this.database.close();
|
|
808
|
+
return this;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
//#endregion
|
|
765
812
|
//#region packages/core/dist/storage/database.js
|
|
766
813
|
const CREATE_SQL = `
|
|
767
814
|
CREATE TABLE IF NOT EXISTS sites (
|
|
@@ -1045,10 +1092,14 @@ function getDb() {
|
|
|
1045
1092
|
recursive: true,
|
|
1046
1093
|
mode: 448
|
|
1047
1094
|
});
|
|
1048
|
-
db = new
|
|
1095
|
+
db = new SqliteDatabase(path);
|
|
1049
1096
|
initDb(db);
|
|
1050
1097
|
return db;
|
|
1051
1098
|
}
|
|
1099
|
+
function checkDatabaseReadiness() {
|
|
1100
|
+
if (getDb().prepare("SELECT 1 AS ready").get()?.ready !== 1) throw new Error("The local SQLite database did not return a readiness row.");
|
|
1101
|
+
return { dbPath: getSeoCliPaths().cacheDbFile };
|
|
1102
|
+
}
|
|
1052
1103
|
function hashKey(parts) {
|
|
1053
1104
|
const json = JSON.stringify(parts);
|
|
1054
1105
|
return Buffer.from(json).toString("base64url");
|
|
@@ -25566,7 +25617,7 @@ async function updatePostmortemWorkflow(input, dependencies = {}) {
|
|
|
25566
25617
|
//#endregion
|
|
25567
25618
|
//#region packages/core/dist/doctor.js
|
|
25568
25619
|
const REQUIRED_SCOPES = ["https://www.googleapis.com/auth/webmasters.readonly", "https://www.googleapis.com/auth/analytics.readonly"];
|
|
25569
|
-
async function runDoctor() {
|
|
25620
|
+
async function runDoctor(dependencies = {}) {
|
|
25570
25621
|
const paths = getSeoCliPaths();
|
|
25571
25622
|
const config = readConfig();
|
|
25572
25623
|
const authMode = getAuthModeStatus();
|
|
@@ -25575,6 +25626,23 @@ async function runDoctor() {
|
|
|
25575
25626
|
const oauthClient = readOauthClient();
|
|
25576
25627
|
const tokenClientConfigured = tokens ? Boolean(getClientConfig(tokens.client_source)) : void 0;
|
|
25577
25628
|
const checks = [];
|
|
25629
|
+
try {
|
|
25630
|
+
const database = (dependencies.checkDatabase ?? checkDatabaseReadiness)();
|
|
25631
|
+
checks.push({
|
|
25632
|
+
id: "local-database",
|
|
25633
|
+
label: "Local database",
|
|
25634
|
+
status: "pass",
|
|
25635
|
+
detail: database.dbPath
|
|
25636
|
+
});
|
|
25637
|
+
} catch (error) {
|
|
25638
|
+
checks.push({
|
|
25639
|
+
id: "local-database",
|
|
25640
|
+
label: "Local database",
|
|
25641
|
+
status: "fail",
|
|
25642
|
+
detail: error instanceof Error ? error.message : "The local database could not be opened.",
|
|
25643
|
+
fix: "Upgrade or reinstall `seo`. If this still fails, report it at https://github.com/iannuttall/seo/issues."
|
|
25644
|
+
});
|
|
25645
|
+
}
|
|
25578
25646
|
checks.push({
|
|
25579
25647
|
id: "config-dir",
|
|
25580
25648
|
label: "Config directory",
|
|
@@ -25882,6 +25950,317 @@ new LocalCredentialsProvider();
|
|
|
25882
25950
|
//#endregion
|
|
25883
25951
|
//#region packages/core/dist/version.js
|
|
25884
25952
|
const SEO_VERSION = version;
|
|
25953
|
+
const TELEMETRY_TIMEOUT_MS = 2e3;
|
|
25954
|
+
const TELEMETRY_REPORTS = [
|
|
25955
|
+
"affected-urls",
|
|
25956
|
+
"agent-readiness",
|
|
25957
|
+
"ai-readiness",
|
|
25958
|
+
"ai-referrals",
|
|
25959
|
+
"ai-search-scorecard",
|
|
25960
|
+
"audit-page",
|
|
25961
|
+
"audit-urls",
|
|
25962
|
+
"cannibalisation",
|
|
25963
|
+
"community-intent",
|
|
25964
|
+
"compare-crawls",
|
|
25965
|
+
"content-optimization",
|
|
25966
|
+
"crawl-diff",
|
|
25967
|
+
"site-crawl",
|
|
25968
|
+
"ctr-underperformers",
|
|
25969
|
+
"decaying-pages",
|
|
25970
|
+
"setup-check",
|
|
25971
|
+
"entity-readiness",
|
|
25972
|
+
"explain-crawl-issue",
|
|
25973
|
+
"geo-gaps",
|
|
25974
|
+
"crawl-report",
|
|
25975
|
+
"index-coverage",
|
|
25976
|
+
"index-coverage-plan",
|
|
25977
|
+
"index-monitor",
|
|
25978
|
+
"index-watch",
|
|
25979
|
+
"internal-links",
|
|
25980
|
+
"link-recovery",
|
|
25981
|
+
"crawl-history",
|
|
25982
|
+
"crawler-rules",
|
|
25983
|
+
"llms-txt-audit",
|
|
25984
|
+
"generate-llms-txt",
|
|
25985
|
+
"measure-change",
|
|
25986
|
+
"monthly-report",
|
|
25987
|
+
"okf-build",
|
|
25988
|
+
"okf-validate",
|
|
25989
|
+
"page-opportunities",
|
|
25990
|
+
"performance-audit",
|
|
25991
|
+
"pseo-audit",
|
|
25992
|
+
"query-clusters",
|
|
25993
|
+
"quick-wins",
|
|
25994
|
+
"redirect-trace",
|
|
25995
|
+
"narrative-report",
|
|
25996
|
+
"second-page",
|
|
25997
|
+
"segment-impact",
|
|
25998
|
+
"striking-distance",
|
|
25999
|
+
"seo-to-ai-query",
|
|
26000
|
+
"top-fixes",
|
|
26001
|
+
"traffic-anomaly",
|
|
26002
|
+
"update-correlation",
|
|
26003
|
+
"search-performance-overview",
|
|
26004
|
+
"monthly-action-plan",
|
|
26005
|
+
"refresh-priorities",
|
|
26006
|
+
"technical-watch",
|
|
26007
|
+
"update-postmortem"
|
|
26008
|
+
];
|
|
26009
|
+
const RETENTION_MILESTONES = [
|
|
26010
|
+
{
|
|
26011
|
+
days: 1,
|
|
26012
|
+
event: "active_d1"
|
|
26013
|
+
},
|
|
26014
|
+
{
|
|
26015
|
+
days: 7,
|
|
26016
|
+
event: "active_d7"
|
|
26017
|
+
},
|
|
26018
|
+
{
|
|
26019
|
+
days: 30,
|
|
26020
|
+
event: "active_d30"
|
|
26021
|
+
}
|
|
26022
|
+
];
|
|
26023
|
+
const CI_ENVIRONMENT_KEYS = [
|
|
26024
|
+
"GITHUB_ACTIONS",
|
|
26025
|
+
"GITLAB_CI",
|
|
26026
|
+
"CIRCLECI",
|
|
26027
|
+
"TRAVIS",
|
|
26028
|
+
"BUILDKITE",
|
|
26029
|
+
"JENKINS_URL",
|
|
26030
|
+
"TEAMCITY_VERSION",
|
|
26031
|
+
"TF_BUILD",
|
|
26032
|
+
"BITBUCKET_BUILD_NUMBER",
|
|
26033
|
+
"CODEBUILD_BUILD_ID"
|
|
26034
|
+
];
|
|
26035
|
+
const TELEMETRY_NOTICE = `SEO Skill collects anonymous usage data to improve the tool (event name,
|
|
26036
|
+
report id, version, agent, OS, architecture, Node major, and install week;
|
|
26037
|
+
never URLs, report data, or identifiers of any kind).
|
|
26038
|
+
Docs: https://seoskill.dev/telemetry
|
|
26039
|
+
Disable: seo telemetry disable (or set DO_NOT_TRACK=1)\n`;
|
|
26040
|
+
function optionEnv(options) {
|
|
26041
|
+
return options.env ?? process.env;
|
|
26042
|
+
}
|
|
26043
|
+
function optionNow(options) {
|
|
26044
|
+
return options.now ?? /* @__PURE__ */ new Date();
|
|
26045
|
+
}
|
|
26046
|
+
function optionStateFile(options) {
|
|
26047
|
+
return options.stateFile ?? getSeoCliPaths().telemetryStateFile;
|
|
26048
|
+
}
|
|
26049
|
+
function isEnvEnabled(value) {
|
|
26050
|
+
return value === "1" || value?.toLowerCase() === "true";
|
|
26051
|
+
}
|
|
26052
|
+
function isTelemetryCi(env = process.env) {
|
|
26053
|
+
return env.NODE_ENV === "test" || Boolean(env.NODE_TEST_CONTEXT) || isEnvEnabled(env.CI) || CI_ENVIRONMENT_KEYS.some((key) => Boolean(env[key]));
|
|
26054
|
+
}
|
|
26055
|
+
function environmentDisableReason(env) {
|
|
26056
|
+
if (isTelemetryCi(env)) return "ci";
|
|
26057
|
+
if (isEnvEnabled(env.DO_NOT_TRACK)) return "do_not_track";
|
|
26058
|
+
if (isEnvEnabled(env.SEOSKILL_TELEMETRY_DISABLED) || isEnvEnabled(env.SEO_TELEMETRY_DISABLED)) return "environment";
|
|
26059
|
+
}
|
|
26060
|
+
function isTelemetryMilestone(value) {
|
|
26061
|
+
return typeof value === "string" && [
|
|
26062
|
+
"first_run",
|
|
26063
|
+
"setup_complete",
|
|
26064
|
+
"first_audit_complete",
|
|
26065
|
+
"active_d1",
|
|
26066
|
+
"active_d7",
|
|
26067
|
+
"active_d30"
|
|
26068
|
+
].includes(value);
|
|
26069
|
+
}
|
|
26070
|
+
function isTelemetryReport(value) {
|
|
26071
|
+
return typeof value === "string" && TELEMETRY_REPORTS.includes(value);
|
|
26072
|
+
}
|
|
26073
|
+
function isValidCohort(value) {
|
|
26074
|
+
return typeof value === "string" && /^\d{4}-W(?:0[1-9]|[1-4]\d|5[0-3])$/.test(value);
|
|
26075
|
+
}
|
|
26076
|
+
function isTelemetryState(value) {
|
|
26077
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
26078
|
+
const state = value;
|
|
26079
|
+
return typeof state.telemetryEnabled === "boolean" && typeof state.firstRunAt === "string" && Number.isFinite(Date.parse(state.firstRunAt)) && isValidCohort(state.cohort) && Array.isArray(state.sentMilestones) && state.sentMilestones.every(isTelemetryMilestone);
|
|
26080
|
+
}
|
|
26081
|
+
function readState(path) {
|
|
26082
|
+
if (!existsSync(path)) return { kind: "missing" };
|
|
26083
|
+
const value = readJsonFile(path);
|
|
26084
|
+
return isTelemetryState(value) ? {
|
|
26085
|
+
kind: "valid",
|
|
26086
|
+
state: value
|
|
26087
|
+
} : { kind: "invalid" };
|
|
26088
|
+
}
|
|
26089
|
+
function writeState(path, state) {
|
|
26090
|
+
writeJsonAtomic(path, state, 384);
|
|
26091
|
+
}
|
|
26092
|
+
function isoWeek(date) {
|
|
26093
|
+
const utc = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
|
26094
|
+
const day = utc.getUTCDay() || 7;
|
|
26095
|
+
utc.setUTCDate(utc.getUTCDate() + 4 - day);
|
|
26096
|
+
const yearStart = new Date(Date.UTC(utc.getUTCFullYear(), 0, 1));
|
|
26097
|
+
const week = Math.ceil(((utc.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
26098
|
+
return `${utc.getUTCFullYear()}-W${String(week).padStart(2, "0")}`;
|
|
26099
|
+
}
|
|
26100
|
+
function newState(options, enabled = true) {
|
|
26101
|
+
const now = optionNow(options);
|
|
26102
|
+
return {
|
|
26103
|
+
telemetryEnabled: enabled,
|
|
26104
|
+
firstRunAt: now.toISOString(),
|
|
26105
|
+
cohort: isoWeek(now),
|
|
26106
|
+
sentMilestones: []
|
|
26107
|
+
};
|
|
26108
|
+
}
|
|
26109
|
+
function detectTelemetryAgent(input = {}) {
|
|
26110
|
+
const env = input.env ?? process.env;
|
|
26111
|
+
const clientName = input.clientName?.toLowerCase() ?? "";
|
|
26112
|
+
if (clientName.includes("claude")) return "claude-code";
|
|
26113
|
+
if (clientName.includes("cursor")) return "cursor";
|
|
26114
|
+
if (clientName.includes("codex")) return "codex";
|
|
26115
|
+
if (input.clientName) return "unknown";
|
|
26116
|
+
if (env.CLAUDECODE || env.CLAUDE_CODE) return "claude-code";
|
|
26117
|
+
if (env.CURSOR_AGENT || env.CURSOR_TRACE_ID || env.CURSOR_SESSION_ID) return "cursor";
|
|
26118
|
+
if (env.CODEX_HOME || env.CODEX_THREAD_ID || env.CODEX_SANDBOX) return "codex";
|
|
26119
|
+
return "cli";
|
|
26120
|
+
}
|
|
26121
|
+
function telemetryAgent(options) {
|
|
26122
|
+
return options.agent ?? detectTelemetryAgent({
|
|
26123
|
+
clientName: options.clientName,
|
|
26124
|
+
env: optionEnv(options)
|
|
26125
|
+
});
|
|
26126
|
+
}
|
|
26127
|
+
function nodeMajor(version) {
|
|
26128
|
+
return version.replace(/^v/, "").split(".")[0] || "unknown";
|
|
26129
|
+
}
|
|
26130
|
+
function buildTelemetryPayload(event, state, fields = {}, options = {}) {
|
|
26131
|
+
return {
|
|
26132
|
+
event,
|
|
26133
|
+
version: options.version ?? SEO_VERSION,
|
|
26134
|
+
agent: telemetryAgent(options),
|
|
26135
|
+
os: options.processPlatform ?? platform(),
|
|
26136
|
+
arch: options.processArch ?? arch(),
|
|
26137
|
+
node: nodeMajor(options.nodeVersion ?? process.version),
|
|
26138
|
+
cohort: state.cohort,
|
|
26139
|
+
schema: 1,
|
|
26140
|
+
...fields.errorCategory ? { errorCategory: fields.errorCategory } : {},
|
|
26141
|
+
...fields.report ? { report: fields.report } : {}
|
|
26142
|
+
};
|
|
26143
|
+
}
|
|
26144
|
+
function dispatchTelemetry(payload, options) {
|
|
26145
|
+
const fetchTelemetry = options.fetch ?? globalThis.fetch;
|
|
26146
|
+
try {
|
|
26147
|
+
fetchTelemetry(options.endpoint ?? "https://seoskill.dev/api/t", {
|
|
26148
|
+
method: "POST",
|
|
26149
|
+
headers: { "content-type": "application/json" },
|
|
26150
|
+
body: JSON.stringify(payload),
|
|
26151
|
+
signal: AbortSignal.timeout(TELEMETRY_TIMEOUT_MS)
|
|
26152
|
+
}).catch(() => void 0);
|
|
26153
|
+
} catch {}
|
|
26154
|
+
}
|
|
26155
|
+
function statusFromState(options) {
|
|
26156
|
+
const stateFile = optionStateFile(options);
|
|
26157
|
+
const envReason = environmentDisableReason(optionEnv(options));
|
|
26158
|
+
const stored = readState(stateFile);
|
|
26159
|
+
if (envReason) return {
|
|
26160
|
+
enabled: false,
|
|
26161
|
+
reason: envReason,
|
|
26162
|
+
stateFile,
|
|
26163
|
+
...stored.kind === "valid" ? { state: stored.state } : {}
|
|
26164
|
+
};
|
|
26165
|
+
if (stored.kind === "invalid") return {
|
|
26166
|
+
enabled: false,
|
|
26167
|
+
reason: "invalid_state",
|
|
26168
|
+
stateFile
|
|
26169
|
+
};
|
|
26170
|
+
if (stored.kind === "valid" && !stored.state.telemetryEnabled) return {
|
|
26171
|
+
enabled: false,
|
|
26172
|
+
reason: "local_setting",
|
|
26173
|
+
stateFile,
|
|
26174
|
+
state: stored.state
|
|
26175
|
+
};
|
|
26176
|
+
return {
|
|
26177
|
+
enabled: true,
|
|
26178
|
+
stateFile,
|
|
26179
|
+
...stored.kind === "valid" ? { state: stored.state } : {}
|
|
26180
|
+
};
|
|
26181
|
+
}
|
|
26182
|
+
function enabledState(options) {
|
|
26183
|
+
const status = statusFromState(options);
|
|
26184
|
+
return status.enabled ? status.state : void 0;
|
|
26185
|
+
}
|
|
26186
|
+
function initializeTelemetry(options = {}) {
|
|
26187
|
+
const initialStatus = statusFromState(options);
|
|
26188
|
+
if (!initialStatus.enabled) return initialStatus;
|
|
26189
|
+
const stateFile = initialStatus.stateFile;
|
|
26190
|
+
const state = initialStatus.state ?? newState(options);
|
|
26191
|
+
const events = [];
|
|
26192
|
+
if (!state.sentMilestones.includes("first_run")) {
|
|
26193
|
+
try {
|
|
26194
|
+
(options.notice ?? ((message) => process.stderr.write(message)))(TELEMETRY_NOTICE);
|
|
26195
|
+
} catch {
|
|
26196
|
+
return {
|
|
26197
|
+
enabled: false,
|
|
26198
|
+
reason: "invalid_state",
|
|
26199
|
+
stateFile
|
|
26200
|
+
};
|
|
26201
|
+
}
|
|
26202
|
+
state.sentMilestones.push("first_run");
|
|
26203
|
+
events.push("first_run");
|
|
26204
|
+
}
|
|
26205
|
+
const daysSinceFirstRun = (optionNow(options).getTime() - Date.parse(state.firstRunAt)) / 864e5;
|
|
26206
|
+
for (const milestone of RETENTION_MILESTONES) if (daysSinceFirstRun >= milestone.days && !state.sentMilestones.includes(milestone.event)) {
|
|
26207
|
+
state.sentMilestones.push(milestone.event);
|
|
26208
|
+
events.push(milestone.event);
|
|
26209
|
+
}
|
|
26210
|
+
try {
|
|
26211
|
+
writeState(stateFile, state);
|
|
26212
|
+
} catch {
|
|
26213
|
+
return {
|
|
26214
|
+
enabled: false,
|
|
26215
|
+
reason: "invalid_state",
|
|
26216
|
+
stateFile
|
|
26217
|
+
};
|
|
26218
|
+
}
|
|
26219
|
+
for (const event of events) dispatchTelemetry(buildTelemetryPayload(event, state, {}, options), options);
|
|
26220
|
+
return {
|
|
26221
|
+
enabled: true,
|
|
26222
|
+
stateFile,
|
|
26223
|
+
state
|
|
26224
|
+
};
|
|
26225
|
+
}
|
|
26226
|
+
function sendEvent(event, fields, options) {
|
|
26227
|
+
const state = enabledState(options);
|
|
26228
|
+
if (!state) return;
|
|
26229
|
+
dispatchTelemetry(buildTelemetryPayload(event, state, fields, options), options);
|
|
26230
|
+
}
|
|
26231
|
+
function trackTelemetryReportStart(report, options = {}) {
|
|
26232
|
+
if (!isTelemetryReport(report)) return;
|
|
26233
|
+
sendEvent("audit_start", { report }, options);
|
|
26234
|
+
}
|
|
26235
|
+
function trackTelemetryReportComplete(report, options = {}) {
|
|
26236
|
+
if (!isTelemetryReport(report)) return;
|
|
26237
|
+
const state = enabledState(options);
|
|
26238
|
+
if (!state) return;
|
|
26239
|
+
dispatchTelemetry(buildTelemetryPayload("audit_complete", state, { report }, options), options);
|
|
26240
|
+
if (state.sentMilestones.includes("first_audit_complete")) return;
|
|
26241
|
+
state.sentMilestones.push("first_audit_complete");
|
|
26242
|
+
try {
|
|
26243
|
+
writeState(optionStateFile(options), state);
|
|
26244
|
+
} catch {
|
|
26245
|
+
return;
|
|
26246
|
+
}
|
|
26247
|
+
dispatchTelemetry(buildTelemetryPayload("first_audit_complete", state, { report }, options), options);
|
|
26248
|
+
}
|
|
26249
|
+
function trackTelemetryReportFailed(report, errorCategory, options = {}) {
|
|
26250
|
+
if (!isTelemetryReport(report)) return;
|
|
26251
|
+
sendEvent("audit_failed", {
|
|
26252
|
+
errorCategory,
|
|
26253
|
+
report
|
|
26254
|
+
}, options);
|
|
26255
|
+
}
|
|
26256
|
+
function telemetryErrorCategory(error) {
|
|
26257
|
+
if (error instanceof Error && error.name === "AbortError") return "crawl_timeout";
|
|
26258
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : void 0;
|
|
26259
|
+
if (code === "ACCESS_DENIED" || code === "AUTH_CONFIG_REQUIRED" || code === "AUTH_EXPIRED" || code === "AUTH_REQUIRED") return "auth";
|
|
26260
|
+
if (code === "INVALID_INPUT" || code === "PROPERTY_NOT_FOUND") return "config";
|
|
26261
|
+
if (code === "OPTIONAL_PROVIDER_UNAVAILABLE" || code === "PROVIDER_UNAVAILABLE" || code === "RATE_LIMITED") return "network";
|
|
26262
|
+
return "unknown";
|
|
26263
|
+
}
|
|
25885
26264
|
//#endregion
|
|
25886
26265
|
//#region packages/mcp/src/input-schemas.ts
|
|
25887
26266
|
const calendarDateSchema = z$1.iso.date("Use a YYYY-MM-DD date.");
|
|
@@ -26790,7 +27169,7 @@ function registerCrawlerTools(server) {
|
|
|
26790
27169
|
//#region packages/mcp/src/diagnosis-tools.ts
|
|
26791
27170
|
function registerDiagnosisTools(server, dependencies = {}) {
|
|
26792
27171
|
server.registerTool("seo_doctor", {
|
|
26793
|
-
description: "Check local auth, scopes, config, and defaults",
|
|
27172
|
+
description: "Check the local database, auth, scopes, config, and defaults",
|
|
26794
27173
|
inputSchema: {}
|
|
26795
27174
|
}, async () => {
|
|
26796
27175
|
try {
|
|
@@ -30053,7 +30432,8 @@ async function runReport(id, params = {}) {
|
|
|
30053
30432
|
//#endregion
|
|
30054
30433
|
//#region packages/mcp/src/discovery-tools.ts
|
|
30055
30434
|
const openOutputSchema = z$1.looseObject({});
|
|
30056
|
-
|
|
30435
|
+
const reportIds = new Set(listReports().map((report) => report.id));
|
|
30436
|
+
function registerDiscoveryTools(server, options = {}) {
|
|
30057
30437
|
server.registerTool("seo_list_reports", {
|
|
30058
30438
|
description: "List compact SEO report ids, names, and purposes, optionally by category",
|
|
30059
30439
|
inputSchema: { category: z$1.enum(REPORT_CATEGORIES).optional() },
|
|
@@ -30097,17 +30477,31 @@ function registerDiscoveryTools(server) {
|
|
|
30097
30477
|
outputSchema: openOutputSchema,
|
|
30098
30478
|
annotations: { destructiveHint: false }
|
|
30099
30479
|
}, async ({ id, params }) => {
|
|
30100
|
-
|
|
30480
|
+
const telemetry = reportIds.has(id) ? options.telemetry?.() : void 0;
|
|
30481
|
+
if (telemetry) trackTelemetryReportStart(id, telemetry);
|
|
30482
|
+
try {
|
|
30483
|
+
const result = await runReport(id, params);
|
|
30484
|
+
if (telemetry) if (result.isError) trackTelemetryReportFailed(id, telemetryErrorCategory(result.structuredContent?.error), telemetry);
|
|
30485
|
+
else trackTelemetryReportComplete(id, telemetry);
|
|
30486
|
+
return result;
|
|
30487
|
+
} catch (error) {
|
|
30488
|
+
if (telemetry) trackTelemetryReportFailed(id, telemetryErrorCategory(error), telemetry);
|
|
30489
|
+
throw error;
|
|
30490
|
+
}
|
|
30101
30491
|
});
|
|
30102
30492
|
}
|
|
30103
30493
|
//#endregion
|
|
30104
30494
|
//#region packages/mcp/src/index.ts
|
|
30105
|
-
function createServer() {
|
|
30495
|
+
function createServer(options = {}) {
|
|
30106
30496
|
const server = new McpServer({
|
|
30107
30497
|
name: "seo",
|
|
30108
30498
|
version: SEO_VERSION
|
|
30109
30499
|
}, { capabilities: { logging: {} } });
|
|
30110
|
-
|
|
30500
|
+
const telemetryOptions = () => ({ clientName: server.server.getClientVersion()?.name });
|
|
30501
|
+
registerDiscoveryTools(server, { telemetry: options.telemetry === false ? void 0 : telemetryOptions });
|
|
30502
|
+
if (options.telemetry !== false) server.server.oninitialized = () => {
|
|
30503
|
+
initializeTelemetry(telemetryOptions());
|
|
30504
|
+
};
|
|
30111
30505
|
return server;
|
|
30112
30506
|
}
|
|
30113
30507
|
async function startMcpServer(opts = {}) {
|