headhunt 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.
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 imharris24
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 for non-commercial purposes only, including without limitation the rights
8
+ to use, copy, modify, merge, publish, and distribute
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
+ 1. The Software may not be used for commercial purposes, including but not limited to selling the Software or using it in a commercial product or service.
13
+ 2. The Software may not be sublicensed for commercial purposes.
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,258 @@
1
+ # HeadHunt-CLI 🎯
2
+
3
+ > The terminal-first SEO metadata hunter. Scrape, score, and audit any website's SEO health directly from the command line.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/headhunt)](https://www.npmjs.com/package/headhunt)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D14.0.0-brightgreen)](https://nodejs.org/)
8
+
9
+ ---
10
+
11
+ ## ✨ Features
12
+
13
+ ### 🔍 Comprehensive SEO Extraction
14
+ - **Basic Metadata** — title, meta description, canonical, robots, viewport, charset, language
15
+ - **Open Graph** — full og: tag detection for social sharing
16
+ - **Twitter Cards** — twitter: card, title, description, image
17
+ - **Schema.org** — JSON-LD & Microdata parsing with type detection
18
+ - **Technical SEO** — doctype, hreflang, pagination, sitemap, RSS, AMP, manifest
19
+
20
+ ### 📊 Intelligent Scoring Engine
21
+ - **100-point SEO score** across 14 weighted categories
22
+ - **Letter grade** (A+ to F) with contextual summary
23
+ - **Priority-ranked recommendations** with impact & effort estimates
24
+
25
+ ### 🔗 Link & Content Analysis
26
+ - Internal vs external link classification
27
+ - Nofollow, sponsored, UGC rel attribute detection
28
+ - Empty anchor text detection
29
+ - Heading hierarchy validation (H1–H6) with skip detection
30
+ - Image alt-text coverage & lazy-loading audit
31
+ - Word count, paragraph count & keyword density estimation
32
+
33
+ ### 🛡️ Security & Performance Signals
34
+ - HTTPS, HSTS, CSP, X-Frame-Options, X-Content-Type-Options
35
+ - Render-blocking script detection
36
+ - HTML payload size & fetch time analysis
37
+ - Estimated total page weight
38
+
39
+ ### 📈 Professional Output Modes
40
+ - **Terminal Report** — beautifully formatted with color-coded scores
41
+ - **JSON Export** — full structured data for pipelines (`--json`, `--json-file`)
42
+ - **Score-Only** — CI-friendly single-line output (`--score-only`)
43
+ - **Deep Audit** — verbose recommendations with impact/effort badges (`--audit`)
44
+ - **Side-by-Side Comparison** — compare two URLs (`--compare`)
45
+
46
+ ---
47
+
48
+ ## 🚀 Installation
49
+
50
+ ### Via npm (recommended)
51
+
52
+ ```bash
53
+ npm install -g headhunt
54
+ ```
55
+
56
+ Then run from anywhere:
57
+
58
+ ```bash
59
+ headhunt https://example.com
60
+ ```
61
+
62
+ ### Via npx (no install)
63
+
64
+ ```bash
65
+ npx headhunt https://example.com
66
+ ```
67
+
68
+ ### From Source
69
+
70
+ ```bash
71
+ git clone https://github.com/imharris24/HeadHunt.git
72
+ cd HeadHunt
73
+ npm install
74
+ chmod +x headhunt.js
75
+ node headhunt.js https://example.com
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 📖 Usage
81
+
82
+ ### Basic Scan
83
+
84
+ ```bash
85
+ headhunt https://example.com
86
+ ```
87
+
88
+ ### Output Modes
89
+
90
+ | Flag | Description |
91
+ |------|-------------|
92
+ | `--json` | Print full raw JSON to stdout |
93
+ | `--json-file` | Save JSON report to `seo-report-<hostname>-<timestamp>.json` |
94
+ | `--score-only` | Print `Score: 78/100 Grade: B+` (CI-friendly) |
95
+ | `--audit` | Show full recommendations with impact & effort badges |
96
+
97
+ ```bash
98
+ # CI pipeline check
99
+ headhunt --score-only https://example.com
100
+
101
+ # Deep audit with actionable fixes
102
+ headhunt --audit https://example.com
103
+
104
+ # Export full JSON for further processing
105
+ headhunt --json-file https://example.com
106
+ ```
107
+
108
+ ### Skip Analysis (Faster)
109
+
110
+ | Flag | Description |
111
+ |------|-------------|
112
+ | `--no-links` | Skip link analysis |
113
+ | `--no-images` | Skip image analysis |
114
+
115
+ ```bash
116
+ headhunt --no-links --no-images https://example.com
117
+ ```
118
+
119
+ ### Compare Two URLs
120
+
121
+ ```bash
122
+ headhunt https://site-a.com --compare https://site-b.com
123
+ ```
124
+
125
+ Generates a side-by-side comparison table with a declared winner.
126
+
127
+ ---
128
+
129
+ ## 📋 Example Output
130
+
131
+ ```
132
+ ════════════════════════════════════════════════════════════════════
133
+
134
+ TARGET https://example.com
135
+ SCANNED 5/23/2026, 10:30:00 AM
136
+ PLATFORM WordPress
137
+
138
+ ────────────────────────────────────────────────────────────────────
139
+
140
+ OVERALL SEO SCORE
141
+
142
+ ██████████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░ 78/100 Grade: B+
143
+
144
+ Solid SEO signals present. Primary opportunities in Image Optimization
145
+ and Structured Data — addressing these could meaningfully boost rankings.
146
+
147
+ ────────────────────────────────────────────────────────────────────
148
+
149
+ CATEGORY BREAKDOWN
150
+
151
+ ✓ Title Tag ████████████████░░░░░░ 15/15
152
+ ✓ Meta Description ████████████░░░░░░░░░░ 10/12
153
+ ~ Heading Structure ████████░░░░░░░░░░░░░░ 6/10
154
+ ✓ Canonical URL █████░░░░░░░░░░░░░░░░░ 5/5
155
+ ...
156
+
157
+ QUICK STATS
158
+
159
+ Title "Example Domain — A pla..." (47c)
160
+ Meta Description 155 chars
161
+ Canonical Present
162
+ H1 Tags 1 (ideal)
163
+ Word Count ~1,240 words (7 min read)
164
+ Images 12 total, 67% have alt
165
+ HTTPS Yes
166
+ Response Time 420ms
167
+
168
+ RECOMMENDATIONS (7 items)
169
+
170
+ 01. ● HIGH Image Optimization
171
+ Issue: 4 image(s) missing alt text (33% uncovered).
172
+ Fix: Add descriptive alt attributes to all meaningful images...
173
+
174
+ 02. ● MEDIUM Structured Data
175
+ Issue: No structured data (schema.org) found.
176
+ Fix: Implement JSON-LD structured data appropriate to your content type...
177
+
178
+ ════════════════════════════════════════════════════════════════════
179
+ ```
180
+
181
+ ---
182
+
183
+ ## 🏗️ Architecture
184
+
185
+ ```
186
+ HeadHuntSEO
187
+ ├── Fetch Engine → axios with custom headers & redirect handling
188
+ ├── Parser Layer → cheerio for fast server-side HTML parsing
189
+ ├── Extractors → modular metadata, schema, link, image, content
190
+ ├── Scoring Engine → weighted rubric across 14 SEO categories
191
+ ├── Recommendations → priority-ranked, impact/effort tagged advice
192
+ └── Display Engine → ANSI-colored terminal reports & JSON serializers
193
+ ```
194
+
195
+ ---
196
+
197
+ ## 🔧 Development
198
+
199
+ ```bash
200
+ # Clone & setup
201
+ git clone https://github.com/imharris24/HeadHunt.git
202
+ cd HeadHunt
203
+ npm install
204
+
205
+ # Run locally
206
+ node headhunt.js https://example.com --audit
207
+
208
+ # Run tests
209
+ npm test
210
+
211
+ # Lint
212
+ npm run lint
213
+ ```
214
+
215
+ ---
216
+
217
+ ## 🗺️ Roadmap
218
+
219
+ | Feature | Status |
220
+ |---------|--------|
221
+ | Core metadata extraction | ✅ |
222
+ | Intelligent scoring engine | ✅ |
223
+ | Terminal reporting | ✅ |
224
+ | JSON export modes | ✅ |
225
+ | URL comparison | ✅ |
226
+ | npm global install | ✅ |
227
+ | **JavaScript rendering** (Puppeteer/Playwright) | 🚧 |
228
+ | **Bulk URL processing** | 🚧 |
229
+ | **Broken link checking** | 🚧 |
230
+ | **CSV / Markdown export** | 🚧 |
231
+ | **Proxy & bot-protection bypass** | 🚧 |
232
+ | **Core Web Vitals integration** | 🚧 |
233
+
234
+ ---
235
+
236
+ ## 🤝 Contributing
237
+
238
+ Contributions are welcome! Please open an issue or submit a pull request.
239
+
240
+ 1. Fork the repository
241
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
242
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
243
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
244
+ 5. Open a Pull Request
245
+
246
+ ---
247
+
248
+ ## 📄 License
249
+
250
+ This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
251
+
252
+ ---
253
+
254
+ ## 🐛 Support
255
+
256
+ Found a bug or have a feature request? Please [open an issue](https://github.com/imharris24/HeadHunt/issues).
257
+
258
+ Built with 💻 by [imharris24](https://github.com/imharris24)
package/headhunt.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ const SEOScraper = require('./src/scraper');
4
+ const main = require('./src/cli');
5
+
6
+ if (require.main === module) {
7
+ main();
8
+ }
9
+
10
+ module.exports = SEOScraper;
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "headhunt",
3
+ "version": "1.0.0",
4
+ "description": "The terminal-first SEO metadata hunter. Scrape, structure, and visualize website <head> tags in JSON format.",
5
+ "main": "headhunt.js",
6
+ "bin": {
7
+ "headhunt": "./headhunt.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/imharris24/HeadHunt-CLI.git"
15
+ },
16
+ "keywords": [
17
+ "seo",
18
+ "scraper",
19
+ "cli",
20
+ "metadata",
21
+ "open-graph",
22
+ "seo-audit",
23
+ "headhunt"
24
+ ],
25
+ "author": "imharris24",
26
+ "license": "MIT",
27
+ "type": "commonjs",
28
+ "bugs": {
29
+ "url": "https://github.com/imharris24/HeadHunt-CLI/issues"
30
+ },
31
+ "homepage": "https://github.com/imharris24/HeadHunt-CLI#readme",
32
+ "dependencies": {
33
+ "axios": "^1.13.2",
34
+ "cheerio": "^1.1.2"
35
+ }
36
+ }
package/src/cli.js ADDED
@@ -0,0 +1,114 @@
1
+ const { URL } = require('url');
2
+ const fs = require('fs');
3
+ const { fmt } = require('./colors');
4
+ const SEOScraper = require('./scraper');
5
+ const { printBanner, printReport, printComparison } = require('./reporter');
6
+
7
+ async function main() {
8
+ const args = process.argv.slice(2);
9
+
10
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
11
+ printBanner();
12
+ console.log(` ${fmt.bold('USAGE')}`);
13
+ console.log(` headhunt [options] <url>`);
14
+ console.log();
15
+ console.log(` ${fmt.bold('OPTIONS')}`);
16
+ console.log(` ${fmt.cyan('--json')} Output raw JSON to stdout`);
17
+ console.log(` ${fmt.cyan('--json-file')} Save full JSON report to file`);
18
+ console.log(` ${fmt.cyan('--no-links')} Skip link crawl (faster)`);
19
+ console.log(` ${fmt.cyan('--no-images')} Skip image analysis`);
20
+ console.log(` ${fmt.cyan('--score-only')} Print score and grade only`);
21
+ console.log(` ${fmt.cyan('--audit')} Full recommendations with impact/effort`);
22
+ console.log(` ${fmt.cyan('--compare <url>')} Compare two URLs side by side`);
23
+ console.log();
24
+ process.exit(0);
25
+ }
26
+
27
+ const options = {
28
+ json: args.includes('--json'),
29
+ jsonFile: args.includes('--json-file'),
30
+ noLinks: args.includes('--no-links'),
31
+ noImages: args.includes('--no-images'),
32
+ scoreOnly: args.includes('--score-only'),
33
+ audit: args.includes('--audit'),
34
+ };
35
+
36
+ const compareIdx = args.indexOf('--compare');
37
+ const compareUrl = compareIdx !== -1 ? args[compareIdx + 1] : null;
38
+
39
+ const urls = args.filter(a => !a.startsWith('--') && a !== compareUrl && (a.startsWith('http://') || a.startsWith('https://')));
40
+
41
+ if (urls.length === 0) {
42
+ console.error(fmt.red(' \u2717 No valid URL provided. URLs must start with http:// or https://'));
43
+ process.exit(1);
44
+ }
45
+
46
+ const targetUrl = urls[0];
47
+
48
+ try { new URL(targetUrl); } catch { console.error(fmt.red(' \u2717 Invalid URL.')); process.exit(1); }
49
+
50
+ if (!options.json && !options.scoreOnly) printBanner();
51
+
52
+ try {
53
+ if (!compareUrl) {
54
+ const scraper = new SEOScraper(targetUrl, options);
55
+ if (!options.json && !options.scoreOnly) console.log(` ${fmt.bold('SCANNING...')}\n`);
56
+ await scraper.fetchPage();
57
+ await scraper.analyze();
58
+ const meta = scraper.metadata;
59
+
60
+ if (options.json) {
61
+ console.log(JSON.stringify(meta, null, 2));
62
+ return;
63
+ }
64
+
65
+ if (options.scoreOnly) {
66
+ console.log(`Score: ${meta.overallScore}/100 Grade: ${meta.grade}`);
67
+ return;
68
+ }
69
+
70
+ printReport(meta, options);
71
+
72
+ if (options.jsonFile) {
73
+ const fname = `seo-report-${new URL(targetUrl).hostname}-${Date.now()}.json`;
74
+ fs.writeFileSync(fname, JSON.stringify(meta, null, 2));
75
+ console.log(` ${fmt.green('\u2713')} JSON report saved \u2192 ${fmt.cyan(fname)}`);
76
+ console.log();
77
+ }
78
+ } else {
79
+ try { new URL(compareUrl); } catch { console.error(fmt.red(' \u2717 Invalid compare URL.')); process.exit(1); }
80
+
81
+ console.log(` ${fmt.bold('SCANNING A...')}\n`);
82
+ const scraperA = new SEOScraper(targetUrl, options);
83
+ await scraperA.fetchPage();
84
+ await scraperA.analyze();
85
+
86
+ console.log();
87
+ console.log(` ${fmt.bold('SCANNING B...')}\n`);
88
+ const scraperB = new SEOScraper(compareUrl, options);
89
+ await scraperB.fetchPage();
90
+ await scraperB.analyze();
91
+
92
+ console.log();
93
+ printComparison(scraperA.metadata, scraperB.metadata);
94
+
95
+ if (options.jsonFile) {
96
+ const fname = `seo-compare-${Date.now()}.json`;
97
+ fs.writeFileSync(fname, JSON.stringify({ a: scraperA.metadata, b: scraperB.metadata }, null, 2));
98
+ console.log(` ${fmt.green('\u2713')} Comparison JSON saved \u2192 ${fmt.cyan(fname)}`);
99
+ }
100
+ }
101
+ } catch (error) {
102
+ console.error(`\n ${fmt.red('\u2717 Error:')} ${error.message}`);
103
+ if (error.code === 'ECONNREFUSED') console.error(fmt.dim(' Connection refused \u2014 is the server running?'));
104
+ if (error.code === 'ETIMEDOUT') console.error(fmt.dim(' Request timed out \u2014 try again or check network.'));
105
+ if (error.response?.status === 403) console.error(fmt.dim(' 403 Forbidden \u2014 server is blocking automated requests.'));
106
+ process.exit(1);
107
+ }
108
+ }
109
+
110
+ if (require.main === module) {
111
+ main();
112
+ }
113
+
114
+ module.exports = main;
package/src/colors.js ADDED
@@ -0,0 +1,50 @@
1
+ const C = {
2
+ reset: '\x1b[0m',
3
+ bold: '\x1b[1m',
4
+ dim: '\x1b[2m',
5
+ italic: '\x1b[3m',
6
+ underline: '\x1b[4m',
7
+
8
+ black: '\x1b[30m',
9
+ red: '\x1b[31m',
10
+ green: '\x1b[32m',
11
+ yellow: '\x1b[33m',
12
+ blue: '\x1b[34m',
13
+ magenta: '\x1b[35m',
14
+ cyan: '\x1b[36m',
15
+ white: '\x1b[37m',
16
+ gray: '\x1b[90m',
17
+
18
+ bgRed: '\x1b[41m',
19
+ bgGreen: '\x1b[42m',
20
+ bgYellow: '\x1b[43m',
21
+ bgBlue: '\x1b[44m',
22
+ bgMagenta: '\x1b[45m',
23
+ bgCyan: '\x1b[46m',
24
+
25
+ brightRed: '\x1b[91m',
26
+ brightGreen: '\x1b[92m',
27
+ brightYellow: '\x1b[93m',
28
+ brightBlue: '\x1b[94m',
29
+ brightMagenta: '\x1b[95m',
30
+ brightCyan: '\x1b[96m',
31
+ brightWhite: '\x1b[97m',
32
+ };
33
+
34
+ const fmt = {
35
+ bold: (s) => `${C.bold}${s}${C.reset}`,
36
+ dim: (s) => `${C.dim}${s}${C.reset}`,
37
+ green: (s) => `${C.green}${s}${C.reset}`,
38
+ red: (s) => `${C.red}${s}${C.reset}`,
39
+ yellow: (s) => `${C.yellow}${s}${C.reset}`,
40
+ cyan: (s) => `${C.cyan}${s}${C.reset}`,
41
+ magenta: (s) => `${C.magenta}${s}${C.reset}`,
42
+ blue: (s) => `${C.blue}${s}${C.reset}`,
43
+ gray: (s) => `${C.gray}${s}${C.reset}`,
44
+ brightGreen: (s) => `${C.brightGreen}${s}${C.reset}`,
45
+ brightRed: (s) => `${C.brightRed}${s}${C.reset}`,
46
+ brightYellow: (s) => `${C.brightYellow}${s}${C.reset}`,
47
+ brightCyan: (s) => `${C.brightCyan}${s}${C.reset}`,
48
+ };
49
+
50
+ module.exports = { C, fmt };
@@ -0,0 +1,31 @@
1
+ const https = require('https');
2
+
3
+ const httpsAgent = new https.Agent({ rejectUnauthorized: false });
4
+ const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
5
+
6
+ const SCORE_WEIGHTS = {
7
+ title: { max: 15, label: 'Title Tag' },
8
+ description: { max: 12, label: 'Meta Description' },
9
+ headings: { max: 10, label: 'Heading Structure' },
10
+ canonical: { max: 5, label: 'Canonical URL' },
11
+ robots: { max: 3, label: 'Robots Meta' },
12
+ openGraph: { max: 8, label: 'Open Graph Tags' },
13
+ twitterCard: { max: 5, label: 'Twitter Card' },
14
+ schema: { max: 8, label: 'Structured Data' },
15
+ images: { max: 8, label: 'Image Optimization' },
16
+ mobile: { max: 5, label: 'Mobile Readiness' },
17
+ performance: { max: 7, label: 'Page Performance' },
18
+ links: { max: 5, label: 'Link Structure' },
19
+ security: { max: 5, label: 'Security Signals' },
20
+ content: { max: 4, label: 'Content Quality' },
21
+ };
22
+
23
+ const PRIORITY = {
24
+ CRITICAL: { label: '\u{1F534} CRITICAL', color: '\x1b[91m' },
25
+ HIGH: { label: '\u{1F7E0} HIGH', color: '\x1b[33m' },
26
+ MEDIUM: { label: '\u{1F7E1} MEDIUM', color: '\x1b[93m' },
27
+ LOW: { label: '\u{1F535} LOW', color: '\x1b[36m' },
28
+ GOOD: { label: '\u2705 PASSED', color: '\x1b[92m' },
29
+ };
30
+
31
+ module.exports = { httpsAgent, USER_AGENT, SCORE_WEIGHTS, PRIORITY };
package/src/logger.js ADDED
@@ -0,0 +1,10 @@
1
+ const { fmt } = require('./colors');
2
+
3
+ const log = {
4
+ info: (msg) => console.log(` ${fmt.blue('\u2139')} ${msg}`),
5
+ ok: (msg) => console.log(` ${fmt.green('\u2713')} ${fmt.dim(msg)}`),
6
+ warn: (msg) => console.log(` ${fmt.yellow('\u26A0')} ${msg}`),
7
+ err: (msg) => console.log(` ${fmt.red('\u2717')} ${msg}`),
8
+ };
9
+
10
+ module.exports = log;