squirrelscan 0.0.17

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/README.md ADDED
@@ -0,0 +1,148 @@
1
+ ![squirrelscan](https://mintcdn.com/squirrelscan/CCMTmLbI4xfnpJbQ/logo/light.svg?fit=max&auto=format&n=CCMTmLbI4xfnpJbQ&q=85&s=1303484a4ea3c154c29dd5f6245e55cd)
2
+
3
+ # squirrelscan
4
+
5
+ **CLI Website Audits for Humans, Agents & LLMs**
6
+
7
+ A comprehensive website audit tool for SEO, performance, accessibility, content, and more. Built from the ground up for AI coding agents and developer workflows.
8
+
9
+ ## Features
10
+
11
+ - **149+ Audit Rules** across 20 categories (SEO, accessibility, performance, security, E-E-A-T)
12
+ - **AI-Native Design** with LLM-optimized output formats for Claude Code, Cursor, and other agents
13
+ - **Smart Incremental Crawling** with ETag, Last-Modified, and content hashing
14
+ - **Multiple Output Formats** including console, JSON, HTML reports, and text for piping to LLMs
15
+ - **Single Binary** with zero dependencies
16
+ - **Shell Completions** for bash, zsh, fish
17
+ - **Self-Updating** with built-in version management
18
+
19
+ ## Three Ways to Use
20
+
21
+ ### 1. CLI for Humans
22
+
23
+ Run audits directly in your terminal:
24
+
25
+ ```bash
26
+ squirrel audit example.com
27
+ ```
28
+
29
+ ### 2. Pipe to AI
30
+
31
+ Pipe clean output to any AI assistant:
32
+
33
+ ```bash
34
+ squirrel audit example.com --format text | claude
35
+ ```
36
+
37
+ ### 3. AI Agent Skill
38
+
39
+ Install the skill for autonomous workflows:
40
+
41
+ ```bash
42
+ npx skills install squirrelscan/skills
43
+ ```
44
+
45
+ Then prompt your AI agent:
46
+ ```
47
+ Use the audit-website skill to audit this site and fix all issues
48
+ ```
49
+
50
+ ## Installation
51
+
52
+ **npm (all platforms):**
53
+ ```bash
54
+ npm install -g squirrelscan
55
+ ```
56
+
57
+ Or run without installing:
58
+ ```bash
59
+ npx squirrelscan audit example.com
60
+ ```
61
+
62
+ **macOS / Linux:**
63
+ ```bash
64
+ curl -fsSL https://squirrelscan.com/install | bash
65
+ ```
66
+
67
+ **Windows:**
68
+ ```powershell
69
+ iwr -useb https://squirrelscan.com/install.ps1 | iex
70
+ ```
71
+
72
+ ## Quick Start
73
+
74
+ ```bash
75
+ # Audit a website
76
+ squirrel audit https://example.com
77
+
78
+ # Generate HTML report
79
+ squirrel audit https://example.com -f html -o report.html
80
+
81
+ # Pipe to Claude for AI analysis
82
+ squirrel audit https://example.com --format text | claude
83
+
84
+ # Limit pages for faster results
85
+ squirrel audit https://example.com -m 10
86
+ ```
87
+
88
+ ## Resources
89
+
90
+ - **Website:** [squirrelscan.com](https://squirrelscan.com)
91
+ - **Documentation:** [docs.squirrelscan.com](https://docs.squirrelscan.com)
92
+ - **AI Agent Skills:** [github.com/squirrelscan/skills](https://github.com/squirrelscan/skills)
93
+
94
+ ## Rule Categories
95
+
96
+ | Category | Examples |
97
+ |----------|----------|
98
+ | **Core SEO** | Meta tags, canonical URLs, h1, robots meta |
99
+ | **Accessibility** | ARIA labels, focus indicators, landmarks |
100
+ | **Performance** | Core Web Vitals (LCP, CLS, INP) |
101
+ | **Security** | HTTPS, CSP, leaked secrets (96 patterns) |
102
+ | **E-E-A-T** | Author bylines, expertise signals, trust indicators |
103
+ | **Content** | Word count, readability, freshness |
104
+ | **Images** | Alt text, modern formats, lazy loading |
105
+ | **Links** | Broken links, redirect chains, anchor text |
106
+ | **Schema** | JSON-LD structured data validation |
107
+ | **Mobile** | Viewport, tap targets, responsive design |
108
+
109
+ And 10 more categories covering video, analytics, i18n, local SEO, and more.
110
+
111
+ ## AI Agent Integration
112
+
113
+ squirrelscan is designed for autonomous AI workflows:
114
+
115
+ ```bash
116
+ # Install the skill for Claude Code, Cursor, etc.
117
+ npx skills install squirrelscan/skills
118
+ ```
119
+
120
+ Example AI prompts:
121
+ - "Use the audit-website skill to audit example.com and fix all high-severity issues"
122
+ - "Audit this site, enter plan mode, and create a comprehensive fix strategy"
123
+ - "Use audit-website skill to check for regressions after my recent changes"
124
+
125
+ See [AI Agent Integration docs](https://docs.squirrelscan.com/agents) for advanced workflows.
126
+
127
+ ## Output Formats
128
+
129
+ | Format | Flag | Use Case |
130
+ |--------|------|----------|
131
+ | Console | (default) | Human-readable terminal output |
132
+ | JSON | `-f json` | CI/CD, programmatic processing |
133
+ | HTML | `-f html` | Visual reports for sharing |
134
+ | Markdown | `-f markdown` | Documentation, GitHub |
135
+ | Text | `-f text` | Clean output for piping to LLMs |
136
+
137
+ ## Development Status
138
+
139
+ squirrelscan is in **active beta**. Expect rapid iteration and breaking changes. Feedback and issue reports welcome!
140
+
141
+ ## Links
142
+
143
+ - [Website](https://squirrelscan.com)
144
+ - [Documentation](https://docs.squirrelscan.com)
145
+ - [AI Agent Skills](https://github.com/squirrelscan/skills)
146
+ - [Share Feedback](https://squirrelscan.com/feedback)
147
+ - [Bugs, Issues & Feature Requests](https://github.com/squirrelscan/squirrelscan/issues)
148
+ - [Twitter/X](https://x.com/squirrelscan_)
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * squirrel CLI wrapper
5
+ * Executes the platform-specific binary downloaded during npm install
6
+ */
7
+
8
+ const { execFileSync, spawnSync } = require("child_process");
9
+ const path = require("path");
10
+ const fs = require("fs");
11
+ const { getBinaryExtension } = require("../lib/platform");
12
+
13
+ const binaryName = `squirrel${getBinaryExtension()}`;
14
+ const binaryPath = path.join(__dirname, binaryName);
15
+
16
+ // Check if binary exists
17
+ if (!fs.existsSync(binaryPath)) {
18
+ console.error("Error: squirrelscan binary not found.");
19
+ console.error("");
20
+ console.error("The binary should have been downloaded during npm install.");
21
+ console.error("Try reinstalling: npm install -g squirrelscan");
22
+ console.error("");
23
+ console.error("Or install directly:");
24
+ console.error(" curl -fsSL https://squirrelscan.com/install | bash");
25
+ process.exit(1);
26
+ }
27
+
28
+ // Execute binary with all arguments
29
+ const args = process.argv.slice(2);
30
+
31
+ try {
32
+ // Use spawnSync for proper signal handling and stdio inheritance
33
+ const result = spawnSync(binaryPath, args, {
34
+ stdio: "inherit",
35
+ windowsHide: true,
36
+ });
37
+
38
+ // Forward exit code
39
+ if (result.status !== null) {
40
+ process.exit(result.status);
41
+ }
42
+
43
+ // Handle signal termination
44
+ if (result.signal) {
45
+ process.kill(process.pid, result.signal);
46
+ }
47
+ } catch (err) {
48
+ console.error(`Error executing squirrel: ${err.message}`);
49
+ process.exit(1);
50
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Platform detection for squirrelscan binary downloads
3
+ * Mirrors logic from install.sh
4
+ */
5
+
6
+ const os = require("os");
7
+ const fs = require("fs");
8
+
9
+ /**
10
+ * Detect if running on musl libc (Alpine, etc.)
11
+ * @returns {boolean}
12
+ */
13
+ function isMusl() {
14
+ // Check for musl loader
15
+ try {
16
+ const files = fs.readdirSync("/lib");
17
+ if (files.some((f) => f.startsWith("ld-musl-"))) {
18
+ return true;
19
+ }
20
+ } catch {
21
+ // /lib doesn't exist or not readable
22
+ }
23
+
24
+ // Check for Alpine
25
+ try {
26
+ if (fs.existsSync("/etc/alpine-release")) {
27
+ return true;
28
+ }
29
+ } catch {
30
+ // Not Alpine
31
+ }
32
+
33
+ return false;
34
+ }
35
+
36
+ /**
37
+ * Get platform identifier for binary download
38
+ * @returns {string} Platform identifier (e.g., "darwin-arm64", "linux-x64-musl")
39
+ */
40
+ function getPlatform() {
41
+ const platform = os.platform();
42
+ const arch = os.arch();
43
+
44
+ let osName;
45
+ switch (platform) {
46
+ case "darwin":
47
+ osName = "darwin";
48
+ break;
49
+ case "linux":
50
+ osName = "linux";
51
+ break;
52
+ case "win32":
53
+ osName = "windows";
54
+ break;
55
+ default:
56
+ throw new Error(`Unsupported platform: ${platform}`);
57
+ }
58
+
59
+ let archName;
60
+ switch (arch) {
61
+ case "x64":
62
+ archName = "x64";
63
+ break;
64
+ case "arm64":
65
+ archName = "arm64";
66
+ break;
67
+ default:
68
+ throw new Error(`Unsupported architecture: ${arch}`);
69
+ }
70
+
71
+ // Add musl suffix for Linux if needed
72
+ const libc = osName === "linux" && isMusl() ? "-musl" : "";
73
+
74
+ return `${osName}-${archName}${libc}`;
75
+ }
76
+
77
+ /**
78
+ * Get binary filename for current platform
79
+ * @param {string} version - Version string (e.g., "0.0.17")
80
+ * @returns {string} Binary filename
81
+ */
82
+ function getBinaryFilename(version) {
83
+ const platform = getPlatform();
84
+ const ext = os.platform() === "win32" ? ".exe" : "";
85
+ return `squirrel-${version}-${platform}${ext}`;
86
+ }
87
+
88
+ /**
89
+ * Get extension for current platform
90
+ * @returns {string} ".exe" on Windows, "" otherwise
91
+ */
92
+ function getBinaryExtension() {
93
+ return os.platform() === "win32" ? ".exe" : "";
94
+ }
95
+
96
+ module.exports = {
97
+ getPlatform,
98
+ getBinaryFilename,
99
+ getBinaryExtension,
100
+ isMusl,
101
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "squirrelscan",
3
+ "version": "0.0.17",
4
+ "description": "CLI website audits for humans, agents and LLMs",
5
+ "bin": {
6
+ "squirrel": "bin/squirrel.js"
7
+ },
8
+ "scripts": {
9
+ "postinstall": "node scripts/postinstall.js"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/squirrelscan/squirrelscan.git"
14
+ },
15
+ "homepage": "https://squirrelscan.com",
16
+ "bugs": {
17
+ "url": "https://github.com/squirrelscan/squirrelscan/issues"
18
+ },
19
+ "keywords": [
20
+ "seo",
21
+ "audit",
22
+ "cli",
23
+ "website",
24
+ "llm",
25
+ "accessibility",
26
+ "performance",
27
+ "security",
28
+ "crawler"
29
+ ],
30
+ "author": "squirrelscan",
31
+ "license": "UNLICENSED",
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
35
+ "os": [
36
+ "darwin",
37
+ "linux",
38
+ "win32"
39
+ ],
40
+ "cpu": [
41
+ "x64",
42
+ "arm64"
43
+ ],
44
+ "files": [
45
+ "bin/",
46
+ "lib/",
47
+ "scripts/",
48
+ "README.md"
49
+ ]
50
+ }
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * postinstall script for squirrelscan npm package
5
+ * Downloads the platform-specific binary from GitHub releases
6
+ */
7
+
8
+ const https = require("https");
9
+ const fs = require("fs");
10
+ const path = require("path");
11
+ const crypto = require("crypto");
12
+ const { getPlatform, getBinaryExtension } = require("../lib/platform");
13
+
14
+ const REPO = "squirrelscan/squirrelscan";
15
+ const pkg = require("../package.json");
16
+ const VERSION = `v${pkg.version}`;
17
+
18
+ // Colors for terminal output
19
+ const supportsColor = process.stdout.isTTY;
20
+ const green = (s) => (supportsColor ? `\x1b[32m${s}\x1b[0m` : s);
21
+ const yellow = (s) => (supportsColor ? `\x1b[33m${s}\x1b[0m` : s);
22
+ const red = (s) => (supportsColor ? `\x1b[31m${s}\x1b[0m` : s);
23
+ const blue = (s) => (supportsColor ? `\x1b[34m${s}\x1b[0m` : s);
24
+
25
+ const log = (msg) => console.log(`${green("==>")} ${msg}`);
26
+ const info = (msg) => console.log(`${blue("::")} ${msg}`);
27
+ const warn = (msg) => console.log(`${yellow("Warning:")} ${msg}`);
28
+ const error = (msg) => {
29
+ console.error(`${red("Error:")} ${msg}`);
30
+ process.exit(1);
31
+ };
32
+
33
+ /**
34
+ * HTTPS GET with redirect following
35
+ * @param {string} url
36
+ * @returns {Promise<{data: Buffer, statusCode: number}>}
37
+ */
38
+ function httpsGet(url) {
39
+ return new Promise((resolve, reject) => {
40
+ const request = https.get(url, { headers: { "User-Agent": "squirrelscan-npm" } }, (res) => {
41
+ // Follow redirects
42
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
43
+ return httpsGet(res.headers.location).then(resolve).catch(reject);
44
+ }
45
+
46
+ if (res.statusCode !== 200) {
47
+ reject(new Error(`HTTP ${res.statusCode}: ${url}`));
48
+ return;
49
+ }
50
+
51
+ const chunks = [];
52
+ res.on("data", (chunk) => chunks.push(chunk));
53
+ res.on("end", () => resolve({ data: Buffer.concat(chunks), statusCode: res.statusCode }));
54
+ res.on("error", reject);
55
+ });
56
+
57
+ request.on("error", reject);
58
+ request.setTimeout(120000, () => {
59
+ request.destroy();
60
+ reject(new Error("Request timeout"));
61
+ });
62
+ });
63
+ }
64
+
65
+ /**
66
+ * Fetch with retry
67
+ * @param {string} url
68
+ * @param {number} attempts
69
+ * @returns {Promise<Buffer>}
70
+ */
71
+ async function fetchWithRetry(url, attempts = 3) {
72
+ for (let i = 1; i <= attempts; i++) {
73
+ try {
74
+ const { data } = await httpsGet(url);
75
+ return data;
76
+ } catch (err) {
77
+ if (i < attempts) {
78
+ warn(`Download failed, retrying (${i}/${attempts})...`);
79
+ await new Promise((r) => setTimeout(r, 2000));
80
+ } else {
81
+ throw err;
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Compute SHA256 hash of buffer
89
+ * @param {Buffer} buffer
90
+ * @returns {string}
91
+ */
92
+ function sha256(buffer) {
93
+ return crypto.createHash("sha256").update(buffer).digest("hex");
94
+ }
95
+
96
+ async function main() {
97
+ const platform = getPlatform();
98
+ const binDir = path.join(__dirname, "..", "bin");
99
+ const binaryName = `squirrel${getBinaryExtension()}`;
100
+ const binaryPath = path.join(binDir, binaryName);
101
+
102
+ log(`Installing squirrelscan ${VERSION} for ${platform}...`);
103
+
104
+ // Ensure bin directory exists
105
+ if (!fs.existsSync(binDir)) {
106
+ fs.mkdirSync(binDir, { recursive: true });
107
+ }
108
+
109
+ // Fetch manifest
110
+ const releaseUrl = `https://github.com/${REPO}/releases/download/${VERSION}`;
111
+ const manifestUrl = `${releaseUrl}/manifest.json`;
112
+
113
+ info("Fetching manifest...");
114
+ let manifest;
115
+ try {
116
+ const manifestData = await fetchWithRetry(manifestUrl);
117
+ manifest = JSON.parse(manifestData.toString());
118
+ } catch (err) {
119
+ error(`Failed to fetch manifest: ${err.message}\n URL: ${manifestUrl}`);
120
+ }
121
+
122
+ // Get binary info for platform
123
+ const binaryInfo = manifest.binaries?.[platform];
124
+ if (!binaryInfo) {
125
+ error(`No binary available for platform: ${platform}\n See: https://github.com/${REPO}/releases/tag/${VERSION}`);
126
+ }
127
+
128
+ const { filename, sha256: expectedSha256 } = binaryInfo;
129
+
130
+ // Download binary
131
+ const binaryUrl = `${releaseUrl}/${filename}`;
132
+ info(`Downloading ${filename}...`);
133
+
134
+ let binaryData;
135
+ try {
136
+ binaryData = await fetchWithRetry(binaryUrl);
137
+ } catch (err) {
138
+ error(`Failed to download binary: ${err.message}\n URL: ${binaryUrl}`);
139
+ }
140
+
141
+ // Verify checksum
142
+ info("Verifying checksum...");
143
+ const actualSha256 = sha256(binaryData);
144
+ if (actualSha256 !== expectedSha256) {
145
+ error(`Checksum mismatch!\n Expected: ${expectedSha256}\n Actual: ${actualSha256}`);
146
+ }
147
+ info(`Checksum verified: ${expectedSha256.slice(0, 16)}...`);
148
+
149
+ // Write binary
150
+ fs.writeFileSync(binaryPath, binaryData);
151
+
152
+ // Make executable (Unix only)
153
+ if (process.platform !== "win32") {
154
+ fs.chmodSync(binaryPath, 0o755);
155
+ }
156
+
157
+ log("Installation complete!");
158
+ info(`Binary: ${binaryPath}`);
159
+ }
160
+
161
+ main().catch((err) => {
162
+ console.error(err);
163
+ process.exit(1);
164
+ });