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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **CLI Website Audits for Humans, Agents & LLMs**
6
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.
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. Pipe to AI
29
+ ### 2. AI Coding Agent Skill
30
30
 
31
- Pipe clean output to any AI assistant:
31
+ Install the skill for autonomous workflows:
32
32
 
33
33
  ```bash
34
- squirrel audit example.com --format text | claude
34
+ npx skills install squirrelscan/skills
35
35
  ```
36
36
 
37
- ### 3. AI Agent Skill
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 text | claude
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 platform-specific binary downloaded during npm install
5
+ * Executes the natively installed binary
6
6
  */
7
7
 
8
- const { execFileSync, spawnSync } = require("child_process");
8
+ const { spawnSync } = require("child_process");
9
9
  const path = require("path");
10
10
  const fs = require("fs");
11
- const { getBinaryExtension } = require("../lib/platform");
11
+ const os = require("os");
12
12
 
13
- const binaryName = `squirrel${getBinaryExtension()}`;
14
- const binaryPath = path.join(__dirname, binaryName);
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
- // Check if binary exists
17
- if (!fs.existsSync(binaryPath)) {
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 downloaded during npm install.");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squirrelscan",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "CLI website audits for humans, agents and LLMs",
5
5
  "bin": {
6
6
  "squirrel": "bin/squirrel.js"
@@ -2,18 +2,21 @@
2
2
 
3
3
  /**
4
4
  * postinstall script for squirrelscan npm package
5
- * Downloads the platform-specific binary from GitHub releases
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 of buffer
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 binDir = path.join(__dirname, "..", "bin");
99
- const binaryName = `squirrel${getBinaryExtension()}`;
100
- const binaryPath = path.join(binDir, binaryName);
126
+ const ext = getBinaryExtension();
101
127
 
102
- log(`Installing squirrelscan ${VERSION} for ${platform}...`);
103
-
104
- // Ensure bin directory exists
105
- if (!fs.existsSync(binDir)) {
106
- fs.mkdirSync(binDir, { recursive: true });
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/${VERSION}`;
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/${VERSION}`);
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
- // Write binary
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(`Binary: ${binaryPath}`);
212
+ info("Run 'squirrel --help' to get started");
159
213
  }
160
214
 
161
215
  main().catch((err) => {