skylos 2.1.1__tar.gz → 2.2.2__tar.gz

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.

Potentially problematic release.


This version of skylos might be problematic. Click here for more details.

Files changed (48) hide show
  1. {skylos-2.1.1 → skylos-2.2.2}/PKG-INFO +1 -1
  2. {skylos-2.1.1 → skylos-2.2.2}/README.md +222 -3
  3. {skylos-2.1.1 → skylos-2.2.2}/pyproject.toml +1 -1
  4. {skylos-2.1.1 → skylos-2.2.2}/setup.py +1 -1
  5. skylos-2.2.2/skylos/__init__.py +10 -0
  6. {skylos-2.1.1 → skylos-2.2.2}/skylos/analyzer.py +54 -9
  7. {skylos-2.1.1 → skylos-2.2.2}/skylos/cli.py +107 -22
  8. skylos-2.2.2/skylos/codemods.py +238 -0
  9. {skylos-2.1.1 → skylos-2.2.2}/skylos/constants.py +2 -3
  10. skylos-2.2.2/skylos/rules/secrets.py +268 -0
  11. {skylos-2.1.1 → skylos-2.2.2}/skylos/server.py +1 -12
  12. {skylos-2.1.1 → skylos-2.2.2}/skylos/visitor.py +97 -24
  13. {skylos-2.1.1 → skylos-2.2.2}/skylos.egg-info/PKG-INFO +1 -1
  14. {skylos-2.1.1 → skylos-2.2.2}/skylos.egg-info/SOURCES.txt +7 -2
  15. skylos-2.2.2/test/sample_repo/__init__.py +0 -0
  16. skylos-2.2.2/test/sample_repo/sample_repo/__init__.py +0 -0
  17. {skylos-2.1.1 → skylos-2.2.2}/test/test_analyzer.py +6 -43
  18. skylos-2.2.2/test/test_new_behaviours.py +52 -0
  19. skylos-2.2.2/test/test_secrets.py +179 -0
  20. skylos-2.1.1/skylos/__init__.py +0 -8
  21. skylos-2.1.1/skylos/codemods.py +0 -89
  22. {skylos-2.1.1 → skylos-2.2.2}/setup.cfg +0 -0
  23. {skylos-2.1.1/test → skylos-2.2.2/skylos/rules}/__init__.py +0 -0
  24. {skylos-2.1.1/test/sample_repo → skylos-2.2.2/skylos/visitors}/__init__.py +0 -0
  25. {skylos-2.1.1/skylos → skylos-2.2.2/skylos/visitors}/framework_aware.py +0 -0
  26. {skylos-2.1.1/skylos → skylos-2.2.2/skylos/visitors}/test_aware.py +0 -0
  27. {skylos-2.1.1 → skylos-2.2.2}/skylos.egg-info/dependency_links.txt +0 -0
  28. {skylos-2.1.1 → skylos-2.2.2}/skylos.egg-info/entry_points.txt +0 -0
  29. {skylos-2.1.1 → skylos-2.2.2}/skylos.egg-info/requires.txt +0 -0
  30. {skylos-2.1.1 → skylos-2.2.2}/skylos.egg-info/top_level.txt +0 -0
  31. {skylos-2.1.1/test/sample_repo/sample_repo → skylos-2.2.2/test}/__init__.py +0 -0
  32. {skylos-2.1.1 → skylos-2.2.2}/test/compare_tools.py +0 -0
  33. {skylos-2.1.1 → skylos-2.2.2}/test/conftest.py +0 -0
  34. {skylos-2.1.1 → skylos-2.2.2}/test/diagnostics.py +0 -0
  35. {skylos-2.1.1 → skylos-2.2.2}/test/sample_repo/app.py +0 -0
  36. {skylos-2.1.1 → skylos-2.2.2}/test/sample_repo/sample_repo/commands.py +0 -0
  37. {skylos-2.1.1 → skylos-2.2.2}/test/sample_repo/sample_repo/models.py +0 -0
  38. {skylos-2.1.1 → skylos-2.2.2}/test/sample_repo/sample_repo/routes.py +0 -0
  39. {skylos-2.1.1 → skylos-2.2.2}/test/sample_repo/sample_repo/utils.py +0 -0
  40. {skylos-2.1.1 → skylos-2.2.2}/test/test_changes_analyzer.py +0 -0
  41. {skylos-2.1.1 → skylos-2.2.2}/test/test_cli.py +0 -0
  42. {skylos-2.1.1 → skylos-2.2.2}/test/test_codemods.py +0 -0
  43. {skylos-2.1.1 → skylos-2.2.2}/test/test_constants.py +0 -0
  44. {skylos-2.1.1 → skylos-2.2.2}/test/test_framework_aware.py +0 -0
  45. {skylos-2.1.1 → skylos-2.2.2}/test/test_integration.py +0 -0
  46. {skylos-2.1.1 → skylos-2.2.2}/test/test_skylos.py +0 -0
  47. {skylos-2.1.1 → skylos-2.2.2}/test/test_test_aware.py +0 -0
  48. {skylos-2.1.1 → skylos-2.2.2}/test/test_visitor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skylos
