@aexol/spectral 0.9.42 → 0.9.44

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.
Files changed (144) hide show
  1. package/dist/extensions/aexol-mcp.d.ts.map +1 -1
  2. package/dist/extensions/aexol-mcp.js +44 -9
  3. package/dist/extensions/browser/browser-service.d.ts +4 -1
  4. package/dist/extensions/browser/browser-service.d.ts.map +1 -1
  5. package/dist/extensions/browser/browser-service.js +34 -7
  6. package/dist/extensions/browser/config.d.ts +3 -0
  7. package/dist/extensions/browser/config.d.ts.map +1 -1
  8. package/dist/extensions/browser/config.js +1 -0
  9. package/dist/extensions/browser/index.d.ts.map +1 -1
  10. package/dist/extensions/browser/index.js +3 -0
  11. package/dist/extensions/browser/record/record-tool.d.ts.map +1 -1
  12. package/dist/extensions/browser/record/record-tool.js +27 -4
  13. package/dist/extensions/browser/scenario/markdown-parser.d.ts +44 -0
  14. package/dist/extensions/browser/scenario/markdown-parser.d.ts.map +1 -0
  15. package/dist/extensions/browser/scenario/markdown-parser.js +159 -0
  16. package/dist/extensions/browser/scenario/scenario-tool.d.ts +4 -0
  17. package/dist/extensions/browser/scenario/scenario-tool.d.ts.map +1 -0
  18. package/dist/extensions/browser/scenario/scenario-tool.js +381 -0
  19. package/dist/extensions/browser/tools/core.js +1 -1
  20. package/dist/extensions/browser/tools/screenshot.d.ts.map +1 -1
  21. package/dist/extensions/browser/tools/screenshot.js +2 -1
  22. package/dist/extensions/seo/analyzers/agent-ux.d.ts +5 -0
  23. package/dist/extensions/seo/analyzers/agent-ux.d.ts.map +1 -0
  24. package/dist/extensions/seo/analyzers/agent-ux.js +95 -0
  25. package/dist/extensions/seo/analyzers/content-optimize.d.ts +3 -0
  26. package/dist/extensions/seo/analyzers/content-optimize.d.ts.map +1 -0
  27. package/dist/extensions/seo/analyzers/content-optimize.js +190 -0
  28. package/dist/extensions/seo/analyzers/content.d.ts +16 -0
  29. package/dist/extensions/seo/analyzers/content.d.ts.map +1 -0
  30. package/dist/extensions/seo/analyzers/content.js +174 -0
  31. package/dist/extensions/seo/analyzers/crawler.d.ts +4 -0
  32. package/dist/extensions/seo/analyzers/crawler.d.ts.map +1 -0
  33. package/dist/extensions/seo/analyzers/crawler.js +288 -0
  34. package/dist/extensions/seo/analyzers/drift.d.ts +13 -0
  35. package/dist/extensions/seo/analyzers/drift.d.ts.map +1 -0
  36. package/dist/extensions/seo/analyzers/drift.js +204 -0
  37. package/dist/extensions/seo/analyzers/images.d.ts +10 -0
  38. package/dist/extensions/seo/analyzers/images.d.ts.map +1 -0
  39. package/dist/extensions/seo/analyzers/images.js +49 -0
  40. package/dist/extensions/seo/analyzers/keywords.d.ts +9 -0
  41. package/dist/extensions/seo/analyzers/keywords.d.ts.map +1 -0
  42. package/dist/extensions/seo/analyzers/keywords.js +183 -0
  43. package/dist/extensions/seo/analyzers/parasite.d.ts +18 -0
  44. package/dist/extensions/seo/analyzers/parasite.d.ts.map +1 -0
  45. package/dist/extensions/seo/analyzers/parasite.js +142 -0
  46. package/dist/extensions/seo/analyzers/performance.d.ts +6 -0
  47. package/dist/extensions/seo/analyzers/performance.d.ts.map +1 -0
  48. package/dist/extensions/seo/analyzers/performance.js +257 -0
  49. package/dist/extensions/seo/analyzers/preload.d.ts +12 -0
  50. package/dist/extensions/seo/analyzers/preload.d.ts.map +1 -0
  51. package/dist/extensions/seo/analyzers/preload.js +108 -0
  52. package/dist/extensions/seo/analyzers/readability.d.ts +4 -0
  53. package/dist/extensions/seo/analyzers/readability.d.ts.map +1 -0
  54. package/dist/extensions/seo/analyzers/readability.js +121 -0
  55. package/dist/extensions/seo/analyzers/schema.d.ts +21 -0
  56. package/dist/extensions/seo/analyzers/schema.d.ts.map +1 -0
  57. package/dist/extensions/seo/analyzers/schema.js +324 -0
  58. package/dist/extensions/seo/analyzers/sitemap.d.ts +20 -0
  59. package/dist/extensions/seo/analyzers/sitemap.d.ts.map +1 -0
  60. package/dist/extensions/seo/analyzers/sitemap.js +121 -0
  61. package/dist/extensions/seo/analyzers/technical.d.ts +11 -0
  62. package/dist/extensions/seo/analyzers/technical.d.ts.map +1 -0
  63. package/dist/extensions/seo/analyzers/technical.js +268 -0
  64. package/dist/extensions/seo/config.d.ts +24 -0
  65. package/dist/extensions/seo/config.d.ts.map +1 -0
  66. package/dist/extensions/seo/config.js +48 -0
  67. package/dist/extensions/seo/index.d.ts +16 -0
  68. package/dist/extensions/seo/index.d.ts.map +1 -0
  69. package/dist/extensions/seo/index.js +126 -0
  70. package/dist/extensions/seo/tools/agent-ux.d.ts +7 -0
  71. package/dist/extensions/seo/tools/agent-ux.d.ts.map +1 -0
  72. package/dist/extensions/seo/tools/agent-ux.js +63 -0
  73. package/dist/extensions/seo/tools/batch.d.ts +4 -0
  74. package/dist/extensions/seo/tools/batch.d.ts.map +1 -0
  75. package/dist/extensions/seo/tools/batch.js +193 -0
  76. package/dist/extensions/seo/tools/content.d.ts +7 -0
  77. package/dist/extensions/seo/tools/content.d.ts.map +1 -0
  78. package/dist/extensions/seo/tools/content.js +55 -0
  79. package/dist/extensions/seo/tools/crawl.d.ts +4 -0
  80. package/dist/extensions/seo/tools/crawl.d.ts.map +1 -0
  81. package/dist/extensions/seo/tools/crawl.js +166 -0
  82. package/dist/extensions/seo/tools/drift.d.ts +7 -0
  83. package/dist/extensions/seo/tools/drift.d.ts.map +1 -0
  84. package/dist/extensions/seo/tools/drift.js +104 -0
  85. package/dist/extensions/seo/tools/fetch.d.ts +7 -0
  86. package/dist/extensions/seo/tools/fetch.d.ts.map +1 -0
  87. package/dist/extensions/seo/tools/fetch.js +123 -0
  88. package/dist/extensions/seo/tools/full-audit.d.ts +11 -0
  89. package/dist/extensions/seo/tools/full-audit.d.ts.map +1 -0
  90. package/dist/extensions/seo/tools/full-audit.js +191 -0
  91. package/dist/extensions/seo/tools/helpers.d.ts +13 -0
  92. package/dist/extensions/seo/tools/helpers.d.ts.map +1 -0
  93. package/dist/extensions/seo/tools/helpers.js +24 -0
  94. package/dist/extensions/seo/tools/images.d.ts +7 -0
  95. package/dist/extensions/seo/tools/images.d.ts.map +1 -0
  96. package/dist/extensions/seo/tools/images.js +46 -0
  97. package/dist/extensions/seo/tools/keywords.d.ts +4 -0
  98. package/dist/extensions/seo/tools/keywords.d.ts.map +1 -0
  99. package/dist/extensions/seo/tools/keywords.js +234 -0
  100. package/dist/extensions/seo/tools/parasite.d.ts +7 -0
  101. package/dist/extensions/seo/tools/parasite.d.ts.map +1 -0
  102. package/dist/extensions/seo/tools/parasite.js +53 -0
  103. package/dist/extensions/seo/tools/parse.d.ts +7 -0
  104. package/dist/extensions/seo/tools/parse.d.ts.map +1 -0
  105. package/dist/extensions/seo/tools/parse.js +66 -0
  106. package/dist/extensions/seo/tools/performance.d.ts +4 -0
  107. package/dist/extensions/seo/tools/performance.d.ts.map +1 -0
  108. package/dist/extensions/seo/tools/performance.js +201 -0
  109. package/dist/extensions/seo/tools/preload.d.ts +7 -0
  110. package/dist/extensions/seo/tools/preload.d.ts.map +1 -0
  111. package/dist/extensions/seo/tools/preload.js +52 -0
  112. package/dist/extensions/seo/tools/report.d.ts +4 -0
  113. package/dist/extensions/seo/tools/report.d.ts.map +1 -0
  114. package/dist/extensions/seo/tools/report.js +167 -0
  115. package/dist/extensions/seo/tools/schema.d.ts +7 -0
  116. package/dist/extensions/seo/tools/schema.d.ts.map +1 -0
  117. package/dist/extensions/seo/tools/schema.js +105 -0
  118. package/dist/extensions/seo/tools/sitemap.d.ts +7 -0
  119. package/dist/extensions/seo/tools/sitemap.d.ts.map +1 -0
  120. package/dist/extensions/seo/tools/sitemap.js +85 -0
  121. package/dist/extensions/seo/tools/technical.d.ts +7 -0
  122. package/dist/extensions/seo/tools/technical.d.ts.map +1 -0
  123. package/dist/extensions/seo/tools/technical.js +62 -0
  124. package/dist/extensions/seo/types.d.ts +445 -0
  125. package/dist/extensions/seo/types.d.ts.map +1 -0
  126. package/dist/extensions/seo/types.js +7 -0
  127. package/dist/extensions/seo/utils/fetcher.d.ts +28 -0
  128. package/dist/extensions/seo/utils/fetcher.d.ts.map +1 -0
  129. package/dist/extensions/seo/utils/fetcher.js +118 -0
  130. package/dist/extensions/seo/utils/parser.d.ts +8 -0
  131. package/dist/extensions/seo/utils/parser.d.ts.map +1 -0
  132. package/dist/extensions/seo/utils/parser.js +265 -0
  133. package/dist/extensions/seo/utils/renderer.d.ts +39 -0
  134. package/dist/extensions/seo/utils/renderer.d.ts.map +1 -0
  135. package/dist/extensions/seo/utils/renderer.js +190 -0
  136. package/dist/extensions/seo/utils/url-safety.d.ts +31 -0
  137. package/dist/extensions/seo/utils/url-safety.d.ts.map +1 -0
  138. package/dist/extensions/seo/utils/url-safety.js +202 -0
  139. package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
  140. package/dist/sdk/coding-agent/core/extensions/native-extensions.js +11 -0
  141. package/dist/server/handlers/paths-pick-directory.d.ts +0 -27
  142. package/dist/server/handlers/paths-pick-directory.d.ts.map +1 -1
  143. package/dist/server/handlers/paths-pick-directory.js +86 -92
  144. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/tools/report.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oCAAoC,CAAC;AACvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9C,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CA+E9E"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * seo_report tool — generate formatted reports from audit results.
