@houtini/seo-audit-console 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/LICENSE +92 -0
  2. package/README.md +211 -0
  3. package/dist/audit/checks.d.ts +35 -0
  4. package/dist/audit/checks.d.ts.map +1 -0
  5. package/dist/audit/checks.js +1475 -0
  6. package/dist/audit/checks.js.map +1 -0
  7. package/dist/audit/drift.d.ts +40 -0
  8. package/dist/audit/drift.d.ts.map +1 -0
  9. package/dist/audit/drift.js +148 -0
  10. package/dist/audit/drift.js.map +1 -0
  11. package/dist/audit/engine.d.ts +33 -0
  12. package/dist/audit/engine.d.ts.map +1 -0
  13. package/dist/audit/engine.js +186 -0
  14. package/dist/audit/engine.js.map +1 -0
  15. package/dist/audit/opportunities.d.ts +23 -0
  16. package/dist/audit/opportunities.d.ts.map +1 -0
  17. package/dist/audit/opportunities.js +149 -0
  18. package/dist/audit/opportunities.js.map +1 -0
  19. package/dist/audit/report.d.ts +11 -0
  20. package/dist/audit/report.d.ts.map +1 -0
  21. package/dist/audit/report.js +59 -0
  22. package/dist/audit/report.js.map +1 -0
  23. package/dist/audit/schema-validate.d.ts +35 -0
  24. package/dist/audit/schema-validate.d.ts.map +1 -0
  25. package/dist/audit/schema-validate.js +293 -0
  26. package/dist/audit/schema-validate.js.map +1 -0
  27. package/dist/audit/templates.d.ts +43 -0
  28. package/dist/audit/templates.d.ts.map +1 -0
  29. package/dist/audit/templates.js +129 -0
  30. package/dist/audit/templates.js.map +1 -0
  31. package/dist/audit/topicGaps.d.ts +42 -0
  32. package/dist/audit/topicGaps.d.ts.map +1 -0
  33. package/dist/audit/topicGaps.js +181 -0
  34. package/dist/audit/topicGaps.js.map +1 -0
  35. package/dist/core/AuditDatabase.d.ts +33 -0
  36. package/dist/core/AuditDatabase.d.ts.map +1 -0
  37. package/dist/core/AuditDatabase.js +481 -0
  38. package/dist/core/AuditDatabase.js.map +1 -0
  39. package/dist/core/Backlinks.d.ts +33 -0
  40. package/dist/core/Backlinks.d.ts.map +1 -0
  41. package/dist/core/Backlinks.js +110 -0
  42. package/dist/core/Backlinks.js.map +1 -0
  43. package/dist/core/Crawler.d.ts +23 -0
  44. package/dist/core/Crawler.d.ts.map +1 -0
  45. package/dist/core/Crawler.js +588 -0
  46. package/dist/core/Crawler.js.map +1 -0
  47. package/dist/core/DataForSeoClient.d.ts +86 -0
  48. package/dist/core/DataForSeoClient.d.ts.map +1 -0
  49. package/dist/core/DataForSeoClient.js +232 -0
  50. package/dist/core/DataForSeoClient.js.map +1 -0
  51. package/dist/core/Entities.d.ts +23 -0
  52. package/dist/core/Entities.d.ts.map +1 -0
  53. package/dist/core/Entities.js +62 -0
  54. package/dist/core/Entities.js.map +1 -0
  55. package/dist/core/GscClient.d.ts +22 -0
  56. package/dist/core/GscClient.d.ts.map +1 -0
  57. package/dist/core/GscClient.js +93 -0
  58. package/dist/core/GscClient.js.map +1 -0
  59. package/dist/core/GscSync.d.ts +20 -0
  60. package/dist/core/GscSync.d.ts.map +1 -0
  61. package/dist/core/GscSync.js +133 -0
  62. package/dist/core/GscSync.js.map +1 -0
  63. package/dist/core/JobManager.d.ts +30 -0
  64. package/dist/core/JobManager.d.ts.map +1 -0
  65. package/dist/core/JobManager.js +68 -0
  66. package/dist/core/JobManager.js.map +1 -0
  67. package/dist/core/RankTracker.d.ts +25 -0
  68. package/dist/core/RankTracker.d.ts.map +1 -0
  69. package/dist/core/RankTracker.js +78 -0
  70. package/dist/core/RankTracker.js.map +1 -0
  71. package/dist/core/Refresh.d.ts +32 -0
  72. package/dist/core/Refresh.d.ts.map +1 -0
  73. package/dist/core/Refresh.js +73 -0
  74. package/dist/core/Refresh.js.map +1 -0
  75. package/dist/core/UrlInspector.d.ts +22 -0
  76. package/dist/core/UrlInspector.d.ts.map +1 -0
  77. package/dist/core/UrlInspector.js +92 -0
  78. package/dist/core/UrlInspector.js.map +1 -0
  79. package/dist/core/WikidataClient.d.ts +19 -0
  80. package/dist/core/WikidataClient.d.ts.map +1 -0
  81. package/dist/core/WikidataClient.js +59 -0
  82. package/dist/core/WikidataClient.js.map +1 -0
  83. package/dist/core/agentReadiness.d.ts +34 -0
  84. package/dist/core/agentReadiness.d.ts.map +1 -0
  85. package/dist/core/agentReadiness.js +119 -0
  86. package/dist/core/agentReadiness.js.map +1 -0
  87. package/dist/core/ctrModel.d.ts +2 -0
  88. package/dist/core/ctrModel.d.ts.map +1 -0
  89. package/dist/core/ctrModel.js +6 -0
  90. package/dist/core/ctrModel.js.map +1 -0
  91. package/dist/core/dashboardData.d.ts +312 -0
  92. package/dist/core/dashboardData.d.ts.map +1 -0
  93. package/dist/core/dashboardData.js +550 -0
  94. package/dist/core/dashboardData.js.map +1 -0
  95. package/dist/core/dataStorage.d.ts +42 -0
  96. package/dist/core/dataStorage.d.ts.map +1 -0
  97. package/dist/core/dataStorage.js +193 -0
  98. package/dist/core/dataStorage.js.map +1 -0
  99. package/dist/core/draftBrief.d.ts +27 -0
  100. package/dist/core/draftBrief.d.ts.map +1 -0
  101. package/dist/core/draftBrief.js +69 -0
  102. package/dist/core/draftBrief.js.map +1 -0
  103. package/dist/core/extract.d.ts +67 -0
  104. package/dist/core/extract.d.ts.map +1 -0
  105. package/dist/core/extract.js +262 -0
  106. package/dist/core/extract.js.map +1 -0
  107. package/dist/core/gscFreshness.d.ts +18 -0
  108. package/dist/core/gscFreshness.d.ts.map +1 -0
  109. package/dist/core/gscFreshness.js +32 -0
  110. package/dist/core/gscFreshness.js.map +1 -0
  111. package/dist/core/linkGraph.d.ts +19 -0
  112. package/dist/core/linkGraph.d.ts.map +1 -0
  113. package/dist/core/linkGraph.js +125 -0
  114. package/dist/core/linkGraph.js.map +1 -0
  115. package/dist/core/passageScore.d.ts +19 -0
  116. package/dist/core/passageScore.d.ts.map +1 -0
  117. package/dist/core/passageScore.js +59 -0
  118. package/dist/core/passageScore.js.map +1 -0
  119. package/dist/core/paths.d.ts +5 -0
  120. package/dist/core/paths.d.ts.map +1 -0
  121. package/dist/core/paths.js +15 -0
  122. package/dist/core/paths.js.map +1 -0
  123. package/dist/core/queryData.d.ts +35 -0
  124. package/dist/core/queryData.d.ts.map +1 -0
  125. package/dist/core/queryData.js +200 -0
  126. package/dist/core/queryData.js.map +1 -0
  127. package/dist/core/reranker.d.ts +8 -0
  128. package/dist/core/reranker.d.ts.map +1 -0
  129. package/dist/core/reranker.js +68 -0
  130. package/dist/core/reranker.js.map +1 -0
  131. package/dist/core/robots.d.ts +11 -0
  132. package/dist/core/robots.d.ts.map +1 -0
  133. package/dist/core/robots.js +76 -0
  134. package/dist/core/robots.js.map +1 -0
  135. package/dist/core/sitemap.d.ts +15 -0
  136. package/dist/core/sitemap.d.ts.map +1 -0
  137. package/dist/core/sitemap.js +100 -0
  138. package/dist/core/sitemap.js.map +1 -0
  139. package/dist/core/sql.d.ts +6 -0
  140. package/dist/core/sql.d.ts.map +1 -0
  141. package/dist/core/sql.js +6 -0
  142. package/dist/core/sql.js.map +1 -0
  143. package/dist/core/types.d.ts +22 -0
  144. package/dist/core/types.d.ts.map +1 -0
  145. package/dist/core/types.js +2 -0
  146. package/dist/core/types.js.map +1 -0
  147. package/dist/core/url-key.d.ts +39 -0
  148. package/dist/core/url-key.d.ts.map +1 -0
  149. package/dist/core/url-key.js +106 -0
  150. package/dist/core/url-key.js.map +1 -0
  151. package/dist/generators/index.d.ts +45 -0
  152. package/dist/generators/index.d.ts.map +1 -0
  153. package/dist/generators/index.js +184 -0
  154. package/dist/generators/index.js.map +1 -0
  155. package/dist/index.d.ts +3 -0
  156. package/dist/index.d.ts.map +1 -0
  157. package/dist/index.js +10 -0
  158. package/dist/index.js.map +1 -0
  159. package/dist/server.d.ts +7 -0
  160. package/dist/server.d.ts.map +1 -0
  161. package/dist/server.js +1387 -0
  162. package/dist/server.js.map +1 -0
  163. package/dist/src/ui/dashboard.html +347 -0
  164. package/dist/src/ui/sync-progress.html +104 -0
  165. package/package.json +101 -0
  166. package/server.json +57 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * url-key — the single source of truth for the join between GSC data and crawl data.