3
- Version: 2.1.1
3
+ Version: 2.2.2
4
4
  Summary: A static analysis tool for Python codebases
5
5
  Author-email: oha <aaronoh2015@gmail.com>
6
6
  Requires-Python: >=3.9
@@ -32,6 +32,7 @@
32
32
  - [Example Output](#example-output)
33
33
  - [Interactive Mode](#interactive-mode)
34
34
  - [Development](#development)
35
+ - [CI/CD (Pre-commit & GitHub Actions)](#cicd-pre-commit--github-actions)
35
36
  - [FAQ](#faq)
36
37
  - [Limitations](#limitations)
37
38
  - [Troubleshooting](#troubleshooting)
@@ -51,6 +52,8 @@
51
52
  * **Unused Imports**: Identifies imports that are not used
52
53
  * **Folder Management**: Inclusion/exclusion of directories
53
54
  * **Ignore Pragmas**: Skip lines tagged with `# pragma: no skylos`, `# pragma: no cover`, or `# noqa`
55
+ **NEW** **Secrets Scanning (PoC, opt-in)**: Detects API keys & secrets (GitHub, GitLab, Slack, Stripe, AWS, Google, SendGrid, Twilio, private key blocks)
56
+
54
57
 
55
58
  ## Benchmark (You can find this benchmark test in `test` folder)
56
59
 
@@ -94,12 +97,17 @@ pip install .
94
97
  ```bash
95
98
  skylos /path/to/your/project
96
99
 
100
+ skylos /path/to/your/project --secrets ## include api key scan
101
+
97
102
  # To launch the front end
98
103
  skylos run
99
104
 
100
105
  # Interactive mode - select items to remove
101
106
  skylos --interactive /path/to/your/project
102
107
 
108
+ # Comment out items
109
+ skylos . --interactive --comment-out
110
+
103
111
  # Dry run - see what would be removed
104
112
  skylos --interactive --dry-run /path/to/your/project
105
113
 
@@ -193,7 +201,7 @@ When Skylos detects a test file, it by default, will apply a confidence penalty
193
201
 
194
202
  ## Ignoring Pragmas
195
203
 
196
- To ignore any warning, indicate `# pragma: no skylos` **ON THE SAME LINE** as the function/class you want to ignore
204
+ 1. To ignore any warning, indicate `# pragma: no skylos` **ON THE SAME LINE** as the function/class you want to ignore
197
205
 
198
206
  Example
199
207
 
@@ -203,6 +211,14 @@ Example
203
211
  return new_path
204
212
  ```
205
213
 
214
+ 2. To suppress a **secret** on a line, add: `# skylos: ignore[SKY-S101]`
215
+
216
+ Example
217
+ ```python
218
+
219
+ API_KEY = "ghp_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" # skylos: ignore[SKY-S101]
220
+ ```
221
+
206
222
  ## Including & Excluding Files
207
223
 
208
224
  ### Default Exclusions
@@ -244,6 +260,7 @@ Options:
244
260
  --no-default-excludes Don't exclude default folders (__pycache__, .git, venv, etc.)
245
261
  --list-default-excludes List the default excluded folders and
246
262
  -c, --confidence LEVEL Confidence threshold (0-100). Lower values will show more items.
263
+ -- secrets Scan for api keys/secrets
247
264
  ```
248
265
 
249
266
  ## Interactive Mode
@@ -254,6 +271,206 @@ The interactive mode lets you select specific functions and imports to remove:
254
271
  2. **Confirm changes**: Review selected items before applying
255
272
  3. **Auto-cleanup**: Files are automatically updated
256
273
 
274
+ ## CI/CD (Pre-commit & GitHub Actions)
275
+
276
+ Pick **one** (or use **both**)
277
+
278
+ 1. Pre-commit (local + CI): runs Skylos before commits/PRs.
279
+ - You must install pre-commit locally once. Skylos gets installed automatically by the hook.
280
+
281
+ 2. GitHub Actions: runs Skylos on pushes/PRs in CI.
282
+ - No local install needed
283
+
284
+ ### Option A — Pre-commit (local + CI)
285
+
286
+ 1. Create or edit `.pre-commit-config.yaml` at the repo root:
287
+
288
+ **A: Skylos hook repo**
289
+ ```yaml
290
+ ## .pre-commit-config.yaml
291
+ repos:
292
+ - repo: https://github.com/duriantaco/skylos
293
+ rev: v2.2.2
294
+ hooks:
295
+ - id: skylos-scan
296
+ name: skylos report
297
+ entry: python -m skylos.cli
298
+ language: python
299
+ types_or: [python]
300
+ pass_filenames: false
301
+ require_serial: true
302
+ args: [".", "--output", "report.json", "--confidence", "70"]
303
+
304
+ - repo: local
305
+ hooks:
306
+ - id: skylos-fail-on-findings
307
+ name: skylos
308
+ env:
309
+ SKYLOS_SOFT: "1"
310
+ language: python
311
+ language_version: python3
312
+ pass_filenames: false
313
+ require_serial: true
314
+ entry: >
315
+ python -c "import os, json, sys, pathlib;
316
+ p=pathlib.Path('report.json');
317
+
318
+ if not p.exists():
319
+ sys.exit(0);
320
+
321
+ data=json.loads(p.read_text(encoding='utf-8'));
322
+
323
+ count = 0
324
+ for v in data.values():
325
+ if isinstance(v, list):
326
+ count += len(v)
327
+
328
+ print(f'[skylos] findings: {count}');
329
+ sys.exit(0 if os.getenv('SKYLOS_SOFT') or count==0 else 1)"
330
+ ```
331
+ **B: self-contained local hook**
332
+
333
+ ```yaml
334
+ repos:
335
+ - repo: local
336
+ hooks:
337
+ - id: skylos-scan
338
+ name: skylos report
339
+ language: python
340
+ entry: python -m skylos.cli
341
+ pass_filenames: false
342
+ require_serial: true
343
+ additional_dependencies: [skylos==2.2.2]
344
+ args: [".", "--output", "report.json", "--confidence", "70"]
345
+
346
+ - id: skylos-fail-on-findings
347
+ name: skylos (soft)
348
+ language: python
349
+ language_version: python3
350
+ pass_filenames: false
351
+ require_serial: true
352
+ entry: >
353
+ python -c "import os, json, sys, pathlib;
354
+ p=pathlib.Path('report.json');
355
+
356
+ if not p.exists():
357
+ sys.exit(0);
358
+
359
+ data=json.loads(p.read_text(encoding='utf-8'));
360
+
361
+ count = 0
362
+ for v in data.values():
363
+ if isinstance(v, list):
364
+ count += len(v)
365
+
366
+ print(f'[skylos] findings: {count}');
367
+ sys.exit(0 if os.getenv('SKYLOS_SOFT') or count==0 else 1)"
368
+ ```
369
+
370
+ **Install requirements:**
371
+
372
+ You must install pre-commit locally once:
373
+ ```bash
374
+ pip install pre-commit
375
+ pre-commit install
376
+ ```
377
+
378
+ 2. pre-commit run --all-files
379
+
380
+
381
+ 3. Run the same hooks in CI (GitHub Actions): create .github/workflows/pre-commit.yml:
382
+
383
+ ```yaml
384
+ name: pre-commit
385
+ on: [push, pull_request]
386
+ jobs:
387
+ run:
388
+ runs-on: ubuntu-latest
389
+ steps:
390
+ - uses: actions/checkout@v4
391
+ - uses: actions/setup-python@v5
392
+ with: { python-version: "3.11", cache: "pip" }
393
+ - uses: pre-commit/action@v3.0.1
394
+ with: { extra_args: --all-files }
395
+ ```
396
+
397
+ **Pre commit behavior:** the second hook is soft by default (SKYLOS_SOFT=1). This means that it prints findings and passes. You can remove the env/logic if you want pre-commit to block commits on finding
398
+
399
+ ### Option B — Github Actions
400
+
401
+ 1. Create .github/workflows/skylos.yml:
402
+
403
+ ```yaml
404
+ name: Skylos Deadcode Scan
405
+
406
+ on:
407
+ pull_request:
408
+ push:
409
+ branches: [ main, master ]
410
+ workflow_dispatch:
411
+
412
+ jobs:
413
+ scan:
414
+ runs-on: ubuntu-latest
415
+ env:
416
+ SKYLOS_STRICT: ${{ vars.SKYLOS_STRICT || 'false' }}
417
+ steps:
418
+ - uses: actions/checkout@v4
419
+
420
+ - uses: actions/setup-python@v5
421
+ with:
422
+ python-version: '3.11'
423
+ cache: 'pip'
424
+
425
+ - name: Install Skylos
426
+ run: pip install skylos
427
+
428
+ - name: Run Skylos
429
+ env:
430
+ REPORT: skylos_${{ github.run_number }}_${{ github.sha }}.json
431
+ run: |
432
+ echo "REPORT=$REPORT" >> "$GITHUB_OUTPUT"
433
+ skylos . --json > "$REPORT"
434
+ id: scan
435
+
436
+ - name: Fail if there are findings
437
+ continue-on-error: ${{ env.SKYLOS_STRICT != 'true' }}
438
+ env:
439
+ REPORT: ${{ steps.scan.outputs.REPORT }}
440
+ run: |
441
+ python - << 'PY'
442
+ import json, sys, os
443
+ report = os.environ["REPORT"]
444
+ data = json.load(open(report, "r", encoding="utf-8"))
445
+ count = 0
446
+ for value in data.values():
447
+ if isinstance(value, list):
448
+ count += len(value)
449
+ print(f"Findings: {count}")
450
+ if count > 0:
451
+ print(f"::warning title=Skylos findings::{count} potential issues found. See {report}")
452
+ sys.exit(1 if count > 0 else 0)
453
+ PY
454
+
455
+ - name: Upload report artifact
456
+ if: always()
457
+ uses: actions/upload-artifact@v4
458
+ with:
459
+ name: ${{ steps.scan.outputs.REPORT }}
460
+ path: ${{ steps.scan.outputs.REPORT }}
461
+
462
+ - name: Summarize in job log
463
+ if: always()
464
+ run: |
465
+ echo "Skylos report: ${{ steps.scan.outputs.REPORT }}" >> $GITHUB_STEP_SUMMARY
466
+ ```
467
+
468
+ **To make the job fail on findings (strict mode)**:
469
+
470
+ 1. Go to GitHub -> Settings -> Secrets and variables -> Actions -> Variables
471
+
472
+ 2. Add variable SKYLOS_STRICT with value true
473
+
257
474
  ## Development
258
475
 
259
476
  ### Prerequisites
@@ -307,6 +524,7 @@ A: Start with 60 (default) for safe cleanup. Use 30 for framework applications.
307
524
  - **Frameworks**: Django models, Flask, FastAPI routes may appear unused but aren't
308
525
  - **Test data**: Limited scenarios, your mileage may vary
309
526
  - **False positives**: Always manually review before deleting code
527
+ - **Secrets PoC**: May emit both a provider hit and a generic high-entropy hit for the same token. All tokens are detected only in py files (`.py`, `.pyi`, `.pyw`).
310
528
 
311
529
  ## Troubleshooting
312
530
 
@@ -339,9 +557,10 @@ We welcome contributions! Please read our [Contributing Guidelines](CONTRIBUTING
339
557
  ## Roadmap
340
558
  - [x] Expand our test cases
341
559
  - [ ] Configuration file support
342
- - [ ] Git hooks integration
343
- - [ ] CI/CD integration examples
560
+ - [x] Git hooks integration
561
+ - [x] CI/CD integration examples
344
562
  - [ ] Further optimization
563
+ - [ ] Add new rules
345
564
 
346
565
  ## License
347
566
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "skylos"
7
- version = "2.1.1"
7
+ version = "2.2.2"
8
8
  requires-python = ">=3.9"
9
9
  description = "A static analysis tool for Python codebases"
10
10
  authors = [{name = "oha", email = "aaronoh2015@gmail.com"}]
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="skylos",
5
- version="2.1.1",
5
+ version="2.2.2",
6
6
  packages=find_packages(),
7
7
  python_requires=">=3.9",
8
8
  install_requires=[
@@ -0,0 +1,10 @@
1
+ __version__ = "2.2.2"
2
+
3
+ def analyze(*args, **kwargs):
4
+ from .analyzer import analyze as _analyze
5
+ return _analyze(*args, **kwargs)
6
+
7
+ def debug_test():
8
+ return "debug-ok"
9
+
10
+ __all__ = ["analyze", "debug_test", "__version__"]
@@ -7,10 +7,11 @@ from pathlib import Path
7
7
  from collections import defaultdict
8
8
  from skylos.visitor import Visitor
9
9
  from skylos.constants import ( PENALTIES, AUTO_CALLED )
10
- from skylos.test_aware import TestAwareVisitor
10
+ from skylos.visitors.test_aware import TestAwareVisitor
11
+ from skylos.rules.secrets import scan_ctx as _secrets_scan_ctx
11
12
  import os
12
13
  import traceback
13
- from skylos.framework_aware import FrameworkAwareVisitor, detect_framework_usage
14
+ from skylos.visitors.framework_aware import FrameworkAwareVisitor, detect_framework_usage
14
15
 
15
16
  logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s')
16
17
  logger=logging.getLogger('Skylos')
@@ -155,18 +156,40 @@ class Skylos:
155
156
 
156
157
  def _apply_penalties(self, def_obj, visitor, framework):
157
158
  confidence=100
159
+
160
+ if getattr(visitor, "ignore_lines", None) and def_obj.line in visitor.ignore_lines:
161
+ def_obj.confidence = 0
162
+ return
163
+
164
+ if def_obj.type == "variable" and def_obj.simple_name == "_":
165
+ def_obj.confidence = 0
166
+ return
167
+
158
168
  if def_obj.simple_name.startswith("_") and not def_obj.simple_name.startswith("__"):
159
169
  confidence -= PENALTIES["private_name"]
170
+
160
171
  if def_obj.simple_name.startswith("__") and def_obj.simple_name.endswith("__"):
161
172
  confidence -= PENALTIES["dunder_or_magic"]
162
- if def_obj.type == "variable" and def_obj.simple_name.isupper():
163
- confidence = 0
173
+
164
174
  if def_obj.in_init and def_obj.type in ("function", "class"):
165
175
  confidence -= PENALTIES["in_init_file"]
176
+
166
177
  if def_obj.name.split(".")[0] in self.dynamic:
167
178
  confidence -= PENALTIES["dynamic_module"]
179
+
168
180
  if visitor.is_test_file or def_obj.line in visitor.test_decorated_lines:
169
181
  confidence -= PENALTIES["test_related"]
182
+
183
+ if def_obj.type == "variable" and getattr(framework, "dataclass_fields", None):
184
+ if def_obj.name in framework.dataclass_fields:
185
+ def_obj.confidence = 0
186
+ return
187
+
188
+ if def_obj.type == "variable":
189
+ fr = getattr(framework, "first_read_lineno", {}).get(def_obj.name)
190
+ if fr is not None and fr >= def_obj.line:
191
+ def_obj.confidence = 0
192
+ return
170
193
 
171
194
  framework_confidence = detect_framework_usage(def_obj, visitor=framework)
172
195
  if framework_confidence is not None:
@@ -215,7 +238,7 @@ class Skylos:
215
238
  if method.simple_name == "format" and cls.endswith("Formatter"):
216
239
  method.references += 1
217
240
 
218
- def analyze(self, path, thr=60, exclude_folders=None):
241
+ def analyze(self, path, thr=60, exclude_folders= None, enable_secrets = False):
219
242
  files, root = self._get_python_files(path, exclude_folders)
220
243
 
221
244
  if not files:
@@ -238,6 +261,7 @@ class Skylos:
238
261
  for f in files:
239
262
  modmap[f] = self._module(root, f)
240
263
 
264
+ all_secrets = []
241
265
  for file in files:
242
266
  mod = modmap[file]
243
267
  defs, refs, dyn, exports, test_flags, framework_flags = proc_file(file, mod)
@@ -250,6 +274,16 @@ class Skylos:
250
274
  self.dynamic.update(dyn)
251
275
  self.exports[mod].update(exports)
252
276
 
277
+ if enable_secrets and _secrets_scan_ctx is not None:
278
+ try:
279
+ src_lines = Path(file).read_text(encoding="utf-8", errors="ignore").splitlines(True)
280
+ ctx = {"relpath": str(file), "lines": src_lines, "tree": None}
281
+ findings = list(_secrets_scan_ctx(ctx))
282
+ if findings:
283
+ all_secrets.extend(findings)
284
+ except Exception:
285
+ pass
286
+
253
287
  self._mark_refs()
254
288
  self._apply_heuristics()
255
289
  self._mark_exports()
@@ -262,7 +296,7 @@ class Skylos:
262
296
  for d in sorted(self.defs.values(), key=def_sort_key):
263
297
  if shown >= 50:
264
298
  break
265
- print(f" {d.type:<8} refs={d.references:<2} conf={d.confidence:<3} exported={d.is_exported} line={d.line:<4} {d.name}")
299
+ print(f" type={d.type} refs={d.references} conf={d.confidence} exported={d.is_exported} line={d.line} name={d.name}")
266
300
  shown += 1
267
301
 
268
302
  unused = []
@@ -281,6 +315,9 @@ class Skylos:
281
315
  "excluded_folders": exclude_folders or [],
282
316
  }
283
317
  }
318
+
319
+ if enable_secrets and all_secrets:
320
+ result["secrets"] = all_secrets
284
321
 
285
322
  for u in unused:
286
323
  if u["type"] in ("function", "method"):
@@ -304,10 +341,13 @@ def proc_file(file_or_args, mod=None):
304
341
 
305
342
  try:
306
343
  source = Path(file).read_text(encoding="utf-8")
307
- tree = ast.parse(source)
344
+ ignore_lines = {i for i, line in enumerate(source.splitlines(), start=1)
345
+ if "pragma: no skylos" in line}
346
+ tree = ast.parse(source)
308
347
 
309
348
  tv = TestAwareVisitor(filename=file)
310
349
  tv.visit(tree)
350
+ tv.ignore_lines = ignore_lines
311
351
 
312
352
  fv = FrameworkAwareVisitor(filename=file)
313
353
  fv.visit(tree)
@@ -315,18 +355,23 @@ def proc_file(file_or_args, mod=None):
315
355
  v = Visitor(mod, file)
316
356
  v.visit(tree)
317
357
 
358
+ fv.dataclass_fields = getattr(v, "dataclass_fields", set())
359
+ fv.first_read_lineno = getattr(v, "first_read_lineno", {})
360
+
318
361
  return v.defs, v.refs, v.dyn, v.exports, tv, fv
362
+
319
363
  except Exception as e:
320
364
  logger.error(f"{file}: {e}")
321
365
  if os.getenv("SKYLOS_DEBUG"):
322
366
  logger.error(traceback.format_exc())
323
367
  dummy_visitor = TestAwareVisitor(filename=file)
368
+ dummy_visitor.ignore_lines = set()
324
369
  dummy_framework_visitor = FrameworkAwareVisitor(filename=file)
325
370
 
326
371
  return [], [], set(), set(), dummy_visitor, dummy_framework_visitor
327
372
 
328
- def analyze(path,conf=60, exclude_folders=None):
329
- return Skylos().analyze(path,conf, exclude_folders)
373
+ def analyze(path, conf=60, exclude_folders=None, enable_secrets=False):
374
+ return Skylos().analyze(path,conf, exclude_folders, enable_secrets)
330
375
 
331
376
  if __name__ == "__main__":
332
377
  if len(sys.argv)>1: