seo 0.2.5 → 0.2.6

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/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(): McpServer;
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,8 +1,8 @@
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-BF_XgK7-.js";
2
+ import { n as load, t as version } from "./package-B9-D646K.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";
@@ -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
  }
@@ -25882,6 +25883,317 @@ new LocalCredentialsProvider();
25882
25883
  //#endregion
25883
25884
  //#region packages/core/dist/version.js
25884
25885
  const SEO_VERSION = version;
25886
+ const TELEMETRY_TIMEOUT_MS = 2e3;
25887
+ const TELEMETRY_REPORTS = [
25888
+ "affected-urls",
25889
+ "agent-readiness",
25890
+ "ai-readiness",
25891
+ "ai-referrals",
25892
+ "ai-search-scorecard",
25893
+ "audit-page",
25894
+ "audit-urls",
25895
+ "cannibalisation",
25896
+ "community-intent",
25897
+ "compare-crawls",
25898
+ "content-optimization",
25899
+ "crawl-diff",
25900
+ "site-crawl",
25901
+ "ctr-underperformers",
25902
+ "decaying-pages",
25903
+ "setup-check",
25904
+ "entity-readiness",
25905
+ "explain-crawl-issue",
25906
+ "geo-gaps",
25907
+ "crawl-report",
25908
+ "index-coverage",
25909
+ "index-coverage-plan",
25910
+ "index-monitor",
25911
+ "index-watch",
25912
+ "internal-links",
25913
+ "link-recovery",
25914
+ "crawl-history",
25915
+ "crawler-rules",
25916
+ "llms-txt-audit",
25917
+ "generate-llms-txt",
25918
+ "measure-change",
25919
+ "monthly-report",
25920
+ "okf-build",
25921
+ "okf-validate",
25922
+ "page-opportunities",
25923
+ "performance-audit",
25924
+ "pseo-audit",
25925
+ "query-clusters",
25926
+ "quick-wins",
25927
+ "redirect-trace",
25928
+ "narrative-report",
25929
+ "second-page",
25930
+ "segment-impact",
25931
+ "striking-distance",
25932
+ "seo-to-ai-query",
25933
+ "top-fixes",
25934
+ "traffic-anomaly",
25935
+ "update-correlation",
25936
+ "search-performance-overview",
25937
+ "monthly-action-plan",
25938
+ "refresh-priorities",
25939
+ "technical-watch",
25940
+ "update-postmortem"
25941
+ ];
25942
+ const RETENTION_MILESTONES = [
25943
+ {
25944
+ days: 1,
25945
+ event: "active_d1"
25946
+ },
25947
+ {
25948
+ days: 7,
25949
+ event: "active_d7"
25950
+ },
25951
+ {
25952
+ days: 30,
25953
+ event: "active_d30"
25954
+ }
25955
+ ];
25956
+ const CI_ENVIRONMENT_KEYS = [
25957
+ "GITHUB_ACTIONS",
25958
+ "GITLAB_CI",
25959
+ "CIRCLECI",
25960
+ "TRAVIS",
25961
+ "BUILDKITE",
25962
+ "JENKINS_URL",
25963
+ "TEAMCITY_VERSION",
25964
+ "TF_BUILD",
25965
+ "BITBUCKET_BUILD_NUMBER",
25966
+ "CODEBUILD_BUILD_ID"
25967
+ ];
25968
+ const TELEMETRY_NOTICE = `SEO Skill collects anonymous usage data to improve the tool (event name,
25969
+ report id, version, agent, OS, architecture, Node major, and install week;
25970
+ never URLs, report data, or identifiers of any kind).
25971
+ Docs: https://seoskill.dev/telemetry
25972
+ Disable: seo telemetry disable (or set DO_NOT_TRACK=1)\n`;
25973
+ function optionEnv(options) {
25974
+ return options.env ?? process.env;
25975
+ }
25976
+ function optionNow(options) {
25977
+ return options.now ?? /* @__PURE__ */ new Date();
25978
+ }
25979
+ function optionStateFile(options) {
25980
+ return options.stateFile ?? getSeoCliPaths().telemetryStateFile;
25981
+ }
25982
+ function isEnvEnabled(value) {
25983
+ return value === "1" || value?.toLowerCase() === "true";
25984
+ }
25985
+ function isTelemetryCi(env = process.env) {
25986
+ return env.NODE_ENV === "test" || Boolean(env.NODE_TEST_CONTEXT) || isEnvEnabled(env.CI) || CI_ENVIRONMENT_KEYS.some((key) => Boolean(env[key]));
25987
+ }
25988
+ function environmentDisableReason(env) {
25989
+ if (isTelemetryCi(env)) return "ci";
25990
+ if (isEnvEnabled(env.DO_NOT_TRACK)) return "do_not_track";
25991
+ if (isEnvEnabled(env.SEOSKILL_TELEMETRY_DISABLED) || isEnvEnabled(env.SEO_TELEMETRY_DISABLED)) return "environment";
25992
+ }
25993
+ function isTelemetryMilestone(value) {
25994
+ return typeof value === "string" && [
25995
+ "first_run",
25996
+ "setup_complete",
25997
+ "first_audit_complete",
25998
+ "active_d1",
25999
+ "active_d7",
26000
+ "active_d30"
26001
+ ].includes(value);
26002
+ }
26003
+ function isTelemetryReport(value) {
26004
+ return typeof value === "string" && TELEMETRY_REPORTS.includes(value);
26005
+ }
26006
+ function isValidCohort(value) {
26007
+ return typeof value === "string" && /^\d{4}-W(?:0[1-9]|[1-4]\d|5[0-3])$/.test(value);
26008
+ }
26009
+ function isTelemetryState(value) {
26010
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
26011
+ const state = value;
26012
+ 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);
26013
+ }
26014
+ function readState(path) {
26015
+ if (!existsSync(path)) return { kind: "missing" };
26016
+ const value = readJsonFile(path);
26017
+ return isTelemetryState(value) ? {
26018
+ kind: "valid",
26019
+ state: value
26020
+ } : { kind: "invalid" };
26021
+ }
26022
+ function writeState(path, state) {
26023
+ writeJsonAtomic(path, state, 384);
26024
+ }
26025
+ function isoWeek(date) {
26026
+ const utc = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
26027
+ const day = utc.getUTCDay() || 7;
26028
+ utc.setUTCDate(utc.getUTCDate() + 4 - day);
26029
+ const yearStart = new Date(Date.UTC(utc.getUTCFullYear(), 0, 1));
26030
+ const week = Math.ceil(((utc.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
26031
+ return `${utc.getUTCFullYear()}-W${String(week).padStart(2, "0")}`;
26032
+ }
26033
+ function newState(options, enabled = true) {
26034
+ const now = optionNow(options);
26035
+ return {
26036
+ telemetryEnabled: enabled,
26037
+ firstRunAt: now.toISOString(),
26038
+ cohort: isoWeek(now),
26039
+ sentMilestones: []
26040
+ };
26041
+ }
26042
+ function detectTelemetryAgent(input = {}) {
26043
+ const env = input.env ?? process.env;
26044
+ const clientName = input.clientName?.toLowerCase() ?? "";
26045
+ if (clientName.includes("claude")) return "claude-code";
26046
+ if (clientName.includes("cursor")) return "cursor";
26047
+ if (clientName.includes("codex")) return "codex";
26048
+ if (input.clientName) return "unknown";
26049
+ if (env.CLAUDECODE || env.CLAUDE_CODE) return "claude-code";
26050
+ if (env.CURSOR_AGENT || env.CURSOR_TRACE_ID || env.CURSOR_SESSION_ID) return "cursor";
26051
+ if (env.CODEX_HOME || env.CODEX_THREAD_ID || env.CODEX_SANDBOX) return "codex";
26052
+ return "cli";
26053
+ }
26054
+ function telemetryAgent(options) {
26055
+ return options.agent ?? detectTelemetryAgent({
26056
+ clientName: options.clientName,
26057
+ env: optionEnv(options)
26058
+ });
26059
+ }
26060
+ function nodeMajor(version) {
26061
+ return version.replace(/^v/, "").split(".")[0] || "unknown";
26062
+ }
26063
+ function buildTelemetryPayload(event, state, fields = {}, options = {}) {
26064
+ return {
26065
+ event,
26066
+ version: options.version ?? SEO_VERSION,
26067
+ agent: telemetryAgent(options),
26068
+ os: options.processPlatform ?? platform(),
26069
+ arch: options.processArch ?? arch(),
26070
+ node: nodeMajor(options.nodeVersion ?? process.version),
26071
+ cohort: state.cohort,
26072
+ schema: 1,
26073
+ ...fields.errorCategory ? { errorCategory: fields.errorCategory } : {},
26074
+ ...fields.report ? { report: fields.report } : {}
26075
+ };
26076
+ }
26077
+ function dispatchTelemetry(payload, options) {
26078
+ const fetchTelemetry = options.fetch ?? globalThis.fetch;
26079
+ try {
26080
+ fetchTelemetry(options.endpoint ?? "https://seoskill.dev/api/t", {
26081
+ method: "POST",
26082
+ headers: { "content-type": "application/json" },
26083
+ body: JSON.stringify(payload),
26084
+ signal: AbortSignal.timeout(TELEMETRY_TIMEOUT_MS)
26085
+ }).catch(() => void 0);
26086
+ } catch {}
26087
+ }
26088
+ function statusFromState(options) {
26089
+ const stateFile = optionStateFile(options);
26090
+ const envReason = environmentDisableReason(optionEnv(options));
26091
+ const stored = readState(stateFile);
26092
+ if (envReason) return {
26093
+ enabled: false,
26094
+ reason: envReason,
26095
+ stateFile,
26096
+ ...stored.kind === "valid" ? { state: stored.state } : {}
26097
+ };
26098
+ if (stored.kind === "invalid") return {
26099
+ enabled: false,
26100
+ reason: "invalid_state",
26101
+ stateFile
26102
+ };
26103
+ if (stored.kind === "valid" && !stored.state.telemetryEnabled) return {
26104
+ enabled: false,
26105
+ reason: "local_setting",
26106
+ stateFile,
26107
+ state: stored.state
26108
+ };
26109
+ return {
26110
+ enabled: true,
26111
+ stateFile,
26112
+ ...stored.kind === "valid" ? { state: stored.state } : {}
26113
+ };
26114
+ }
26115
+ function enabledState(options) {
26116
+ const status = statusFromState(options);
26117
+ return status.enabled ? status.state : void 0;
26118
+ }
26119
+ function initializeTelemetry(options = {}) {
26120
+ const initialStatus = statusFromState(options);
26121
+ if (!initialStatus.enabled) return initialStatus;
26122
+ const stateFile = initialStatus.stateFile;
26123
+ const state = initialStatus.state ?? newState(options);
26124
+ const events = [];
26125
+ if (!state.sentMilestones.includes("first_run")) {
26126
+ try {
26127
+ (options.notice ?? ((message) => process.stderr.write(message)))(TELEMETRY_NOTICE);
26128
+ } catch {
26129
+ return {
26130
+ enabled: false,
26131
+ reason: "invalid_state",
26132
+ stateFile
26133
+ };
26134
+ }
26135
+ state.sentMilestones.push("first_run");
26136
+ events.push("first_run");
26137
+ }
26138
+ const daysSinceFirstRun = (optionNow(options).getTime() - Date.parse(state.firstRunAt)) / 864e5;
26139
+ for (const milestone of RETENTION_MILESTONES) if (daysSinceFirstRun >= milestone.days && !state.sentMilestones.includes(milestone.event)) {
26140
+ state.sentMilestones.push(milestone.event);
26141
+ events.push(milestone.event);
26142
+ }
26143
+ try {
26144
+ writeState(stateFile, state);
26145
+ } catch {
26146
+ return {
26147
+ enabled: false,
26148
+ reason: "invalid_state",
26149
+ stateFile
26150
+ };
26151
+ }
26152
+ for (const event of events) dispatchTelemetry(buildTelemetryPayload(event, state, {}, options), options);
26153
+ return {
26154
+ enabled: true,
26155
+ stateFile,
26156
+ state
26157
+ };
26158
+ }
26159
+ function sendEvent(event, fields, options) {
26160
+ const state = enabledState(options);
26161
+ if (!state) return;
26162
+ dispatchTelemetry(buildTelemetryPayload(event, state, fields, options), options);
26163
+ }
26164
+ function trackTelemetryReportStart(report, options = {}) {
26165
+ if (!isTelemetryReport(report)) return;
26166
+ sendEvent("audit_start", { report }, options);
26167
+ }
26168
+ function trackTelemetryReportComplete(report, options = {}) {
26169
+ if (!isTelemetryReport(report)) return;
26170
+ const state = enabledState(options);
26171
+ if (!state) return;
26172
+ dispatchTelemetry(buildTelemetryPayload("audit_complete", state, { report }, options), options);
26173
+ if (state.sentMilestones.includes("first_audit_complete")) return;
26174
+ state.sentMilestones.push("first_audit_complete");
26175
+ try {
26176
+ writeState(optionStateFile(options), state);
26177
+ } catch {
26178
+ return;
26179
+ }
26180
+ dispatchTelemetry(buildTelemetryPayload("first_audit_complete", state, { report }, options), options);
26181
+ }
26182
+ function trackTelemetryReportFailed(report, errorCategory, options = {}) {
26183
+ if (!isTelemetryReport(report)) return;
26184
+ sendEvent("audit_failed", {
26185
+ errorCategory,
26186
+ report
26187
+ }, options);
26188
+ }
26189
+ function telemetryErrorCategory(error) {
26190
+ if (error instanceof Error && error.name === "AbortError") return "crawl_timeout";
26191
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : void 0;
26192
+ if (code === "ACCESS_DENIED" || code === "AUTH_CONFIG_REQUIRED" || code === "AUTH_EXPIRED" || code === "AUTH_REQUIRED") return "auth";
26193
+ if (code === "INVALID_INPUT" || code === "PROPERTY_NOT_FOUND") return "config";
26194
+ if (code === "OPTIONAL_PROVIDER_UNAVAILABLE" || code === "PROVIDER_UNAVAILABLE" || code === "RATE_LIMITED") return "network";
26195
+ return "unknown";
26196
+ }
25885
26197
  //#endregion
25886
26198
  //#region packages/mcp/src/input-schemas.ts
25887
26199
  const calendarDateSchema = z$1.iso.date("Use a YYYY-MM-DD date.");
@@ -30053,7 +30365,8 @@ async function runReport(id, params = {}) {
30053
30365
  //#endregion
30054
30366
  //#region packages/mcp/src/discovery-tools.ts
30055
30367
  const openOutputSchema = z$1.looseObject({});
30056
- function registerDiscoveryTools(server) {
30368
+ const reportIds = new Set(listReports().map((report) => report.id));
30369
+ function registerDiscoveryTools(server, options = {}) {
30057
30370
  server.registerTool("seo_list_reports", {
30058
30371
  description: "List compact SEO report ids, names, and purposes, optionally by category",
30059
30372
  inputSchema: { category: z$1.enum(REPORT_CATEGORIES).optional() },
@@ -30097,17 +30410,31 @@ function registerDiscoveryTools(server) {
30097
30410
  outputSchema: openOutputSchema,
30098
30411
  annotations: { destructiveHint: false }
30099
30412
  }, async ({ id, params }) => {
30100
- return runReport(id, params);
30413
+ const telemetry = reportIds.has(id) ? options.telemetry?.() : void 0;
30414
+ if (telemetry) trackTelemetryReportStart(id, telemetry);
30415
+ try {
30416
+ const result = await runReport(id, params);
30417
+ if (telemetry) if (result.isError) trackTelemetryReportFailed(id, telemetryErrorCategory(result.structuredContent?.error), telemetry);
30418
+ else trackTelemetryReportComplete(id, telemetry);
30419
+ return result;
30420
+ } catch (error) {
30421
+ if (telemetry) trackTelemetryReportFailed(id, telemetryErrorCategory(error), telemetry);
30422
+ throw error;
30423
+ }
30101
30424
  });
30102
30425
  }
30103
30426
  //#endregion
30104
30427
  //#region packages/mcp/src/index.ts
30105
- function createServer() {
30428
+ function createServer(options = {}) {
30106
30429
  const server = new McpServer({
30107
30430
  name: "seo",
30108
30431
  version: SEO_VERSION
30109
30432
  }, { capabilities: { logging: {} } });
30110
- registerDiscoveryTools(server);
30433
+ const telemetryOptions = () => ({ clientName: server.server.getClientVersion()?.name });
30434
+ registerDiscoveryTools(server, { telemetry: options.telemetry === false ? void 0 : telemetryOptions });
30435
+ if (options.telemetry !== false) server.server.oninitialized = () => {
30436
+ initializeTelemetry(telemetryOptions());
30437
+ };
30111
30438
  return server;
30112
30439
  }
30113
30440
  async function startMcpServer(opts = {}) {