3
+ *
4
+ * GSC stores Google-canonicalised URLs; the crawler stores raw discovered URLs.
5
+ * Neither normalises, and they normalise differently — so a naive `page = url`
6
+ * join silently misses trailing-slash / www / protocol / tracking-param variants.
7
+ * Both write paths run their URL through `urlKey()` and store the result in a
8
+ * `*_key` column; all joins use the key. The raw URL is kept for evidence, and
9
+ * raw-vs-key divergence (esp. canonical mismatch) is itself a finding.
10
+ *
11
+ * See research/05 (normalisation pitfalls) and plan/architecture §2.
12
+ */
13
+ export interface UrlKeyOptions {
14
+ /**
15
+ * Host form to unify to, derived from the GSC property at sync time:
16
+ * - 'apex' → strip a leading `www.`
17
+ * - 'www' → ensure a leading `www.`
18
+ * - 'asis' → leave host as delivered (default; safest when property type unknown)
19
+ */
20
+ hostForm?: 'apex' | 'www' | 'asis';
21
+ /** Force https (Google almost always canonicalises to https). Default true. */
22
+ forceHttps?: boolean;
23
+ }
24
+ /**
25
+ * Normalise a URL into a stable join key.
26
+ * Returns the original trimmed string if it cannot be parsed (so a bad URL still
27
+ * joins to itself rather than throwing).
28
+ */
29
+ export declare function urlKey(rawUrl: string, options?: UrlKeyOptions): string;
30
+ /**
31
+ * The registrable brand label of a property — the first domain label, lower-cased, alphanumerics
32
+ * only (so "sc-domain:ehi.com.au" → "ehi", "https://www.sim-racing.gg/" → "simracing"). Returns null
33
+ * for labels under 3 chars (too short to match safely). Used for whole-token brand matching:
34
+ * `(' '||LOWER(query)||' ') LIKE '% '||brand||' %'` — same scheme as the dashboard's branded split.
35
+ */
36
+ export declare function brandToken(siteUrl: string): string | null;
37
+ /** Derive the hostForm to use from a GSC property identifier. */
38
+ export declare function hostFormForProperty(siteUrl: string): UrlKeyOptions['hostForm'];
39
+ //# sourceMappingURL=url-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-key.d.ts","sourceRoot":"","sources":["../../src/core/url-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;IACnC,+EAA+E;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAQD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAgD1E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIzD;AAED,iEAAiE;AACjE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAS9E"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * url-key — the single source of truth for the join between GSC data and crawl data.
3
+ *
4
+ * GSC stores Google-canonicalised URLs; the crawler stores raw discovered URLs.
5
+ * Neither normalises, and they normalise differently — so a naive `page = url`
6
+ * join silently misses trailing-slash / www / protocol / tracking-param variants.
7
+ * Both write paths run their URL through `urlKey()` and store the result in a
8
+ * `*_key` column; all joins use the key. The raw URL is kept for evidence, and
9
+ * raw-vs-key divergence (esp. canonical mismatch) is itself a finding.
10
+ *
11
+ * See research/05 (normalisation pitfalls) and plan/architecture §2.
12
+ */
13
+ /** Tracking / session params stripped before keying (prefix match for families). */
14
+ const TRACKING_PARAM_PREFIXES = ['utm_', 'mc_', 'hsa_', '_hs', 'pk_', 'piwik_', 'matomo_'];
15
+ const TRACKING_PARAM_EXACT = new Set([
16
+ 'gclid', 'gbraid', 'wbraid', 'dclid', 'fbclid', 'msclkid', 'twclid', 'ttclid',
17
+ 'igshid', 'mkt_tok', 'yclid', 'ref', 'ref_src', 'spm', 'vero_id', 'oly_anon_id',
18
+ 'oly_enc_id', '_ga', '_gl', 'gclsrc',
19
+ ]);
20
+ function isTrackingParam(name) {
21
+ const lower = name.toLowerCase();
22
+ if (TRACKING_PARAM_EXACT.has(lower))
23
+ return true;
24
+ return TRACKING_PARAM_PREFIXES.some(p => lower.startsWith(p));
25
+ }
26
+ /**
27
+ * Normalise a URL into a stable join key.
28
+ * Returns the original trimmed string if it cannot be parsed (so a bad URL still
29
+ * joins to itself rather than throwing).
30
+ */
31
+ export function urlKey(rawUrl, options = {}) {
32
+ const { hostForm = 'asis', forceHttps = true } = options;
33
+ const raw = (rawUrl ?? '').trim();
34
+ if (!raw)
35
+ return '';
36
+ let u;
37
+ try {
38
+ u = new URL(raw);
39
+ }
40
+ catch {
41
+ return raw; // un-parseable — key to itself, never throw on the hot path
42
+ }
43
+ // Only normalise http(s); leave other schemes (mailto:, tel:) untouched.
44
+ if (u.protocol !== 'http:' && u.protocol !== 'https:')
45
+ return raw;
46
+ // Scheme
47
+ if (forceHttps)
48
+ u.protocol = 'https:';
49
+ // Host: lowercase (hosts are case-insensitive), strip default ports, unify www.
50
+ let host = u.hostname.toLowerCase();
51
+ if (hostForm === 'apex' && host.startsWith('www.'))
52
+ host = host.slice(4);
53
+ else if (hostForm === 'www' && !host.startsWith('www.'))
54
+ host = `www.${host}`;
55
+ u.hostname = host;
56
+ // Strip default web ports unconditionally — after the https rewrite, a leftover
57
+ // :80 would otherwise survive on an https key and break the join.
58
+ if (u.port === '80' || u.port === '443')
59
+ u.port = '';
60
+ // Fragment never affects indexing identity.
61
+ u.hash = '';
62
+ // Query: drop tracking params, sort the rest for stable ordering. Preserve key case.
63
+ const kept = [];
64
+ for (const [k, v] of u.searchParams.entries()) {
65
+ if (!isTrackingParam(k))
66
+ kept.push([k, v]);
67
+ }
68
+ kept.sort((a, b) => (a[0] === b[0] ? (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0) : a[0] < b[0] ? -1 : 1));
69
+ u.search = '';
70
+ for (const [k, v] of kept)
71
+ u.searchParams.append(k, v);
72
+ // Path: preserve case (paths are case-sensitive); strip a single trailing slash
73
+ // except the root. Decode nothing — leave percent-encoding as delivered.
74
+ let path = u.pathname;
75
+ if (path.length > 1 && path.endsWith('/'))
76
+ path = path.slice(0, -1);
77
+ u.pathname = path;
78
+ // Rebuild without a trailing '?' when there are no params.
79
+ const query = u.searchParams.toString();
80
+ return `${u.protocol}//${u.host}${u.pathname}${query ? `?${query}` : ''}`;
81
+ }
82
+ /**
83
+ * The registrable brand label of a property — the first domain label, lower-cased, alphanumerics
84
+ * only (so "sc-domain:ehi.com.au" → "ehi", "https://www.sim-racing.gg/" → "simracing"). Returns null
85
+ * for labels under 3 chars (too short to match safely). Used for whole-token brand matching:
86
+ * `(' '||LOWER(query)||' ') LIKE '% '||brand||' %'` — same scheme as the dashboard's branded split.
87
+ */
88
+ export function brandToken(siteUrl) {
89
+ const host = (siteUrl ?? '').replace(/^sc-domain:/, '').replace(/^https?:\/\//, '').replace(/^www\./, '').replace(/\/.*$/, '');
90
+ const b = (host.split('.')[0] ?? '').toLowerCase().replace(/[^a-z0-9]/g, '');
91
+ return b.length >= 3 ? b : null;
92
+ }
93
+ /** Derive the hostForm to use from a GSC property identifier. */
94
+ export function hostFormForProperty(siteUrl) {
95
+ // Domain properties ("sc-domain:example.com") cover both — default to apex.
96
+ if (siteUrl.startsWith('sc-domain:'))
97
+ return 'apex';
98
+ // URL-prefix properties carry their own host form; honour it.
99
+ try {
100
+ return new URL(siteUrl).hostname.toLowerCase().startsWith('www.') ? 'www' : 'apex';
101
+ }
102
+ catch {
103
+ return 'asis';
104
+ }
105
+ }
106
+ //# sourceMappingURL=url-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-key.js","sourceRoot":"","sources":["../../src/core/url-key.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type Database from 'better-sqlite3';
2
+ export interface JsonLdResult {
3
+ type: string;
4
+ jsonLd: Record<string, unknown>;
5
+ scriptTag: string;
6
+ filledFrom: string[];
7
+ todo: string[];
8
+ }
9
+ export declare function generateJsonLd(page: any): JsonLdResult;
10
+ export interface RedirectResult {
11
+ brokenUrl: string;
12
+ suggested: string | null;
13
+ score: number;
14
+ rule: string;
15
+ }
16
+ export type RedirectFormat = 'htaccess' | 'nginx' | 'nextjs';
17
+ export declare function redirectRule(fromPath: string, toPath: string, format: RedirectFormat): string;
18
+ /**
19
+ * Suggest a 301 for a broken/redirecting URL. When the crawler already RECORDED the real
20
+ * destination (redirect findings), pass it as `knownTarget` — it is used verbatim. Token-
21
+ * overlap fuzzy matching is only the fallback for genuinely dead URLs (404s) where no
22
+ * destination is known; a fuzzy guess presented as a paste-ready rule can be confidently wrong.
23
+ */
24
+ export declare function suggestRedirect(brokenUrl: string, livePages: {
25
+ url: string;
26
+ }[], format?: RedirectFormat, knownTarget?: string | null): RedirectResult;
27
+ /** Collapse a recorded redirect chain: one exact-match rule per hop, straight to the final URL. */
28
+ export declare function collapseChainRules(finalUrl: string, hops: {
29
+ from: string;
30
+ to: string;
31
+ }[], format: RedirectFormat): {
32
+ finalUrl: string;
33
+ rules: string[];
34
+ };
35
+ export interface LinkSuggestion {
36
+ donorUrl: string;
37
+ ipr: number;
38
+ inlinkCount: number;
39
+ relevance: number;
40
+ }
41
+ export declare function suggestInternalLinks(db: Database.Database, receiverUrlKey: string, anchor: string): {
42
+ anchor: string;
43
+ donors: LinkSuggestion[];
44
+ };
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generators/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAwB3C,MAAM,WAAW,YAAY;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE;AAiBxI,wBAAgB,cAAc,CAAC,IAAI,EAAE,GAAG,GAAG,YAAY,CAgCtD;AAGD,MAAM,WAAW,cAAc;IAAG,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAE5G,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAQ7D,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAI7F;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,MAAM,GAAE,cAA2B,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CAYjK;AAED,mGAAmG;AACnG,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,MAAM,EAAE,cAAc,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAOxJ;AAGD,MAAM,WAAW,cAAc;IAAG,QAAQ,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;AAEzG,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,EAAE,CAAA;CAAE,CAiBhJ"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Finding → fix generators. Each produces a concrete, paste-ready remediation from
3
+ * crawl/GSC data — the differentiator over a static report. All are dry-run: they
4
+ * return artifacts, they never write to the user's site.
5
+ */
6
+ // schema-dts is compile-time types; at runtime we build plain objects + serialise.
7
+ // safeJsonLd escapes for safe <script> embedding (per schema-dts docs).
8
+ const safeJsonLd = (data) => JSON.stringify(data, null, 2).replace(/</g, '\\u003C').replace(/>/g, '\\u003E').replace(/&/g, '\\u0026').replace(/'/g, '\\u0027');
9
+ const pathOf = (u) => { try {
10
+ return new URL(u).pathname;
11
+ }
12
+ catch {
13
+ return u || '/';
14
+ } };
15
+ const pathTokens = (u) => pathOf(u).toLowerCase().split(/[/\-_.]+/).filter(Boolean);
16
+ function tokenOverlap(a, b) {
17
+ const ta = new Set(pathTokens(a)), tb = new Set(pathTokens(b));
18
+ if (!tb.size)
19
+ return 0;
20
+ let m = 0;
21
+ for (const t of tb)
22
+ if (ta.has(t))
23
+ m++;
24
+ return m / Math.max(ta.size, tb.size);
25
+ }
26
+ function inferType(urlKey) {
27
+ const p = pathOf(urlKey);
28
+ if (p === '/' || p === '')
29
+ return 'Organization';
30
+ if (/\/(articles?|blog|news|posts?|guides?)\//.test(p))
31
+ return 'Article';
32
+ if (/\/(products?|shop|item|store)\//.test(p))
33
+ return 'Product';
34
+ return 'WebPage';
35
+ }
36
+ // Resolve a possibly-relative URL to absolute against the page URL (Google requires
37
+ // absolute image/logo URLs — generating a relative one just reproduces the finding).
38
+ function absUrl(v, base) {
39
+ if (!v)
40
+ return undefined;
41
+ try {
42
+ return new URL(v, base).toString();
43
+ }
44
+ catch {
45
+ return v;
46
+ }
47
+ }
48
+ export function generateJsonLd(page) {
49
+ const type = inferType(page.url_key);
50
+ // A malformed og_tags row must degrade to a fix without og:image, not error the tool call.
51
+ const og = (() => { try {
52
+ return page.og_tags ? JSON.parse(page.og_tags) : {};
53
+ }
54
+ catch {
55
+ return {};
56
+ } })();
57
+ const filledFrom = [];
58
+ const todo = [];
59
+ const o = { '@context': 'https://schema.org', '@type': type };
60
+ const ogImage = absUrl(og['og:image'], page.url); // absolutised
61
+ if (type === 'Organization') {
62
+ if (page.title) {
63
+ o.name = page.title;
64
+ filledFrom.push('name←title');
65
+ }
66
+ else
67
+ todo.push('name');
68
+ o.url = page.url;
69
+ filledFrom.push('url');
70
+ if (ogImage) {
71
+ o.logo = ogImage;
72
+ filledFrom.push('logo←og:image');
73
+ }
74
+ else
75
+ todo.push('logo');
76
+ todo.push('sameAs (social profiles)');
77
+ }
78
+ else if (type === 'Article') {
79
+ o.headline = page.h1 || page.title || '';
80
+ filledFrom.push('headline←h1/title');
81
+ o.url = page.url;
82
+ filledFrom.push('url');
83
+ if (page.meta_description) {
84
+ o.description = page.meta_description;
85
+ filledFrom.push('description');
86
+ }
87
+ if (ogImage) {
88
+ o.image = ogImage;
89
+ filledFrom.push('image←og:image');
90
+ }
91
+ else
92
+ todo.push('image');
93
+ o.author = { '@type': 'Person', name: 'TODO — author name' };
94
+ todo.push('author.name', 'datePublished', 'dateModified');
95
+ }
96
+ else if (type === 'Product') {
97
+ if (page.title) {
98
+ o.name = page.title;
99
+ filledFrom.push('name←title');
100
+ }
101
+ else
102
+ todo.push('name');
103
+ if (ogImage) {
104
+ o.image = ogImage;
105
+ filledFrom.push('image←og:image');
106
+ }
107
+ else
108
+ todo.push('image');
109
+ o.offers = { '@type': 'Offer', price: 'TODO', priceCurrency: 'TODO', availability: 'https://schema.org/InStock' };
110
+ todo.push('offers.price', 'offers.priceCurrency');
111
+ }
112
+ else {
113
+ if (page.title) {
114
+ o.name = page.title;
115
+ filledFrom.push('name←title');
116
+ }
117
+ o.url = page.url;
118
+ filledFrom.push('url');
119
+ if (page.meta_description) {
120
+ o.description = page.meta_description;
121
+ filledFrom.push('description');
122
+ }
123
+ }
124
+ return { type, jsonLd: o, scriptTag: `<script type="application/ld+json">\n${safeJsonLd(o)}\n</script>`, filledFrom, todo };
125
+ }
126
+ // Escape regex metacharacters — a path with `.`/`+`/`(` must not match other URLs or
127
+ // break the nginx config load.
128
+ const reEsc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
129
+ // One exact-match 301 rule. htaccess uses RedirectMatch with an anchored regex — plain
130
+ // `Redirect 301 /from /to` is PREFIX-matching and silently redirects every sub-path.
131
+ export function redirectRule(fromPath, toPath, format) {
132
+ return format === 'nginx' ? `rewrite ^${reEsc(fromPath)}$ ${toPath} permanent;`
133
+ : format === 'nextjs' ? `{ source: '${fromPath}', destination: '${toPath}', permanent: true },`
134
+ : `RedirectMatch 301 ^${reEsc(fromPath)}$ ${toPath}`;
135
+ }
136
+ /**
137
+ * Suggest a 301 for a broken/redirecting URL. When the crawler already RECORDED the real
138
+ * destination (redirect findings), pass it as `knownTarget` — it is used verbatim. Token-
139
+ * overlap fuzzy matching is only the fallback for genuinely dead URLs (404s) where no
140
+ * destination is known; a fuzzy guess presented as a paste-ready rule can be confidently wrong.
141
+ */
142
+ export function suggestRedirect(brokenUrl, livePages, format = 'htaccess', knownTarget) {
143
+ const from = pathOf(brokenUrl);
144
+ if (knownTarget) {
145
+ return { brokenUrl, suggested: knownTarget, score: 1, rule: redirectRule(from, pathOf(knownTarget), format) };
146
+ }
147
+ let best = null, bestScore = 0;
148
+ for (const p of livePages) {
149
+ const s = tokenOverlap(brokenUrl, p.url);
150
+ if (s > bestScore) {
151
+ bestScore = s;
152
+ best = p.url;
153
+ }
154
+ }
155
+ const to = best ? pathOf(best) : '/';
156
+ const rule = !best || bestScore < 0.34
157
+ ? `# no confident match for ${from} — choose a target manually`
158
+ : redirectRule(from, to, format);
159
+ return { brokenUrl, suggested: bestScore >= 0.34 ? best : null, score: Math.round(bestScore * 100) / 100, rule };
160
+ }
161
+ /** Collapse a recorded redirect chain: one exact-match rule per hop, straight to the final URL. */
162
+ export function collapseChainRules(finalUrl, hops, format) {
163
+ const toPath = pathOf(finalUrl);
164
+ const rules = hops
165
+ .map(h => pathOf(h.from))
166
+ .filter(fromPath => fromPath !== toPath)
167
+ .map(fromPath => redirectRule(fromPath, toPath, format));
168
+ return { finalUrl, rules };
169
+ }
170
+ export function suggestInternalLinks(db, receiverUrlKey, anchor) {
171
+ const alreadyLinking = new Set(db.prepare('SELECT DISTINCT source_key FROM links WHERE target_key = ?').all(receiverUrlKey).map(r => r.source_key));
172
+ // Prefer high-authority donors (iPR) — the "money move" is link equity flowing from
173
+ // strong pages to the under-linked receiver; topical relevance breaks ties.
174
+ // HTML pages only — never suggest linking from a non-HTML resource (RSS feed, image, PDF).
175
+ // Match the MIME type before any charset parameter (mirrors isHtmlContentType).
176
+ const candidates = db.prepare("SELECT url, url_key, ipr, inlink_count FROM pages WHERE status_code=200 AND indexable=1 AND (LOWER(content_type) LIKE 'text/html%' OR LOWER(content_type) LIKE 'application/xhtml+xml%') AND url_key != ? ORDER BY ipr DESC LIMIT 80").all(receiverUrlKey);
177
+ const donors = candidates
178
+ .filter(c => !alreadyLinking.has(c.url_key))
179
+ .map(c => ({ donorUrl: c.url, ipr: Math.round(c.ipr ?? 0), inlinkCount: c.inlink_count, relevance: Math.round(tokenOverlap(c.url, receiverUrlKey) * 100) / 100 }))
180
+ .sort((a, b) => b.relevance - a.relevance || b.ipr - a.ipr)
181
+ .slice(0, 5);
182
+ return { anchor, donors };
183
+ }
184
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/generators/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ // Copyright 2026 Richard Baxter — Apache-2.0
3
+ // seo-audit-console — MCP server entry point.
4
+ import { createServer } from './server.js';
5
+ const { run } = createServer();
6
+ run().catch((err) => {
7
+ console.error('[seo-audit-console] fatal:', err instanceof Error ? err.message : err);
8
+ process.exit(1);
9
+ });
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,6CAA6C;AAC7C,8CAA8C;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,EAAE,GAAG,EAAE,GAAG,YAAY,EAAE,CAAC;AAC/B,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC3B,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function dataDir(): string;
3
+ export declare function createServer(): {
4
+ server: McpServer;
5
+ run: () => Promise<void>;
6
+ };
7
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAwDpE,wBAAgB,OAAO,IAAI,MAAM,CAGhC;AAuJD,wBAAgB,YAAY,IAAI;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAu3C9E"}