3
+ */
4
+ import { mkdirSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { errorResult, textResult } from "./helpers.js";
7
+ export function registerReportTools(ext, config) {
8
+ const reportTool = {
9
+ name: "seo_report",
10
+ label: "SEO Report Generator",
11
+ description: "Generate a formatted SEO report from audit results. " +
12
+ "Accepts a FullAuditResult object (from seo_audit) and produces a Markdown, JSON, or HTML report. " +
13
+ "Optionally writes the report to a file in the SEO output directory (default: ~/.spectral/seo-reports/).",
14
+ promptSnippet: "`seo_report { auditResult, format?, outputPath? }` — generate formatted SEO report",
15
+ parameters: {
16
+ type: "object",
17
+ properties: {
18
+ auditResult: {
19
+ type: "object",
20
+ description: "The FullAuditResult object from seo_audit.",
21
+ },
22
+ format: {
23
+ type: "string",
24
+ enum: ["markdown", "json", "html"],
25
+ description: "Output format. Default: 'markdown'.",
26
+ },
27
+ outputPath: {
28
+ type: "string",
29
+ description: "File path to write the report. If omitted, returns content only.",
30
+ },
31
+ },
32
+ required: ["auditResult"],
33
+ },
34
+ async execute(_toolCallId, params) {
35
+ const { auditResult, format, outputPath } = params;
36
+ try {
37
+ const fmt = format ?? "markdown";
38
+ let content;
39
+ switch (fmt) {
40
+ case "json":
41
+ content = JSON.stringify(auditResult, null, 2);
42
+ break;
43
+ case "html":
44
+ content = generateHtmlReport(auditResult);
45
+ break;
46
+ case "markdown":
47
+ default:
48
+ content = generateMarkdownReport(auditResult);
49
+ break;
50
+ }
51
+ let savedPath;
52
+ if (outputPath) {
53
+ const fullPath = outputPath.startsWith("/")
54
+ ? outputPath
55
+ : join(config.outputDir, outputPath);
56
+ mkdirSync(join(fullPath, ".."), { recursive: true });
57
+ writeFileSync(fullPath, content, "utf-8");
58
+ savedPath = fullPath;
59
+ }
60
+ const summary = `✓ Report generated (${fmt})\n` +
61
+ (savedPath ? `Saved to: ${savedPath}\n` : "") +
62
+ `Content length: ${content.length} chars`;
63
+ return textResult(summary, {
64
+ format: fmt,
65
+ content: content.length > 50_000 ? content.slice(0, 50_000) + "..." : content,
66
+ savedPath,
67
+ });
68
+ }
69
+ catch (err) {
70
+ return errorResult(err instanceof Error ? err.message : String(err));
71
+ }
72
+ },
73
+ };
74
+ ext.registerTool(reportTool);
75
+ }
76
+ function generateMarkdownReport(audit) {
77
+ const lines = [
78
+ `# SEO Audit Report`,
79
+ ``,
80
+ `**URL:** ${audit.url}`,
81
+ `**Date:** ${audit.auditedAt}`,
82
+ `**Overall Score:** ${audit.overallScore}/100`,
83
+ ``,
84
+ ];
85
+ if (audit.technical) {
86
+ lines.push(`## Technical SEO`, `**Score:** ${audit.technical.score}/100`, ``);
87
+ lines.push(`| Category | Score |`, `|----------|-------|`);
88
+ for (const [cat, score] of Object.entries(audit.technical.categoryScores)) {
89
+ lines.push(`| ${cat} | ${score}/100 |`);
90
+ }
91
+ lines.push(``);
92
+ lines.push(`**HTTPS:** ${audit.technical.isHttps ? "✓" : "✗"}`);
93
+ lines.push(`**Mobile Optimized:** ${audit.technical.mobileOptimized ? "✓" : "✗"}`);
94
+ lines.push(`**robots.txt:** ${audit.technical.robotsTxt.exists ? "✓" : "✗"}`);
95
+ lines.push(``);
96
+ }
97
+ if (audit.content) {
98
+ lines.push(`## Content Quality`, `**Overall Quality:** ${audit.content.overallQuality}/100`, ``, `- Word count: ${audit.content.wordCount}`, `- Filler score: ${audit.content.fillerScore}/100`, `- AI pattern score: ${audit.content.aiPatternScore}/100`, `- Information density: ${audit.content.informationDensity}/100`, `- Repetition score: ${audit.content.repetitionScore}/100`, `- Flags: ${audit.content.flags.join(", ") || "none"}`, ``);
99
+ }
100
+ if (audit.schema) {
101
+ lines.push(`## Schema Markup`, `**Detected schemas:** ${audit.schema.detectedSchemas.length}`, `**Deprecated types:** ${audit.schema.deprecatedTypes.join(", ") || "none"}`, ``);
102
+ if (audit.schema.recommendations.length > 0) {
103
+ lines.push(`**Recommendations:**`);
104
+ for (const rec of audit.schema.recommendations) {
105
+ lines.push(`- ${rec}`);
106
+ }
107
+ lines.push(``);
108
+ }
109
+ }
110
+ if (audit.images) {
111
+ lines.push(`## Images`, `**Total:** ${audit.images.totalImages}`, `**Missing alt:** ${audit.images.missingAlt}`, `**Missing dimensions:** ${audit.images.missingDimensions}`, `**Modern formats:** ${audit.images.modernFormats}`, ``);
112
+ }
113
+ if (audit.preload) {
114
+ lines.push(`## Preload & bfcache`, `**Score:** ${audit.preload.score}/100`, `**Speculation rules:** ${audit.preload.speculationRules.inlineBlocks} inline blocks`, `**Preload hints:** ${audit.preload.preloadHints}`, ``);
115
+ }
116
+ if (audit.agentUx) {
117
+ lines.push(`## Agent UX`, `**Score:** ${audit.agentUx.agentUxScore}/100`, `**Real buttons:** ${audit.agentUx.html.realButtons}`, `**Real anchors:** ${audit.agentUx.html.realAnchors}`, `**div onclick widgets:** ${audit.agentUx.html.divOnclickWidgets}`, ``);
118
+ }
119
+ if (audit.allIssues.length > 0) {
120
+ lines.push(`## Issues (${audit.allIssues.length})`, ``);
121
+ const sorted = audit.allIssues.sort((a, b) => severityRank(a.severity) - severityRank(b.severity));
122
+ for (const issue of sorted) {
123
+ lines.push(`### [${issue.severity.toUpperCase()}] ${issue.title}`);
124
+ lines.push(`**Category:** ${issue.category}`);
125
+ lines.push(`**Description:** ${issue.description}`);
126
+ lines.push(`**Recommendation:** ${issue.recommendation}`);
127
+ lines.push(``);
128
+ }
129
+ }
130
+ return lines.join("\n");
131
+ }
132
+ function generateHtmlReport(audit) {
133
+ const md = generateMarkdownReport(audit);
134
+ // Simple HTML wrapper around markdown content.
135
+ return `<!DOCTYPE html>
136
+ <html>
137
+ <head>
138
+ <meta charset="utf-8">
139
+ <title>SEO Audit Report — ${audit.url}</title>
140
+ <style>
141
+ body { font-family: system-ui, sans-serif; max-width: 900px; margin: 2rem auto; padding: 0 1rem; line-height: 1.6; }
142
+ h1 { border-bottom: 2px solid #333; }
143
+ h2 { margin-top: 2rem; color: #2563eb; }
144
+ table { border-collapse: collapse; width: 100%; }
145
+ td, th { border: 1px solid #ddd; padding: 0.5rem; }
146
+ </style>
147
+ </head>
148
+ <body>
149
+ <pre>${escapeHtml(md)}</pre>
150
+ </body>
151
+ </html>`;
152
+ }
153
+ function escapeHtml(text) {
154
+ return text
155
+ .replace(/&/g, "&amp;")
156
+ .replace(/</g, "&lt;")
157
+ .replace(/>/g, "&gt;");
158
+ }
159
+ function severityRank(s) {
160
+ switch (s) {
161
+ case "critical": return 0;
162
+ case "high": return 1;
163
+ case "medium": return 2;
164
+ case "low": return 3;
165
+ default: return 4;
166
+ }
167
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * seo_schema_audit and seo_schema_generate tools.
3
+ */
4
+ import type { ExtensionAPI } from "../../../sdk/coding-agent/index.js";
5
+ import type { SeoConfig } from "../config.js";
6
+ export declare function registerSchemaTools(ext: ExtensionAPI, config: SeoConfig): void;
7
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/tools/schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oCAAoC,CAAC;AACvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAM9C,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CA8G9E"}
@@ -0,0 +1,105 @@
1
+ import { auditSchema, generateSchema } from "../analyzers/schema.js";
2
+ import { fetchPage } from "../utils/fetcher.js";
3
+ import { errorResult, textResult } from "./helpers.js";
4
+ export function registerSchemaTools(ext, config) {
5
+ const auditTool = {
6
+ name: "seo_schema_audit",
7
+ label: "SEO Schema Audit",
8
+ description: "Detect and validate Schema.org structured data (JSON-LD, Microdata, RDFa) on a page. " +
9
+ "Checks for: missing @context, deprecated types (HowTo, FAQPage, SpecialAnnouncement, etc.), " +
10
+ "relative URLs, invalid dates, and missing required properties. Recommends appropriate schema types " +
11
+ "based on page content. No external API calls.",
12
+ promptSnippet: "`seo_schema_audit { url }` — detect and validate Schema.org structured data",
13
+ parameters: {
14
+ type: "object",
15
+ properties: {
16
+ url: { type: "string", description: "The URL to audit for structured data." },
17
+ },
18
+ required: ["url"],
19
+ },
20
+ async execute(_toolCallId, params) {
21
+ const { url } = params;
22
+ try {
23
+ const result = await fetchPage(url, config);
24
+ if (result.error) {
25
+ return errorResult(`Fetch failed: ${result.error}`);
26
+ }
27
+ const audit = auditSchema(result.content, result.finalUrl);
28
+ const schemaLines = audit.detectedSchemas
29
+ .map((s) => ` [${s.format}] ${s.type}${s.issues.length > 0 ? ` — issues: ${s.issues.join("; ")}` : ""}`)
30
+ .join("\n");
31
+ const summary = `✓ Schema Audit: ${result.finalUrl}\n` +
32
+ `Detected Schemas: ${audit.detectedSchemas.length}\n${schemaLines || " (none)"}\n\n` +
33
+ `Deprecated Types: ${audit.deprecatedTypes.length > 0 ? audit.deprecatedTypes.join(", ") : "none"}\n` +
34
+ `Missing @context: ${audit.missingContext ? "⚠️" : "✓"}\n` +
35
+ `Relative URLs: ${audit.relativeUrls.length}\n` +
36
+ `Invalid Dates: ${audit.invalidDates.length}\n\n` +
37
+ `Recommendations:\n${audit.recommendations.map((r) => ` • ${r}`).join("\n") || " (none)"}`;
38
+ return textResult(summary, { audit });
39
+ }
40
+ catch (err) {
41
+ return errorResult(err instanceof Error ? err.message : String(err));
42
+ }
43
+ },
44
+ };
45
+ const generateTool = {
46
+ name: "seo_schema_generate",
47
+ label: "SEO Schema Generate",
48
+ description: "Generate ready-to-use JSON-LD structured data for common schema types: Organization, LocalBusiness, " +
49
+ "Article, BlogPosting, Product, BreadcrumbList, WebSite, Person, ProfilePage, DiscussionForumPosting, " +
50
+ "OrderAction, and Reservation. Returns valid JSON-LD with @context and absolute URLs. " +
51
+ "Pass type-specific fields as additional parameters.",
52
+ promptSnippet: "`seo_schema_generate { type, ...fields }` — generate JSON-LD for a schema type",
53
+ parameters: {
54
+ type: "object",
55
+ properties: {
56
+ type: {
57
+ type: "string",
58
+ enum: [
59
+ "Organization", "LocalBusiness", "Article", "BlogPosting", "Product",
60
+ "BreadcrumbList", "WebSite", "Person", "ProfilePage", "DiscussionForumPosting",
61
+ "OrderAction", "Reservation",
62
+ ],
63
+ description: "The Schema.org type to generate.",
64
+ },
65
+ name: { type: "string", description: "Name (for Organization, LocalBusiness, Product, Person, etc.)." },
66
+ url: { type: "string", description: "URL (for Organization, WebSite, Person, etc.)." },
67
+ headline: { type: "string", description: "Headline (for Article, BlogPosting, DiscussionForumPosting)." },
68
+ author: { type: "string", description: "Author name (for Article, BlogPosting, DiscussionForumPosting)." },
69
+ datePublished: { type: "string", description: "Publication date (YYYY-MM-DD)." },
70
+ provider: { type: "string", description: "Provider name (for Reservation)." },
71
+ start: { type: "string", description: "Start time (for Reservation)." },
72
+ merchant: { type: "string", description: "Merchant name (for OrderAction)." },
73
+ orderUrl: { type: "string", description: "Order URL (for OrderAction)." },
74
+ items: {
75
+ type: "array",
76
+ items: { type: "object", properties: { name: { type: "string" }, url: { type: "string" } } },
77
+ description: "Breadcrumb items (for BreadcrumbList).",
78
+ },
79
+ sameAs: { type: "array", items: { type: "string" }, description: "sameAs URLs (for Person, Organization)." },
80
+ knowsAbout: { type: "array", items: { type: "string" }, description: "knowsAbout topics (for Person, ProfilePage)." },
81
+ },
82
+ required: ["type"],
83
+ },
84
+ async execute(_toolCallId, params) {
85
+ try {
86
+ const generateParams = params;
87
+ const result = generateSchema(generateParams);
88
+ const jsonLdStr = JSON.stringify(result.jsonLd, null, 2);
89
+ const summary = `✓ Generated ${result.type} schema\n\n` +
90
+ `Required properties: ${result.required.join(", ")}\n\n` +
91
+ `<script type="application/ld+json">\n${jsonLdStr}\n</script>`;
92
+ return textResult(summary, {
93
+ type: result.type,
94
+ jsonLd: result.jsonLd,
95
+ required: result.required,
96
+ });
97
+ }
98
+ catch (err) {
99
+ return errorResult(err instanceof Error ? err.message : String(err));
100
+ }
101
+ },
102
+ };
103
+ ext.registerTool(auditTool);
104
+ ext.registerTool(generateTool);
105
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * seo_sitemap_analyze and seo_sitemap_generate tools.
3
+ */
4
+ import type { ExtensionAPI } from "../../../sdk/coding-agent/index.js";
5
+ import type { SeoConfig } from "../config.js";
6
+ export declare function registerSitemapTools(ext: ExtensionAPI, config: SeoConfig): void;
7
+ //# sourceMappingURL=sitemap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/tools/sitemap.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oCAAoC,CAAC;AACvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9C,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CA0F/E"}
@@ -0,0 +1,85 @@
1
+ import { analyzeSitemap, generateSitemap } from "../analyzers/sitemap.js";
2
+ import { errorResult, textResult, truncate } from "./helpers.js";
3
+ export function registerSitemapTools(ext, config) {
4
+ const analyzeTool = {
5
+ name: "seo_sitemap_analyze",
6
+ label: "SEO Sitemap Analyze",
7
+ description: "Fetch and analyze an XML sitemap. Parses sitemap index files and regular sitemaps. " +
8
+ "Reports: URL count, lastmod coverage, invalid entries, file size, child sitemaps (for index files), " +
9
+ "and warnings (size >50MB, >50k URLs, low lastmod coverage). No external API calls.",
10
+ promptSnippet: "`seo_sitemap_analyze { url }` — parse and analyze an XML sitemap",
11
+ parameters: {
12
+ type: "object",
13
+ properties: {
14
+ url: { type: "string", description: "The sitemap URL to analyze." },
15
+ },
16
+ required: ["url"],
17
+ },
18
+ async execute(_toolCallId, params) {
19
+ const { url } = params;
20
+ try {
21
+ const result = await analyzeSitemap(url, config);
22
+ const summary = `✓ Sitemap Analysis: ${result.url}\n` +
23
+ `Type: ${result.isSitemapIndex ? "Sitemap Index" : "URL Set"}\n` +
24
+ `URL Count: ${result.urlCount}\n` +
25
+ `Invalid Entries: ${result.invalidEntries}\n` +
26
+ `Lastmod Coverage: ${result.lastmodCoverage}%\n` +
27
+ `Size: ${result.sizeBytes} bytes\n` +
28
+ (result.isSitemapIndex ? `Child Sitemaps: ${result.childSitemaps.length}\n` : "") +
29
+ (result.warnings.length > 0 ? `\nWarnings:\n${result.warnings.map((w) => ` ⚠️ ${w}`).join("\n")}` : "");
30
+ return textResult(summary, {
31
+ ...result,
32
+ urls: truncate(result.urls.join("\n"), 10_000),
33
+ childSitemaps: result.childSitemaps,
34
+ });
35
+ }
36
+ catch (err) {
37
+ return errorResult(err instanceof Error ? err.message : String(err));
38
+ }
39
+ },
40
+ };
41
+ const generateTool = {
42
+ name: "seo_sitemap_generate",
43
+ label: "SEO Sitemap Generate",
44
+ description: "Generate a valid XML sitemap from a list of URLs. Each entry can include lastmod, changefreq, " +
45
+ "and priority. Returns the XML string ready to save as sitemap.xml.",
46
+ promptSnippet: "`seo_sitemap_generate { urls: [{ loc, lastmod?, changefreq?, priority? }] }` — generate XML sitemap",
47
+ parameters: {
48
+ type: "object",
49
+ properties: {
50
+ urls: {
51
+ type: "array",
52
+ items: {
53
+ type: "object",
54
+ properties: {
55
+ loc: { type: "string", description: "The URL." },
56
+ lastmod: { type: "string", description: "Last modification date (YYYY-MM-DD)." },
57
+ changefreq: { type: "string", description: "Change frequency (always, hourly, daily, weekly, monthly, yearly, never)." },
58
+ priority: { type: "number", description: "Priority 0.0-1.0." },
59
+ },
60
+ required: ["loc"],
61
+ },
62
+ description: "List of URL entries for the sitemap.",
63
+ },
64
+ },
65
+ required: ["urls"],
66
+ },
67
+ async execute(_toolCallId, params) {
68
+ const { urls } = params;
69
+ try {
70
+ if (!urls || urls.length === 0) {
71
+ return errorResult("At least one URL entry is required.");
72
+ }
73
+ const xml = generateSitemap(urls);
74
+ const summary = `✓ Generated sitemap with ${urls.length} URLs\n\n` +
75
+ `${xml.length > 5000 ? xml.slice(0, 5000) + "\n... (truncated)" : xml}`;
76
+ return textResult(summary, { xml, urlCount: urls.length });
77
+ }
78
+ catch (err) {
79
+ return errorResult(err instanceof Error ? err.message : String(err));
80
+ }
81
+ },
82
+ };
83
+ ext.registerTool(analyzeTool);
84
+ ext.registerTool(generateTool);
85
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * seo_technical_audit tool — full technical SEO audit.
3
+ */
4
+ import type { ExtensionAPI } from "../../../sdk/coding-agent/index.js";
5
+ import type { SeoConfig } from "../config.js";
6
+ export declare function registerTechnicalTools(ext: ExtensionAPI, config: SeoConfig): void;
7
+ //# sourceMappingURL=technical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"technical.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/tools/technical.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oCAAoC,CAAC;AACvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAM9C,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CAqDjF"}
@@ -0,0 +1,62 @@
1
+ import { auditTechnical } from "../analyzers/technical.js";
2
+ import { fetchPage } from "../utils/fetcher.js";
3
+ import { parsePage } from "../utils/parser.js";
4
+ import { errorResult, textResult } from "./helpers.js";
5
+ export function registerTechnicalTools(ext, config) {
6
+ const technicalTool = {
7
+ name: "seo_technical_audit",
8
+ label: "SEO Technical Audit",
9
+ description: "Run a comprehensive technical SEO audit covering: crawlability (robots.txt, sitemap reference), " +
10
+ "indexability (canonical, noindex, H1 count), security (HTTPS, security headers), URL structure " +
11
+ "(length, redirect chains, case), mobile optimization (viewport), and AI crawler management " +
12
+ "(GPTBot, ClaudeBot, Google-Extended directives). Returns a 0-100 score with categorized issues " +
13
+ "and priority recommendations. No external API keys required.",
14
+ promptSnippet: "`seo_technical_audit { url }` — audit technical SEO (crawlability, security, mobile, AI crawlers)",
15
+ promptGuidelines: [
16
+ "When the user asks about SEO, search optimization, page audits, schema markup, sitemaps, or technical SEO for any website or URL, use the seo_* tools. Start with seo_audit for comprehensive analysis or use targeted tools like seo_technical_audit for specific concerns. These are offline-first and require no API keys.",
17
+ ],
18
+ parameters: {
19
+ type: "object",
20
+ properties: {
21
+ url: { type: "string", description: "The URL to audit." },
22
+ },
23
+ required: ["url"],
24
+ },
25
+ async execute(_toolCallId, params) {
26
+ const { url } = params;
27
+ try {
28
+ const fetchResult = await fetchPage(url, config);
29
+ const pageData = parsePage(fetchResult.content, fetchResult.finalUrl);
30
+ const result = await auditTechnical(url, config, pageData, fetchResult);
31
+ const issueLines = result.issues
32
+ .sort((a, b) => severityRank(a.severity) - severityRank(b.severity))
33
+ .map((i) => ` [${i.severity.toUpperCase()}] ${i.category}: ${i.title}`)
34
+ .join("\n");
35
+ const summary = `✓ Technical Audit: ${result.url}\n` +
36
+ `Overall Score: ${result.score}/100\n\n` +
37
+ `Category Scores:\n${Object.entries(result.categoryScores)
38
+ .map(([cat, score]) => ` ${cat}: ${score}/100`)
39
+ .join("\n")}\n\n` +
40
+ `HTTPS: ${result.isHttps ? "✓" : "✗"}\n` +
41
+ `Mobile Optimized: ${result.mobileOptimized ? "✓" : "✗"}\n` +
42
+ `robots.txt: ${result.robotsTxt.exists ? "✓" : "✗"}\n` +
43
+ `AI Crawler Rules: ${result.robotsTxt.aiCrawlerRules.length}\n\n` +
44
+ `Issues (${result.issues.length}):\n${issueLines || " (none)"}`;
45
+ return textResult(summary, { result });
46
+ }
47
+ catch (err) {
48
+ return errorResult(err instanceof Error ? err.message : String(err));
49
+ }
50
+ },
51
+ };
52
+ ext.registerTool(technicalTool);
53
+ }
54
+ function severityRank(s) {
55
+ switch (s) {
56
+ case "critical": return 0;
57
+ case "high": return 1;
58
+ case "medium": return 2;
59
+ case "low": return 3;
60
+ default: return 4;
61
+ }
62
+ }