ferret-scan 1.0.6 → 1.0.8

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/CHANGELOG.md CHANGED
@@ -48,4 +48,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
48
48
  - Compliance framework integration (SOC2, ISO27001)
49
49
  - Community rule marketplace
50
50
  - Advanced threat hunting capabilities
51
- - SIEM/SOAR integrations
51
+ - SIEM/SOAR integrations
52
+
53
+ ## [1.0.7] - 2026-02-01
54
+
55
+ ### Fixed
56
+ - Async file discovery for spinner animation during discovery phase
57
+ - Both "Discovering files..." and "Scanning..." spinners now animate smoothly
58
+
59
+ ## [1.0.6] - 2026-02-01
60
+
61
+ ### Fixed
62
+ - Async file reading (`fs.promises.readFile`) for spinner animation during scan phase
63
+ - Spinner now animates during file scanning on large codebases
64
+
65
+ ## [1.0.5] - 2026-02-01
66
+
67
+ ### Fixed
68
+ - Time-based yielding (every 100ms) for more reliable spinner updates
69
+
70
+ ## [1.0.4] - 2026-02-01
71
+
72
+ ### Fixed
73
+ - Yield on every file for spinner animation (intermediate fix)
74
+
75
+ ## [1.0.3] - 2026-02-01
76
+
77
+ ### Fixed
78
+ - Increased yield frequency for spinner updates
79
+
80
+ ## [1.0.2] - 2026-02-01
81
+
82
+ ### Added
83
+ - Progress indicators during scanning using ora spinner
84
+ - Real-time file count and findings display during scan
85
+ - TTY detection to disable spinners in CI environments
86
+
87
+ ## [1.0.1] - 2026-01-31
88
+
89
+ ### Fixed
90
+ - Repository URLs corrected from `ferret-security/ferret-scan` to `fubak/ferret-scan`
91
+ - Added `typescript` as production dependency (required at runtime for AST analysis)
@@ -19,6 +19,6 @@ interface DiscoveryResult {
19
19
  /**
20
20
  * Main file discovery function
21
21
  */
22
- export declare function discoverFiles(paths: string[], options: DiscoveryOptions): DiscoveryResult;
22
+ export declare function discoverFiles(paths: string[], options: DiscoveryOptions): Promise<DiscoveryResult>;
23
23
  export default discoverFiles;
24
24
  //# sourceMappingURL=FileDiscovery.d.ts.map
@@ -3,7 +3,8 @@
3
3
  * Scans directories for skills, agents, hooks, MCP configs, rules files, and other AI CLI files
4
4
  * Supports: Claude Code, Cursor, Windsurf, Continue, Aider, Cline, and generic AI configs
5
5
  */
6
- import { readdirSync, statSync, existsSync } from 'node:fs';
6
+ import { readdir, stat, access } from 'node:fs/promises';
7
+ import { constants } from 'node:fs';
7
8
  import { resolve, extname, basename, relative } from 'node:path';
8
9
  import { createIgnoreFilter, shouldIgnore } from '../utils/ignore.js';
9
10
  import logger from '../utils/logger.js';
@@ -136,10 +137,10 @@ function isAnalyzableFile(filePath) {
136
137
  /**
137
138
  * Recursively discover files in a directory
138
139
  */
139
- function discoverFilesInDirectory(dir, baseDir, ig, options, result) {
140
+ async function discoverFilesInDirectory(dir, baseDir, ig, options, result) {
140
141
  let entries;
141
142
  try {
142
- entries = readdirSync(dir);
143
+ entries = await readdir(dir);
143
144
  }
144
145
  catch (error) {
145
146
  const message = error instanceof Error ? error.message : String(error);
@@ -158,7 +159,7 @@ function discoverFilesInDirectory(dir, baseDir, ig, options, result) {
158
159
  }
159
160
  let stats;
160
161
  try {
161
- stats = statSync(fullPath);
162
+ stats = await stat(fullPath);
162
163
  }
163
164
  catch (error) {
164
165
  const message = error instanceof Error ? error.message : String(error);
@@ -167,7 +168,7 @@ function discoverFilesInDirectory(dir, baseDir, ig, options, result) {
167
168
  }
168
169
  if (stats.isDirectory()) {
169
170
  // Recurse into directory
170
- discoverFilesInDirectory(fullPath, baseDir, ig, options, result);
171
+ await discoverFilesInDirectory(fullPath, baseDir, ig, options, result);
171
172
  }
172
173
  else if (stats.isFile()) {
173
174
  // Check if file should be analyzed
@@ -199,17 +200,29 @@ function discoverFilesInDirectory(dir, baseDir, ig, options, result) {
199
200
  }
200
201
  }
201
202
  }
203
+ /**
204
+ * Check if path exists
205
+ */
206
+ async function pathExists(filePath) {
207
+ try {
208
+ await access(filePath, constants.F_OK);
209
+ return true;
210
+ }
211
+ catch {
212
+ return false;
213
+ }
214
+ }
202
215
  /**
203
216
  * Discover a single file
204
217
  */
205
- function discoverSingleFile(filePath, options, result) {
206
- if (!existsSync(filePath)) {
218
+ async function discoverSingleFile(filePath, options, result) {
219
+ if (!(await pathExists(filePath))) {
207
220
  result.errors.push({ path: filePath, error: 'File does not exist' });
208
221
  return;
209
222
  }
210
223
  let stats;
211
224
  try {
212
- stats = statSync(filePath);
225
+ stats = await stat(filePath);
213
226
  }
214
227
  catch (error) {
215
228
  const message = error instanceof Error ? error.message : String(error);
@@ -242,7 +255,7 @@ function discoverSingleFile(filePath, options, result) {
242
255
  /**
243
256
  * Main file discovery function
244
257
  */
245
- export function discoverFiles(paths, options) {
258
+ export async function discoverFiles(paths, options) {
246
259
  const result = {
247
260
  files: [],
248
261
  skipped: 0,
@@ -254,18 +267,18 @@ export function discoverFiles(paths, options) {
254
267
  }
255
268
  for (const inputPath of paths) {
256
269
  const resolvedPath = resolve(inputPath);
257
- if (!existsSync(resolvedPath)) {
270
+ if (!(await pathExists(resolvedPath))) {
258
271
  logger.warn(`Path does not exist: ${resolvedPath}`);
259
272
  result.errors.push({ path: resolvedPath, error: 'Path does not exist' });
260
273
  continue;
261
274
  }
262
- const stats = statSync(resolvedPath);
275
+ const stats = await stat(resolvedPath);
263
276
  if (stats.isDirectory()) {
264
277
  const ig = createIgnoreFilter(resolvedPath, options.ignore);
265
- discoverFilesInDirectory(resolvedPath, resolvedPath, ig, options, result);
278
+ await discoverFilesInDirectory(resolvedPath, resolvedPath, ig, options, result);
266
279
  }
267
280
  else if (stats.isFile()) {
268
- discoverSingleFile(resolvedPath, options, result);
281
+ await discoverSingleFile(resolvedPath, options, result);
269
282
  }
270
283
  }
271
284
  // Sort files by component type, then by path
@@ -188,7 +188,7 @@ export async function scan(config) {
188
188
  if (showProgress) {
189
189
  spinner = ora('Discovering files...').start();
190
190
  }
191
- const discovery = discoverFiles(config.paths, {
191
+ const discovery = await discoverFiles(config.paths, {
192
192
  maxFileSize: config.maxFileSize,
193
193
  ignore: config.ignore,
194
194
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ferret-scan",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Security scanner for AI CLI configurations - detect prompt injections, credential leaks, and malicious patterns in AI agent configs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",