squirrelscan 0.0.17 → 0.0.18
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 +24 -13
- package/bin/squirrel.js +37 -11
- package/package.json +1 -1
- package/scripts/postinstall.js +78 -24
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**CLI Website Audits for Humans, Agents & LLMs**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[squirrelscan](https://squirrelscan.com) is 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
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
@@ -26,27 +26,37 @@ Run audits directly in your terminal:
|
|
|
26
26
|
squirrel audit example.com
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
### 2.
|
|
29
|
+
### 2. AI Coding Agent Skill
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Install the skill for autonomous workflows:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
|
|
34
|
+
npx skills install squirrelscan/skills
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Install the skill for autonomous workflows:
|
|
37
|
+
Use the slash command:
|
|
40
38
|
|
|
41
|
-
```bash
|
|
42
|
-
npx skills install squirrelscan/skills
|
|
43
39
|
```
|
|
40
|
+
/audit-website
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or prompt your AI agent more specifically:
|
|
44
44
|
|
|
45
|
-
Then prompt your AI agent:
|
|
46
45
|
```
|
|
47
|
-
Use the audit-website skill to audit this site and fix all issues
|
|
46
|
+
Use the audit-website skill to audit this site and fix all issues but only crawl 10 pages
|
|
48
47
|
```
|
|
49
48
|
|
|
49
|
+
More information [in the skills repository](https://github.com/squirrelscan/skills) and our [Getting started with AI Agents](https://docs.squirrelscan.com/agents) documentation.
|
|
50
|
+
|
|
51
|
+
### 3. Pipe to AI agent
|
|
52
|
+
|
|
53
|
+
Pipe clean output to any AI assistant:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
squirrel audit example.com --format llm | claude
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
|
|
50
60
|
## Installation
|
|
51
61
|
|
|
52
62
|
**npm (all platforms):**
|
|
@@ -79,7 +89,7 @@ squirrel audit https://example.com
|
|
|
79
89
|
squirrel audit https://example.com -f html -o report.html
|
|
80
90
|
|
|
81
91
|
# Pipe to Claude for AI analysis
|
|
82
|
-
squirrel audit https://example.com --format
|
|
92
|
+
squirrel audit https://example.com --format llm | claude
|
|
83
93
|
|
|
84
94
|
# Limit pages for faster results
|
|
85
95
|
squirrel audit https://example.com -m 10
|
|
@@ -106,7 +116,7 @@ squirrel audit https://example.com -m 10
|
|
|
106
116
|
| **Schema** | JSON-LD structured data validation |
|
|
107
117
|
| **Mobile** | Viewport, tap targets, responsive design |
|
|
108
118
|
|
|
109
|
-
And 10 more categories covering video, analytics, i18n, local SEO, and more.
|
|
119
|
+
And 10 more categories covering video, analytics, i18n, local SEO, and more. See the [rules reference](https://docs.squirrelscan.com/rules) on the documentation website.
|
|
110
120
|
|
|
111
121
|
## AI Agent Integration
|
|
112
122
|
|
|
@@ -133,6 +143,7 @@ See [AI Agent Integration docs](https://docs.squirrelscan.com/agents) for advanc
|
|
|
133
143
|
| HTML | `-f html` | Visual reports for sharing |
|
|
134
144
|
| Markdown | `-f markdown` | Documentation, GitHub |
|
|
135
145
|
| Text | `-f text` | Clean output for piping to LLMs |
|
|
146
|
+
| LLM | `-f llm` | LLM optimized output |
|
|
136
147
|
|
|
137
148
|
## Development Status
|
|
138
149
|
|
package/bin/squirrel.js
CHANGED
|
@@ -2,22 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* squirrel CLI wrapper
|
|
5
|
-
* Executes the
|
|
5
|
+
* Executes the natively installed binary
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const {
|
|
8
|
+
const { spawnSync } = require("child_process");
|
|
9
9
|
const path = require("path");
|
|
10
10
|
const fs = require("fs");
|
|
11
|
-
const
|
|
11
|
+
const os = require("os");
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
const
|
|
13
|
+
// Native install locations (checked first) + npm package fallback (checked last)
|
|
14
|
+
const homeDir = os.homedir();
|
|
15
|
+
const isWindows = process.platform === "win32";
|
|
16
|
+
const ext = isWindows ? ".exe" : "";
|
|
15
17
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
+
// Local npm package binary (fallback if self install failed)
|
|
19
|
+
const localBinary = path.join(__dirname, `squirrel${ext}`);
|
|
20
|
+
|
|
21
|
+
const binaryLocations = isWindows
|
|
22
|
+
? [
|
|
23
|
+
path.join(homeDir, "AppData", "Local", "squirrel", "bin", "squirrel.exe"),
|
|
24
|
+
path.join(homeDir, ".local", "bin", "squirrel.exe"),
|
|
25
|
+
localBinary,
|
|
26
|
+
]
|
|
27
|
+
: [
|
|
28
|
+
path.join(homeDir, ".local", "bin", "squirrel"),
|
|
29
|
+
"/usr/local/bin/squirrel",
|
|
30
|
+
"/opt/homebrew/bin/squirrel",
|
|
31
|
+
localBinary,
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Find binary
|
|
35
|
+
let binaryPath = null;
|
|
36
|
+
for (const loc of binaryLocations) {
|
|
37
|
+
if (fs.existsSync(loc)) {
|
|
38
|
+
binaryPath = loc;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!binaryPath) {
|
|
18
44
|
console.error("Error: squirrelscan binary not found.");
|
|
19
45
|
console.error("");
|
|
20
|
-
console.error("The binary should have been
|
|
46
|
+
console.error("The binary should have been installed during npm install.");
|
|
47
|
+
console.error("Expected locations:");
|
|
48
|
+
binaryLocations.forEach((loc) => console.error(` - ${loc}`));
|
|
49
|
+
console.error("");
|
|
21
50
|
console.error("Try reinstalling: npm install -g squirrelscan");
|
|
22
51
|
console.error("");
|
|
23
52
|
console.error("Or install directly:");
|
|
@@ -29,18 +58,15 @@ if (!fs.existsSync(binaryPath)) {
|
|
|
29
58
|
const args = process.argv.slice(2);
|
|
30
59
|
|
|
31
60
|
try {
|
|
32
|
-
// Use spawnSync for proper signal handling and stdio inheritance
|
|
33
61
|
const result = spawnSync(binaryPath, args, {
|
|
34
62
|
stdio: "inherit",
|
|
35
63
|
windowsHide: true,
|
|
36
64
|
});
|
|
37
65
|
|
|
38
|
-
// Forward exit code
|
|
39
66
|
if (result.status !== null) {
|
|
40
67
|
process.exit(result.status);
|
|
41
68
|
}
|
|
42
69
|
|
|
43
|
-
// Handle signal termination
|
|
44
70
|
if (result.signal) {
|
|
45
71
|
process.kill(process.pid, result.signal);
|
|
46
72
|
}
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* postinstall script for squirrelscan npm package
|
|
5
|
-
* Downloads
|
|
5
|
+
* Downloads binary and runs native self-install
|
|
6
|
+
*
|
|
7
|
+
* Environment variables:
|
|
8
|
+
* SQUIRREL_VERSION - Pin to specific version (e.g., v0.0.15)
|
|
9
|
+
* SQUIRREL_CHANNEL - Release channel: stable or beta (default: stable)
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
const https = require("https");
|
|
9
13
|
const fs = require("fs");
|
|
10
14
|
const path = require("path");
|
|
11
15
|
const crypto = require("crypto");
|
|
16
|
+
const { spawnSync } = require("child_process");
|
|
12
17
|
const { getPlatform, getBinaryExtension } = require("../lib/platform");
|
|
13
18
|
|
|
14
19
|
const REPO = "squirrelscan/squirrelscan";
|
|
15
|
-
const pkg = require("../package.json");
|
|
16
|
-
const VERSION = `v${pkg.version}`;
|
|
17
20
|
|
|
18
21
|
// Colors for terminal output
|
|
19
22
|
const supportsColor = process.stdout.isTTY;
|
|
@@ -32,13 +35,10 @@ const error = (msg) => {
|
|
|
32
35
|
|
|
33
36
|
/**
|
|
34
37
|
* HTTPS GET with redirect following
|
|
35
|
-
* @param {string} url
|
|
36
|
-
* @returns {Promise<{data: Buffer, statusCode: number}>}
|
|
37
38
|
*/
|
|
38
39
|
function httpsGet(url) {
|
|
39
40
|
return new Promise((resolve, reject) => {
|
|
40
41
|
const request = https.get(url, { headers: { "User-Agent": "squirrelscan-npm" } }, (res) => {
|
|
41
|
-
// Follow redirects
|
|
42
42
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
43
43
|
return httpsGet(res.headers.location).then(resolve).catch(reject);
|
|
44
44
|
}
|
|
@@ -64,9 +64,6 @@ function httpsGet(url) {
|
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Fetch with retry
|
|
67
|
-
* @param {string} url
|
|
68
|
-
* @param {number} attempts
|
|
69
|
-
* @returns {Promise<Buffer>}
|
|
70
67
|
*/
|
|
71
68
|
async function fetchWithRetry(url, attempts = 3) {
|
|
72
69
|
for (let i = 1; i <= attempts; i++) {
|
|
@@ -85,29 +82,68 @@ async function fetchWithRetry(url, attempts = 3) {
|
|
|
85
82
|
}
|
|
86
83
|
|
|
87
84
|
/**
|
|
88
|
-
* Compute SHA256 hash
|
|
89
|
-
* @param {Buffer} buffer
|
|
90
|
-
* @returns {string}
|
|
85
|
+
* Compute SHA256 hash
|
|
91
86
|
*/
|
|
92
87
|
function sha256(buffer) {
|
|
93
88
|
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
94
89
|
}
|
|
95
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Get latest version from GitHub releases
|
|
93
|
+
*/
|
|
94
|
+
async function getLatestVersion(channel) {
|
|
95
|
+
const apiUrl = `https://api.github.com/repos/${REPO}/releases`;
|
|
96
|
+
info(`Fetching releases (channel: ${channel})...`);
|
|
97
|
+
|
|
98
|
+
let releases;
|
|
99
|
+
try {
|
|
100
|
+
const data = await fetchWithRetry(apiUrl);
|
|
101
|
+
releases = JSON.parse(data.toString());
|
|
102
|
+
} catch (err) {
|
|
103
|
+
error(`Failed to fetch releases: ${err.message}\n URL: ${apiUrl}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!releases || releases.length === 0) {
|
|
107
|
+
error(`No releases found. Check: https://github.com/${REPO}/releases`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let release;
|
|
111
|
+
if (channel === "stable") {
|
|
112
|
+
release = releases.find((r) => !r.prerelease);
|
|
113
|
+
} else {
|
|
114
|
+
release = releases[0];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!release) {
|
|
118
|
+
error(`No releases found for channel '${channel}'`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return release.tag_name;
|
|
122
|
+
}
|
|
123
|
+
|
|
96
124
|
async function main() {
|
|
97
125
|
const platform = getPlatform();
|
|
98
|
-
const
|
|
99
|
-
const binaryName = `squirrel${getBinaryExtension()}`;
|
|
100
|
-
const binaryPath = path.join(binDir, binaryName);
|
|
126
|
+
const ext = getBinaryExtension();
|
|
101
127
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
128
|
+
// Write to npm package's bin/ directory (for squirrel.js wrapper fallback)
|
|
129
|
+
const binDir = path.join(__dirname, "..", "bin");
|
|
130
|
+
const binaryPath = path.join(binDir, `squirrel${ext}`);
|
|
131
|
+
|
|
132
|
+
// Determine version: pinned, channel-based, or package default
|
|
133
|
+
let version;
|
|
134
|
+
if (process.env.SQUIRREL_VERSION) {
|
|
135
|
+
version = process.env.SQUIRREL_VERSION;
|
|
136
|
+
log(`Installing pinned version: ${version}`);
|
|
137
|
+
} else {
|
|
138
|
+
const channel = process.env.SQUIRREL_CHANNEL || "stable";
|
|
139
|
+
version = await getLatestVersion(channel);
|
|
140
|
+
log(`Latest version: ${version} (channel: ${channel})`);
|
|
107
141
|
}
|
|
108
142
|
|
|
143
|
+
log(`Installing squirrelscan ${version} for ${platform}...`);
|
|
144
|
+
|
|
109
145
|
// Fetch manifest
|
|
110
|
-
const releaseUrl = `https://github.com/${REPO}/releases/download/${
|
|
146
|
+
const releaseUrl = `https://github.com/${REPO}/releases/download/${version}`;
|
|
111
147
|
const manifestUrl = `${releaseUrl}/manifest.json`;
|
|
112
148
|
|
|
113
149
|
info("Fetching manifest...");
|
|
@@ -122,7 +158,7 @@ async function main() {
|
|
|
122
158
|
// Get binary info for platform
|
|
123
159
|
const binaryInfo = manifest.binaries?.[platform];
|
|
124
160
|
if (!binaryInfo) {
|
|
125
|
-
error(`No binary available for platform: ${platform}\n See: https://github.com/${REPO}/releases/tag/${
|
|
161
|
+
error(`No binary available for platform: ${platform}\n See: https://github.com/${REPO}/releases/tag/${version}`);
|
|
126
162
|
}
|
|
127
163
|
|
|
128
164
|
const { filename, sha256: expectedSha256 } = binaryInfo;
|
|
@@ -146,7 +182,12 @@ async function main() {
|
|
|
146
182
|
}
|
|
147
183
|
info(`Checksum verified: ${expectedSha256.slice(0, 16)}...`);
|
|
148
184
|
|
|
149
|
-
//
|
|
185
|
+
// Ensure bin directory exists
|
|
186
|
+
if (!fs.existsSync(binDir)) {
|
|
187
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Write binary to npm's bin/ (serves as fallback for squirrel.js wrapper)
|
|
150
191
|
fs.writeFileSync(binaryPath, binaryData);
|
|
151
192
|
|
|
152
193
|
// Make executable (Unix only)
|
|
@@ -154,8 +195,21 @@ async function main() {
|
|
|
154
195
|
fs.chmodSync(binaryPath, 0o755);
|
|
155
196
|
}
|
|
156
197
|
|
|
198
|
+
// Run self install to set up native layout (~/.local/bin/squirrel, ~/.squirrel/releases/)
|
|
199
|
+
info("Running self install...");
|
|
200
|
+
const result = spawnSync(binaryPath, ["self", "install"], {
|
|
201
|
+
stdio: "inherit",
|
|
202
|
+
windowsHide: true,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Don't fail npm install if self install fails - binary still works via wrapper fallback
|
|
206
|
+
if (result.status !== 0) {
|
|
207
|
+
warn("Native install setup failed, but npm install succeeded");
|
|
208
|
+
warn("Run 'squirrel self install' manually for full functionality");
|
|
209
|
+
}
|
|
210
|
+
|
|
157
211
|
log("Installation complete!");
|
|
158
|
-
info(
|
|
212
|
+
info("Run 'squirrel --help' to get started");
|
|
159
213
|
}
|
|
160
214
|
|
|
161
215
|
main().catch((err) => {
|