serpx-seo-meta-check 1.0.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 (5) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +187 -0
  3. package/cli.js +287 -0
  4. package/index.js +294 -0
  5. package/package.json +50 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SerpX
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # SerpX SEO Meta Check
2
+
3
+ A fast, lightweight and zero-dependency command-line SEO checker for inspecting essential on-page and technical SEO metadata.
4
+
5
+ SerpX SEO Meta Check is built by [SerpX](https://serpx.ai/), an AI-powered SEO platform for website analysis, keyword research, competitor intelligence and content optimization.
6
+
7
+ ## What It Checks
8
+
9
+ The package analyzes a public webpage and reports:
10
+
11
+ - Page title
12
+ - Title length
13
+ - Meta description
14
+ - Meta description length
15
+ - Canonical URL
16
+ - Meta robots directives
17
+ - Noindex detection
18
+ - HTML language
19
+ - Viewport configuration
20
+ - H1 count and content
21
+ - H2 count
22
+ - H3 count
23
+ - Approximate visible word count
24
+ - Open Graph title
25
+ - Open Graph description
26
+ - Open Graph image
27
+ - Twitter Card metadata
28
+ - JSON-LD structured data count
29
+ - Basic SEO score
30
+
31
+ ## Installation
32
+
33
+ Install globally:
34
+
35
+ ```bash
36
+ npm install -g serpx-seo-meta-check
37
+ ```
38
+
39
+ Then analyze a page:
40
+
41
+ ```bash
42
+ serpx-seo-check https://example.com
43
+ ```
44
+
45
+ You can also run the package directly:
46
+
47
+ ```bash
48
+ npx serpx-seo-meta-check https://example.com
49
+ ```
50
+
51
+ ## Example Output
52
+
53
+ ```text
54
+ SerpX SEO Meta Check
55
+ ========================================
56
+ Final URL: https://example.com/
57
+ Basic SEO score: 45/100
58
+
59
+ Core Metadata
60
+ ----------------------------------------
61
+ Title: Example Domain
62
+ Title length: 14
63
+ Meta description: Not found
64
+ Description length: 0
65
+ Canonical: Not found
66
+ Robots: Not found
67
+ Noindex: No
68
+
69
+ Page Structure
70
+ ----------------------------------------
71
+ HTML lang: en
72
+ Viewport: Found
73
+ H1 count: 1
74
+ H1: Example Domain
75
+ H2 count: 0
76
+ H3 count: 0
77
+ Approx. word count: 28
78
+ ```
79
+
80
+ ## Why Use SerpX SEO Meta Check?
81
+
82
+ Small technical SEO mistakes can create large search visibility problems.
83
+
84
+ A missing canonical tag, an accidental noindex directive, weak metadata or incorrect heading structure can affect how search engines understand a webpage.
85
+
86
+ SerpX SEO Meta Check provides a fast first-pass SEO inspection directly from the terminal.
87
+
88
+ For deeper website analysis, SEO audits, keyword intelligence and AI-powered optimization, visit the [SerpX AI SEO Platform](https://serpx.ai/).
89
+
90
+ ## Competitor Keyword Research
91
+
92
+ On-page metadata is only one part of a complete SEO strategy.
93
+
94
+ Competitive keyword research can reveal the search queries competing websites rank for, identify content gaps and uncover new organic search opportunities.
95
+
96
+ Read the detailed SerpX guide:
97
+
98
+ [Competitor Keyword Research: How to Find and Analyze Competitor Keywords](https://serpx.ai/blog/competitor-keyword-research)
99
+
100
+ The guide explains practical competitor keyword research workflows and how competitive search data can be converted into an actionable SEO strategy.
101
+
102
+ ## Programmatic Usage
103
+
104
+ The analyzer can also be imported into a Node.js project:
105
+
106
+ ```js
107
+ import { analyzeHtml } from "serpx-seo-meta-check";
108
+
109
+ const html = `
110
+ <!doctype html>
111
+ <html lang="en">
112
+ <head>
113
+ <title>Example Page</title>
114
+ <meta
115
+ name="description"
116
+ content="Example page description"
117
+ >
118
+ <link
119
+ rel="canonical"
120
+ href="https://example.com/"
121
+ >
122
+ </head>
123
+ <body>
124
+ <h1>Example Page</h1>
125
+ </body>
126
+ </html>
127
+ `;
128
+
129
+ const result = analyzeHtml(html);
130
+
131
+ console.log(result);
132
+ ```
133
+
134
+ ## SEO Resources
135
+
136
+ Useful SerpX resources:
137
+
138
+ - [SerpX AI SEO Platform](https://serpx.ai/)
139
+ - [Competitor Keyword Research Guide](https://serpx.ai/blog/competitor-keyword-research)
140
+
141
+ ## Requirements
142
+
143
+ - Node.js 18 or newer
144
+ - Public HTTP or HTTPS URL
145
+
146
+ The package does not require third-party npm dependencies.
147
+
148
+ ## Privacy
149
+
150
+ The CLI requests only the URL supplied by the user in order to analyze its publicly available HTML.
151
+
152
+ It does not require:
153
+
154
+ - A SerpX account
155
+ - A SerpX API key
156
+ - Analytics configuration
157
+ - Tracking software
158
+
159
+ ## Limitations
160
+
161
+ This package performs static analysis of the HTML returned by the target server.
162
+
163
+ Websites that insert SEO metadata only after client-side JavaScript execution may require browser rendering for a complete audit.
164
+
165
+ The SEO score is intended as a quick diagnostic indicator and is not a search-engine ranking score.
166
+
167
+ ## About SerpX
168
+
169
+ [SerpX](https://serpx.ai/) is an AI-powered SEO and content optimization platform built for website owners, developers, marketers and SEO professionals.
170
+
171
+ SerpX provides tools for website analysis, technical SEO, content optimization, competitor intelligence and keyword research.
172
+
173
+ For competitive organic search analysis, read the [SerpX competitor keyword research guide](https://serpx.ai/blog/competitor-keyword-research).
174
+
175
+ ## Links
176
+
177
+ Website:
178
+
179
+ https://serpx.ai/
180
+
181
+ Competitor Keyword Research:
182
+
183
+ https://serpx.ai/blog/competitor-keyword-research
184
+
185
+ ## License
186
+
187
+ MIT
package/cli.js ADDED
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { analyzeHtml } from "./index.js";
4
+
5
+ const args = process.argv.slice(2);
6
+
7
+ function showHelp() {
8
+ console.log(`
9
+ SerpX SEO Meta Check
10
+
11
+ Fast command-line on-page SEO metadata analyzer.
12
+
13
+ Usage:
14
+ serpx-seo-check <url>
15
+
16
+ Examples:
17
+ serpx-seo-check https://example.com
18
+ npx serpx-seo-meta-check https://example.com
19
+
20
+ Checks:
21
+ - Page title
22
+ - Title length
23
+ - Meta description
24
+ - Meta description length
25
+ - Canonical URL
26
+ - Robots directives
27
+ - Noindex status
28
+ - H1 headings
29
+ - H2 and H3 counts
30
+ - HTML language
31
+ - Viewport
32
+ - Open Graph metadata
33
+ - Twitter Card
34
+ - JSON-LD structured data
35
+ - Approximate visible word count
36
+ - Basic SEO score
37
+
38
+ More SEO tools:
39
+ https://serpx.ai/
40
+
41
+ SEO research:
42
+ https://serpx.ai/blog/competitor-keyword-research
43
+ `);
44
+ }
45
+
46
+ if (
47
+ args.length === 0 ||
48
+ args.includes("--help") ||
49
+ args.includes("-h")
50
+ ) {
51
+ showHelp();
52
+ process.exit(0);
53
+ }
54
+
55
+ if (
56
+ args.includes("--version") ||
57
+ args.includes("-v")
58
+ ) {
59
+ console.log("1.0.0");
60
+ process.exit(0);
61
+ }
62
+
63
+ const input = args[0];
64
+
65
+ let parsedUrl;
66
+
67
+ try {
68
+ parsedUrl = new URL(input);
69
+ } catch {
70
+ console.error("Error: Invalid URL.");
71
+ console.error(
72
+ "Example: serpx-seo-check https://example.com"
73
+ );
74
+ process.exit(1);
75
+ }
76
+
77
+ if (
78
+ !["http:", "https:"].includes(
79
+ parsedUrl.protocol
80
+ )
81
+ ) {
82
+ console.error(
83
+ "Error: Only HTTP and HTTPS URLs are supported."
84
+ );
85
+ process.exit(1);
86
+ }
87
+
88
+ try {
89
+ const response = await fetch(parsedUrl, {
90
+ redirect: "follow",
91
+ signal: AbortSignal.timeout(15000),
92
+ headers: {
93
+ "user-agent":
94
+ "SerpX-SEO-Meta-Check/1.0 (+https://serpx.ai/)",
95
+ "accept":
96
+ "text/html,application/xhtml+xml"
97
+ }
98
+ });
99
+
100
+ if (!response.ok) {
101
+ throw new Error(
102
+ `HTTP ${response.status} ${response.statusText}`
103
+ );
104
+ }
105
+
106
+ const contentType =
107
+ response.headers.get("content-type") || "";
108
+
109
+ if (
110
+ contentType &&
111
+ !contentType.includes("text/html") &&
112
+ !contentType.includes(
113
+ "application/xhtml+xml"
114
+ )
115
+ ) {
116
+ throw new Error(
117
+ `Unsupported content type: ${contentType}`
118
+ );
119
+ }
120
+
121
+ const html = await response.text();
122
+
123
+ const result = analyzeHtml(html);
124
+
125
+ console.log("");
126
+ console.log("SerpX SEO Meta Check");
127
+ console.log(
128
+ "========================================"
129
+ );
130
+
131
+ console.log(
132
+ `Final URL: ${response.url}`
133
+ );
134
+
135
+ console.log(
136
+ `Basic SEO score: ${result.score}/100`
137
+ );
138
+
139
+ console.log("");
140
+ console.log("Core Metadata");
141
+ console.log(
142
+ "----------------------------------------"
143
+ );
144
+
145
+ console.log(
146
+ `Title: ${
147
+ result.title || "Not found"
148
+ }`
149
+ );
150
+
151
+ console.log(
152
+ `Title length: ${result.titleLength}`
153
+ );
154
+
155
+ console.log(
156
+ `Meta description: ${
157
+ result.metaDescription || "Not found"
158
+ }`
159
+ );
160
+
161
+ console.log(
162
+ `Description length: ${result.metaDescriptionLength}`
163
+ );
164
+
165
+ console.log(
166
+ `Canonical: ${
167
+ result.canonical || "Not found"
168
+ }`
169
+ );
170
+
171
+ console.log(
172
+ `Robots: ${
173
+ result.robots || "Not found"
174
+ }`
175
+ );
176
+
177
+ console.log(
178
+ `Noindex: ${
179
+ result.noindex ? "Yes" : "No"
180
+ }`
181
+ );
182
+
183
+ console.log("");
184
+ console.log("Page Structure");
185
+ console.log(
186
+ "----------------------------------------"
187
+ );
188
+
189
+ console.log(
190
+ `HTML lang: ${
191
+ result.lang || "Not found"
192
+ }`
193
+ );
194
+
195
+ console.log(
196
+ `Viewport: ${
197
+ result.viewport ? "Found" : "Not found"
198
+ }`
199
+ );
200
+
201
+ console.log(
202
+ `H1 count: ${result.h1Count}`
203
+ );
204
+
205
+ console.log(
206
+ `H1: ${
207
+ result.h1.join(" | ") || "Not found"
208
+ }`
209
+ );
210
+
211
+ console.log(
212
+ `H2 count: ${result.h2Count}`
213
+ );
214
+
215
+ console.log(
216
+ `H3 count: ${result.h3Count}`
217
+ );
218
+
219
+ console.log(
220
+ `Approx. word count: ${result.wordCount}`
221
+ );
222
+
223
+ console.log("");
224
+ console.log(
225
+ "Social & Structured Data"
226
+ );
227
+
228
+ console.log(
229
+ "----------------------------------------"
230
+ );
231
+
232
+ console.log(
233
+ `Open Graph title: ${
234
+ result.ogTitle || "Not found"
235
+ }`
236
+ );
237
+
238
+ console.log(
239
+ `Open Graph description: ${
240
+ result.ogDescription || "Not found"
241
+ }`
242
+ );
243
+
244
+ console.log(
245
+ `Open Graph image: ${
246
+ result.ogImage || "Not found"
247
+ }`
248
+ );
249
+
250
+ console.log(
251
+ `Twitter Card: ${
252
+ result.twitterCard || "Not found"
253
+ }`
254
+ );
255
+
256
+ console.log(
257
+ `JSON-LD blocks: ${result.jsonLdCount}`
258
+ );
259
+
260
+ console.log("");
261
+ console.log("More SEO tools:");
262
+ console.log("https://serpx.ai/");
263
+
264
+ console.log("");
265
+ console.log(
266
+ "Competitor keyword research:"
267
+ );
268
+
269
+ console.log(
270
+ "https://serpx.ai/blog/competitor-keyword-research"
271
+ );
272
+
273
+ console.log("");
274
+
275
+ } catch (error) {
276
+ if (error?.name === "TimeoutError") {
277
+ console.error(
278
+ "Failed to analyze URL: Request timed out."
279
+ );
280
+ } else {
281
+ console.error(
282
+ `Failed to analyze URL: ${error.message}`
283
+ );
284
+ }
285
+
286
+ process.exit(1);
287
+ }
package/index.js ADDED
@@ -0,0 +1,294 @@
1
+ function decodeHtml(value = "") {
2
+ return value
3
+ .replace(/&#(\d+);/g, (_, code) =>
4
+ String.fromCodePoint(Number(code))
5
+ )
6
+ .replace(/&#x([0-9a-f]+);/gi, (_, code) =>
7
+ String.fromCodePoint(parseInt(code, 16))
8
+ )
9
+ .replace(/&nbsp;/gi, " ")
10
+ .replace(/&amp;/gi, "&")
11
+ .replace(/&quot;/gi, '"')
12
+ .replace(/&#39;/gi, "'")
13
+ .replace(/&lt;/gi, "<")
14
+ .replace(/&gt;/gi, ">");
15
+ }
16
+
17
+ function cleanText(value = "") {
18
+ return decodeHtml(
19
+ value
20
+ .replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ")
21
+ .replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ")
22
+ .replace(/<noscript\b[^>]*>[\s\S]*?<\/noscript>/gi, " ")
23
+ .replace(/<svg\b[^>]*>[\s\S]*?<\/svg>/gi, " ")
24
+ .replace(/<template\b[^>]*>[\s\S]*?<\/template>/gi, " ")
25
+ .replace(/<!--[\s\S]*?-->/g, " ")
26
+ .replace(/<[^>]+>/g, " ")
27
+ )
28
+ .replace(/\s+/g, " ")
29
+ .trim();
30
+ }
31
+
32
+ function getAttributes(tag = "") {
33
+ const attributes = {};
34
+
35
+ const pattern =
36
+ /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
37
+
38
+ let match;
39
+
40
+ while ((match = pattern.exec(tag)) !== null) {
41
+ const key = match[1].toLowerCase();
42
+
43
+ if (key.startsWith("<")) {
44
+ continue;
45
+ }
46
+
47
+ attributes[key] = decodeHtml(
48
+ (match[2] ?? match[3] ?? match[4] ?? "").trim()
49
+ );
50
+ }
51
+
52
+ return attributes;
53
+ }
54
+
55
+ function findMeta(html, attribute, expectedValue) {
56
+ const tags = html.match(/<meta\b[^>]*>/gi) || [];
57
+
58
+ for (const tag of tags) {
59
+ const attrs = getAttributes(tag);
60
+
61
+ if (
62
+ attrs[attribute] &&
63
+ attrs[attribute].toLowerCase() === expectedValue.toLowerCase()
64
+ ) {
65
+ return attrs.content || "";
66
+ }
67
+ }
68
+
69
+ return "";
70
+ }
71
+
72
+ function findLink(html, relValue) {
73
+ const tags = html.match(/<link\b[^>]*>/gi) || [];
74
+
75
+ for (const tag of tags) {
76
+ const attrs = getAttributes(tag);
77
+
78
+ const rel = (attrs.rel || "")
79
+ .toLowerCase()
80
+ .split(/\s+/)
81
+ .filter(Boolean);
82
+
83
+ if (rel.includes(relValue.toLowerCase())) {
84
+ return attrs.href || "";
85
+ }
86
+ }
87
+
88
+ return "";
89
+ }
90
+
91
+ function findHtmlLang(html) {
92
+ const match = html.match(/<html\b[^>]*>/i);
93
+
94
+ if (!match) {
95
+ return "";
96
+ }
97
+
98
+ return getAttributes(match[0]).lang || "";
99
+ }
100
+
101
+ function getTitle(html) {
102
+ const match = html.match(
103
+ /<title\b[^>]*>([\s\S]*?)<\/title>/i
104
+ );
105
+
106
+ return match ? cleanText(match[1]) : "";
107
+ }
108
+
109
+ function getHeadings(html, level) {
110
+ let pattern;
111
+
112
+ if (level === 1) {
113
+ pattern = /<h1(?:\s[^>]*)?>([\s\S]*?)<\/h1>/gi;
114
+ } else if (level === 2) {
115
+ pattern = /<h2(?:\s[^>]*)?>([\s\S]*?)<\/h2>/gi;
116
+ } else if (level === 3) {
117
+ pattern = /<h3(?:\s[^>]*)?>([\s\S]*?)<\/h3>/gi;
118
+ } else {
119
+ return [];
120
+ }
121
+
122
+ return [...html.matchAll(pattern)]
123
+ .map(match => cleanText(match[1]))
124
+ .filter(Boolean);
125
+ }
126
+
127
+
128
+ function countJsonLd(html) {
129
+ const matches =
130
+ html.match(
131
+ /<script\b[^>]*type=["']application\/ld\+json["'][^>]*>[\s\S]*?<\/script>/gi
132
+ ) || [];
133
+
134
+ return matches.length;
135
+ }
136
+
137
+ function calculateScore(result) {
138
+ let score = 0;
139
+
140
+ if (result.title) {
141
+ score += 15;
142
+ }
143
+
144
+ if (
145
+ result.titleLength >= 20 &&
146
+ result.titleLength <= 65
147
+ ) {
148
+ score += 10;
149
+ }
150
+
151
+ if (result.metaDescription) {
152
+ score += 15;
153
+ }
154
+
155
+ if (
156
+ result.metaDescriptionLength >= 70 &&
157
+ result.metaDescriptionLength <= 170
158
+ ) {
159
+ score += 10;
160
+ }
161
+
162
+ if (result.canonical) {
163
+ score += 10;
164
+ }
165
+
166
+ if (result.h1Count === 1) {
167
+ score += 15;
168
+ }
169
+
170
+ if (result.viewport) {
171
+ score += 5;
172
+ }
173
+
174
+ if (result.lang) {
175
+ score += 5;
176
+ }
177
+
178
+ if (!result.noindex) {
179
+ score += 5;
180
+ }
181
+
182
+ if (result.jsonLdCount > 0) {
183
+ score += 5;
184
+ }
185
+
186
+ if (result.ogTitle) {
187
+ score += 3;
188
+ }
189
+
190
+ if (result.ogDescription) {
191
+ score += 2;
192
+ }
193
+
194
+ return Math.min(score, 100);
195
+ }
196
+
197
+ export function analyzeHtml(html = "") {
198
+ if (typeof html !== "string") {
199
+ throw new TypeError("HTML input must be a string.");
200
+ }
201
+
202
+ const title = getTitle(html);
203
+
204
+ const metaDescription =
205
+ findMeta(html, "name", "description") ||
206
+ findMeta(html, "property", "description");
207
+
208
+ const canonical = findLink(html, "canonical");
209
+
210
+ const robots = findMeta(
211
+ html,
212
+ "name",
213
+ "robots"
214
+ );
215
+
216
+ const viewport = findMeta(
217
+ html,
218
+ "name",
219
+ "viewport"
220
+ );
221
+
222
+ const h1 = getHeadings(html, 1);
223
+ const h2 = getHeadings(html, 2);
224
+ const h3 = getHeadings(html, 3);
225
+
226
+ const visibleText = cleanText(html);
227
+
228
+ const wordCount = visibleText
229
+ ? visibleText.split(/\s+/).filter(Boolean).length
230
+ : 0;
231
+
232
+ const result = {
233
+ title,
234
+ titleLength: title.length,
235
+
236
+ metaDescription,
237
+ metaDescriptionLength: metaDescription.length,
238
+
239
+ canonical,
240
+
241
+ robots,
242
+ noindex: /\bnoindex\b/i.test(robots),
243
+
244
+ lang: findHtmlLang(html),
245
+
246
+ viewport,
247
+
248
+ h1Count: h1.length,
249
+ h1,
250
+
251
+ h2Count: h2.length,
252
+ h3Count: h3.length,
253
+
254
+ wordCount,
255
+
256
+ ogTitle: findMeta(
257
+ html,
258
+ "property",
259
+ "og:title"
260
+ ),
261
+
262
+ ogDescription: findMeta(
263
+ html,
264
+ "property",
265
+ "og:description"
266
+ ),
267
+
268
+ ogImage: findMeta(
269
+ html,
270
+ "property",
271
+ "og:image"
272
+ ),
273
+
274
+ ogUrl: findMeta(
275
+ html,
276
+ "property",
277
+ "og:url"
278
+ ),
279
+
280
+ twitterCard: findMeta(
281
+ html,
282
+ "name",
283
+ "twitter:card"
284
+ ),
285
+
286
+ jsonLdCount: countJsonLd(html)
287
+ };
288
+
289
+ result.score = calculateScore(result);
290
+
291
+ return result;
292
+ }
293
+
294
+ export default analyzeHtml;
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "serpx-seo-meta-check",
3
+ "version": "1.0.0",
4
+ "description": "Fast zero-dependency CLI for checking titles, meta descriptions, canonical tags, robots directives, headings and essential on-page SEO metadata.",
5
+ "homepage": "https://serpx.ai/",
6
+ "keywords": [
7
+ "seo",
8
+ "seo-checker",
9
+ "seo-audit",
10
+ "on-page-seo",
11
+ "technical-seo",
12
+ "meta-tags",
13
+ "meta-description",
14
+ "canonical",
15
+ "robots",
16
+ "headings",
17
+ "h1",
18
+ "word-count",
19
+ "seo-cli",
20
+ "website-audit",
21
+ "serpx"
22
+ ],
23
+ "author": {
24
+ "name": "SerpX",
25
+ "url": "https://serpx.ai/"
26
+ },
27
+ "license": "MIT",
28
+ "type": "module",
29
+ "main": "./index.js",
30
+ "exports": {
31
+ ".": "./index.js"
32
+ },
33
+ "bin": {
34
+ "serpx-seo-check": "cli.js"
35
+ },
36
+ "files": [
37
+ "index.js",
38
+ "cli.js",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "engines": {
43
+ "node": ">=18"
44
+ },
45
+ "scripts": {
46
+ "check": "node --check index.js && node --check cli.js",
47
+ "test": "node cli.js https://example.com",
48
+ "prepublishOnly": "npm run check"
49
+ }
50
+ }