lucidscan 0.5.12__py3-none-any.whl

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.
Files changed (91) hide show
  1. lucidscan/__init__.py +12 -0
  2. lucidscan/bootstrap/__init__.py +26 -0
  3. lucidscan/bootstrap/paths.py +160 -0
  4. lucidscan/bootstrap/platform.py +111 -0
  5. lucidscan/bootstrap/validation.py +76 -0
  6. lucidscan/bootstrap/versions.py +119 -0
  7. lucidscan/cli/__init__.py +50 -0
  8. lucidscan/cli/__main__.py +8 -0
  9. lucidscan/cli/arguments.py +405 -0
  10. lucidscan/cli/commands/__init__.py +64 -0
  11. lucidscan/cli/commands/autoconfigure.py +294 -0
  12. lucidscan/cli/commands/help.py +69 -0
  13. lucidscan/cli/commands/init.py +656 -0
  14. lucidscan/cli/commands/list_scanners.py +59 -0
  15. lucidscan/cli/commands/scan.py +307 -0
  16. lucidscan/cli/commands/serve.py +142 -0
  17. lucidscan/cli/commands/status.py +84 -0
  18. lucidscan/cli/commands/validate.py +105 -0
  19. lucidscan/cli/config_bridge.py +152 -0
  20. lucidscan/cli/exit_codes.py +17 -0
  21. lucidscan/cli/runner.py +284 -0
  22. lucidscan/config/__init__.py +29 -0
  23. lucidscan/config/ignore.py +178 -0
  24. lucidscan/config/loader.py +431 -0
  25. lucidscan/config/models.py +316 -0
  26. lucidscan/config/validation.py +645 -0
  27. lucidscan/core/__init__.py +3 -0
  28. lucidscan/core/domain_runner.py +463 -0
  29. lucidscan/core/git.py +174 -0
  30. lucidscan/core/logging.py +34 -0
  31. lucidscan/core/models.py +207 -0
  32. lucidscan/core/streaming.py +340 -0
  33. lucidscan/core/subprocess_runner.py +164 -0
  34. lucidscan/detection/__init__.py +21 -0
  35. lucidscan/detection/detector.py +154 -0
  36. lucidscan/detection/frameworks.py +270 -0
  37. lucidscan/detection/languages.py +328 -0
  38. lucidscan/detection/tools.py +229 -0
  39. lucidscan/generation/__init__.py +15 -0
  40. lucidscan/generation/config_generator.py +275 -0
  41. lucidscan/generation/package_installer.py +330 -0
  42. lucidscan/mcp/__init__.py +20 -0
  43. lucidscan/mcp/formatter.py +510 -0
  44. lucidscan/mcp/server.py +297 -0
  45. lucidscan/mcp/tools.py +1049 -0
  46. lucidscan/mcp/watcher.py +237 -0
  47. lucidscan/pipeline/__init__.py +17 -0
  48. lucidscan/pipeline/executor.py +187 -0
  49. lucidscan/pipeline/parallel.py +181 -0
  50. lucidscan/plugins/__init__.py +40 -0
  51. lucidscan/plugins/coverage/__init__.py +28 -0
  52. lucidscan/plugins/coverage/base.py +160 -0
  53. lucidscan/plugins/coverage/coverage_py.py +454 -0
  54. lucidscan/plugins/coverage/istanbul.py +411 -0
  55. lucidscan/plugins/discovery.py +107 -0
  56. lucidscan/plugins/enrichers/__init__.py +61 -0
  57. lucidscan/plugins/enrichers/base.py +63 -0
  58. lucidscan/plugins/linters/__init__.py +26 -0
  59. lucidscan/plugins/linters/base.py +125 -0
  60. lucidscan/plugins/linters/biome.py +448 -0
  61. lucidscan/plugins/linters/checkstyle.py +393 -0
  62. lucidscan/plugins/linters/eslint.py +368 -0
  63. lucidscan/plugins/linters/ruff.py +498 -0
  64. lucidscan/plugins/reporters/__init__.py +45 -0
  65. lucidscan/plugins/reporters/base.py +30 -0
  66. lucidscan/plugins/reporters/json_reporter.py +79 -0
  67. lucidscan/plugins/reporters/sarif_reporter.py +303 -0
  68. lucidscan/plugins/reporters/summary_reporter.py +61 -0
  69. lucidscan/plugins/reporters/table_reporter.py +81 -0
  70. lucidscan/plugins/scanners/__init__.py +57 -0
  71. lucidscan/plugins/scanners/base.py +60 -0
  72. lucidscan/plugins/scanners/checkov.py +484 -0
  73. lucidscan/plugins/scanners/opengrep.py +464 -0
  74. lucidscan/plugins/scanners/trivy.py +492 -0
  75. lucidscan/plugins/test_runners/__init__.py +27 -0
  76. lucidscan/plugins/test_runners/base.py +111 -0
  77. lucidscan/plugins/test_runners/jest.py +381 -0
  78. lucidscan/plugins/test_runners/karma.py +481 -0
  79. lucidscan/plugins/test_runners/playwright.py +434 -0
  80. lucidscan/plugins/test_runners/pytest.py +598 -0
  81. lucidscan/plugins/type_checkers/__init__.py +27 -0
  82. lucidscan/plugins/type_checkers/base.py +106 -0
  83. lucidscan/plugins/type_checkers/mypy.py +355 -0
  84. lucidscan/plugins/type_checkers/pyright.py +313 -0
  85. lucidscan/plugins/type_checkers/typescript.py +280 -0
  86. lucidscan-0.5.12.dist-info/METADATA +242 -0
  87. lucidscan-0.5.12.dist-info/RECORD +91 -0
  88. lucidscan-0.5.12.dist-info/WHEEL +5 -0
  89. lucidscan-0.5.12.dist-info/entry_points.txt +34 -0
  90. lucidscan-0.5.12.dist-info/licenses/LICENSE +201 -0
  91. lucidscan-0.5.12.dist-info/top_level.txt +1 -0
lucidscan/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ """
2
+ lucidscan package root.
3
+
4
+ This module currently only exposes version metadata. Core functionality lives in
5
+ subpackages such as `core`, `schema`, and `scanners`.
6
+ """
7
+
8
+ __all__ = ["__version__"]
9
+
10
+ __version__ = "0.3.0"
11
+
12
+
@@ -0,0 +1,26 @@
1
+ """
2
+ Bootstrap module for lucidscan plugin binary management.
3
+
4
+ This module handles:
5
+ - Platform detection (OS + architecture)
6
+ - Plugin binary directory management (~/.lucidscan/bin/)
7
+ - Binary validation utilities
8
+
9
+ Each scanner plugin is responsible for downloading its own binary
10
+ using the utilities provided by this module.
11
+ """
12
+
13
+ from lucidscan.bootstrap.platform import get_platform_info, PlatformInfo
14
+ from lucidscan.bootstrap.paths import get_lucidscan_home, LucidscanPaths
15
+ from lucidscan.bootstrap.validation import validate_binary, PluginValidationResult, ToolStatus
16
+
17
+ __all__ = [
18
+ "get_platform_info",
19
+ "PlatformInfo",
20
+ "get_lucidscan_home",
21
+ "LucidscanPaths",
22
+ "validate_binary",
23
+ "PluginValidationResult",
24
+ "ToolStatus",
25
+ ]
26
+
@@ -0,0 +1,160 @@
1
+ """Path management for lucidscan plugin binary cache.
2
+
3
+ Handles the .lucidscan directory structure and path resolution.
4
+ Each scanner plugin manages its own binary under .lucidscan/bin/{tool}/{version}/.
5
+
6
+ By default, tools are stored in the project root under .lucidscan/.
7
+ The LUCIDSCAN_HOME environment variable can override this for global installations.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+ from typing import ClassVar, Optional
16
+
17
+ # Default directory name
18
+ DEFAULT_HOME_DIR_NAME = ".lucidscan"
19
+
20
+ # Environment variable to override home directory (for global installations)
21
+ LUCIDSCAN_HOME_ENV = "LUCIDSCAN_HOME"
22
+
23
+
24
+ def get_lucidscan_home(project_root: Optional[Path] = None) -> Path:
25
+ """Get the lucidscan home directory path.
26
+
27
+ Resolution order:
28
+ 1. LUCIDSCAN_HOME environment variable (if set) - for global installations
29
+ 2. {project_root}/.lucidscan (if project_root provided)
30
+ 3. {cwd}/.lucidscan (default)
31
+
32
+ Args:
33
+ project_root: Optional project root directory. If not provided,
34
+ uses current working directory.
35
+
36
+ Returns:
37
+ Path to the lucidscan home directory.
38
+ """
39
+ # Global override takes precedence
40
+ env_home = os.environ.get(LUCIDSCAN_HOME_ENV)
41
+ if env_home:
42
+ return Path(env_home)
43
+
44
+ # Use project root or current directory
45
+ if project_root:
46
+ return project_root / DEFAULT_HOME_DIR_NAME
47
+
48
+ return Path.cwd() / DEFAULT_HOME_DIR_NAME
49
+
50
+
51
+ @dataclass
52
+ class LucidscanPaths:
53
+ """Manages paths within the lucidscan home directory.
54
+
55
+ Directory structure (plugin-based):
56
+ {project}/.lucidscan/
57
+ bin/
58
+ trivy/{version}/trivy - Trivy binary
59
+ opengrep/{version}/opengrep - OpenGrep binary
60
+ checkov/{version}/venv/ - Checkov virtualenv
61
+ ruff/{version}/ruff - Ruff binary
62
+ cache/
63
+ trivy/ - Trivy vulnerability DB
64
+ config/ - Configuration files
65
+ logs/ - Debug/diagnostic logs
66
+ """
67
+
68
+ home: Path
69
+
70
+ # Subdirectory names
71
+ _BIN_DIR: ClassVar[str] = "bin"
72
+ _CACHE_DIR: ClassVar[str] = "cache"
73
+ _CONFIG_DIR: ClassVar[str] = "config"
74
+ _LOGS_DIR: ClassVar[str] = "logs"
75
+
76
+ @classmethod
77
+ def default(cls) -> "LucidscanPaths":
78
+ """Create paths from the default lucidscan home (cwd/.lucidscan)."""
79
+ return cls(get_lucidscan_home())
80
+
81
+ @classmethod
82
+ def for_project(cls, project_root: Path) -> "LucidscanPaths":
83
+ """Create paths for a specific project.
84
+
85
+ Args:
86
+ project_root: Project root directory.
87
+
88
+ Returns:
89
+ LucidscanPaths configured for the project.
90
+ """
91
+ return cls(get_lucidscan_home(project_root))
92
+
93
+ @property
94
+ def bin_dir(self) -> Path:
95
+ """Directory containing scanner plugin binaries."""
96
+ return self.home / self._BIN_DIR
97
+
98
+ @property
99
+ def cache_dir(self) -> Path:
100
+ """Directory for scanner caches."""
101
+ return self.home / self._CACHE_DIR
102
+
103
+ @property
104
+ def config_dir(self) -> Path:
105
+ """Directory for configuration files."""
106
+ return self.home / self._CONFIG_DIR
107
+
108
+ @property
109
+ def logs_dir(self) -> Path:
110
+ """Directory for log files."""
111
+ return self.home / self._LOGS_DIR
112
+
113
+ def plugin_bin_dir(self, plugin_name: str, version: str) -> Path:
114
+ """Get the binary directory for a specific plugin version.
115
+
116
+ Args:
117
+ plugin_name: Name of the plugin (e.g., 'trivy', 'opengrep').
118
+ version: Version string.
119
+
120
+ Returns:
121
+ Path to the plugin's version-specific binary directory.
122
+ """
123
+ return self.bin_dir / plugin_name / version
124
+
125
+ def plugin_cache_dir(self, plugin_name: str) -> Path:
126
+ """Get the cache directory for a specific plugin.
127
+
128
+ Args:
129
+ plugin_name: Name of the plugin.
130
+
131
+ Returns:
132
+ Path to the plugin's cache directory.
133
+ """
134
+ return self.cache_dir / plugin_name
135
+
136
+ def ensure_directories(self) -> None:
137
+ """Create all required directories if they don't exist."""
138
+ directories = [
139
+ self.home,
140
+ self.bin_dir,
141
+ self.cache_dir,
142
+ self.config_dir,
143
+ self.logs_dir,
144
+ ]
145
+ for directory in directories:
146
+ directory.mkdir(parents=True, exist_ok=True)
147
+
148
+ def is_initialized(self) -> bool:
149
+ """Check if any scanner plugins have been installed.
150
+
151
+ Returns True if the bin directory exists and contains at least
152
+ one plugin subdirectory.
153
+ """
154
+ if not self.bin_dir.exists():
155
+ return False
156
+ # Check if any plugin directories exist
157
+ for plugin_dir in self.bin_dir.iterdir():
158
+ if plugin_dir.is_dir():
159
+ return True
160
+ return False
@@ -0,0 +1,111 @@
1
+ """Platform detection for lucidscan scanner plugins.
2
+
3
+ Detects OS and architecture to determine which scanner binaries to download.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import platform
9
+ from dataclasses import dataclass
10
+ from typing import Optional
11
+
12
+ # Supported operating systems (lowercase)
13
+ SUPPORTED_OS = frozenset({"darwin", "linux", "windows"})
14
+
15
+ # Supported architectures (normalized)
16
+ SUPPORTED_ARCH = frozenset({"amd64", "arm64"})
17
+
18
+ # Architecture normalization map
19
+ _ARCH_MAP = {
20
+ "x86_64": "amd64",
21
+ "amd64": "amd64",
22
+ "arm64": "arm64",
23
+ "aarch64": "arm64",
24
+ }
25
+
26
+
27
+ def normalize_arch(machine: str) -> Optional[str]:
28
+ """Normalize architecture string to standard form.
29
+
30
+ Args:
31
+ machine: Raw architecture string from platform.machine()
32
+
33
+ Returns:
34
+ Normalized architecture string or None if unknown.
35
+ """
36
+ return _ARCH_MAP.get(machine.lower())
37
+
38
+
39
+ def detect_os() -> str:
40
+ """Detect the current operating system.
41
+
42
+ Returns:
43
+ Lowercase OS name (darwin, linux, windows).
44
+
45
+ Raises:
46
+ ValueError: If the OS is not supported.
47
+ """
48
+ system = platform.system().lower()
49
+ if system not in SUPPORTED_OS:
50
+ raise ValueError(
51
+ f"Unsupported operating system: {platform.system()}. "
52
+ f"Supported: {', '.join(sorted(SUPPORTED_OS))}"
53
+ )
54
+ return system
55
+
56
+
57
+ def detect_arch() -> str:
58
+ """Detect the current CPU architecture.
59
+
60
+ Returns:
61
+ Normalized architecture string (amd64 or arm64).
62
+
63
+ Raises:
64
+ ValueError: If the architecture is not supported.
65
+ """
66
+ machine = platform.machine()
67
+ normalized = normalize_arch(machine)
68
+ if normalized is None:
69
+ raise ValueError(
70
+ f"Unsupported architecture: {machine}. "
71
+ f"Supported: {', '.join(sorted(SUPPORTED_ARCH))}"
72
+ )
73
+ return normalized
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class PlatformInfo:
78
+ """Information about the current platform.
79
+
80
+ Attributes:
81
+ os: Operating system (darwin, linux, windows).
82
+ arch: CPU architecture (amd64, arm64).
83
+ """
84
+
85
+ os: str
86
+ arch: str
87
+
88
+ @property
89
+ def bundle_name(self) -> str:
90
+ """Return the bundle name suffix for this platform.
91
+
92
+ Example: "darwin-arm64", "linux-amd64"
93
+ """
94
+ return f"{self.os}-{self.arch}"
95
+
96
+ def is_supported(self) -> bool:
97
+ """Check if this platform is supported."""
98
+ return self.os in SUPPORTED_OS and self.arch in SUPPORTED_ARCH
99
+
100
+
101
+ def get_platform_info() -> PlatformInfo:
102
+ """Detect and return current platform information.
103
+
104
+ Returns:
105
+ PlatformInfo with detected OS and architecture.
106
+
107
+ Raises:
108
+ ValueError: If the platform is not supported.
109
+ """
110
+ return PlatformInfo(os=detect_os(), arch=detect_arch())
111
+
@@ -0,0 +1,76 @@
1
+ """Tool validation for lucidscan bootstrap.
2
+
3
+ Validates that scanner plugin tools are present and executable.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum
11
+ from pathlib import Path
12
+ from typing import Dict, List
13
+
14
+ from lucidscan.core.logging import get_logger
15
+
16
+ LOGGER = get_logger(__name__)
17
+
18
+
19
+ class ToolStatus(str, Enum):
20
+ """Status of a tool binary."""
21
+
22
+ PRESENT = "present"
23
+ MISSING = "missing"
24
+ NOT_EXECUTABLE = "not_executable"
25
+
26
+
27
+ @dataclass
28
+ class PluginValidationResult:
29
+ """Result of validating scanner plugin tools.
30
+
31
+ Stores validation status for each discovered plugin by name.
32
+ """
33
+
34
+ statuses: Dict[str, ToolStatus] = field(default_factory=dict)
35
+
36
+ def all_valid(self) -> bool:
37
+ """Check if all validated plugins are present and executable."""
38
+ if not self.statuses:
39
+ return True
40
+ return all(status == ToolStatus.PRESENT for status in self.statuses.values())
41
+
42
+ def missing_plugins(self) -> List[str]:
43
+ """Return list of plugins that are missing or not executable."""
44
+ return [
45
+ name
46
+ for name, status in self.statuses.items()
47
+ if status != ToolStatus.PRESENT
48
+ ]
49
+
50
+ def get_status(self, plugin_name: str) -> ToolStatus:
51
+ """Get status for a specific plugin."""
52
+ return self.statuses.get(plugin_name, ToolStatus.MISSING)
53
+
54
+ def to_dict(self) -> Dict[str, str]:
55
+ """Convert to dictionary for JSON serialization."""
56
+ return {name: status.value for name, status in self.statuses.items()}
57
+
58
+
59
+ def validate_binary(path: Path) -> ToolStatus:
60
+ """Validate a single binary file.
61
+
62
+ Args:
63
+ path: Path to the binary file.
64
+
65
+ Returns:
66
+ ToolStatus indicating whether the binary is present and executable.
67
+ """
68
+ if not path.exists():
69
+ return ToolStatus.MISSING
70
+
71
+ # Check if executable
72
+ if not os.access(path, os.X_OK):
73
+ return ToolStatus.NOT_EXECUTABLE
74
+
75
+ return ToolStatus.PRESENT
76
+
@@ -0,0 +1,119 @@
1
+ """Centralized tool version management.
2
+
3
+ Reads tool versions from pyproject.toml [tool.lucidscan.tools] section.
4
+ This is the single source of truth for all lucidscan-managed tool versions.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+ from functools import lru_cache
11
+ from pathlib import Path
12
+ from typing import Any, Dict, Optional
13
+
14
+ # Import tomllib (Python 3.11+) or tomli (Python 3.10)
15
+ try:
16
+ if sys.version_info >= (3, 11):
17
+ import tomllib
18
+
19
+ _tomllib: Any = tomllib
20
+ else:
21
+ import tomli
22
+
23
+ _tomllib = tomli
24
+ except ImportError:
25
+ _tomllib = None # Will use fallback versions
26
+
27
+
28
+ # Hardcoded fallback versions (kept in sync with pyproject.toml)
29
+ # These are used if pyproject.toml cannot be read at runtime
30
+ _FALLBACK_VERSIONS: Dict[str, str] = {
31
+ # Security scanners
32
+ "trivy": "0.68.2",
33
+ "opengrep": "1.15.0",
34
+ "checkov": "3.2.497",
35
+ # Linters
36
+ "ruff": "0.14.11",
37
+ "biome": "2.3.11",
38
+ "checkstyle": "13.0.0",
39
+ # Type checkers
40
+ "pyright": "1.1.408",
41
+ }
42
+
43
+
44
+ @lru_cache(maxsize=1)
45
+ def _load_pyproject_versions() -> Dict[str, str]:
46
+ """Load tool versions from lucidscan's pyproject.toml.
47
+
48
+ Returns:
49
+ Dictionary mapping tool names to versions.
50
+ """
51
+ if _tomllib is None:
52
+ return _FALLBACK_VERSIONS.copy()
53
+
54
+ # Find pyproject.toml relative to this module
55
+ # Structure: src/lucidscan/bootstrap/versions.py -> ../../../pyproject.toml
56
+ pyproject_path = Path(__file__).parent.parent.parent.parent / "pyproject.toml"
57
+
58
+ if not pyproject_path.exists():
59
+ # Installed package - pyproject.toml not available
60
+ return _FALLBACK_VERSIONS.copy()
61
+
62
+ try:
63
+ with open(pyproject_path, "rb") as f:
64
+ data = _tomllib.load(f)
65
+
66
+ versions = {}
67
+
68
+ # Read from [tool.lucidscan.tools] section (new unified section)
69
+ tools_section = data.get("tool", {}).get("lucidscan", {}).get("tools", {})
70
+ versions.update(tools_section)
71
+
72
+ # Also read from legacy [tool.lucidscan.scanners] section for backwards compat
73
+ scanners_section = data.get("tool", {}).get("lucidscan", {}).get("scanners", {})
74
+ for tool, version in scanners_section.items():
75
+ if tool not in versions:
76
+ versions[tool] = version
77
+
78
+ # Fill in any missing tools from fallbacks
79
+ for tool, version in _FALLBACK_VERSIONS.items():
80
+ if tool not in versions:
81
+ versions[tool] = version
82
+
83
+ return versions
84
+
85
+ except Exception:
86
+ return _FALLBACK_VERSIONS.copy()
87
+
88
+
89
+ def get_tool_version(tool_name: str, default: Optional[str] = None) -> str:
90
+ """Get the version for a specific tool.
91
+
92
+ Args:
93
+ tool_name: Name of the tool (e.g., 'trivy', 'ruff').
94
+ default: Optional default version if tool not found.
95
+
96
+ Returns:
97
+ Version string for the tool.
98
+
99
+ Raises:
100
+ KeyError: If tool not found and no default provided.
101
+ """
102
+ versions = _load_pyproject_versions()
103
+
104
+ if tool_name in versions:
105
+ return versions[tool_name]
106
+
107
+ if default is not None:
108
+ return default
109
+
110
+ raise KeyError(f"Unknown tool: {tool_name}. Available: {list(versions.keys())}")
111
+
112
+
113
+ def get_all_versions() -> Dict[str, str]:
114
+ """Get all tool versions.
115
+
116
+ Returns:
117
+ Dictionary mapping tool names to versions.
118
+ """
119
+ return _load_pyproject_versions().copy()
@@ -0,0 +1,50 @@
1
+ """LucidScan CLI package.
2
+
3
+ This package provides the command-line interface for lucidscan.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Iterable, Optional
9
+
10
+ from lucidscan.cli.runner import CLIRunner, get_version
11
+ from lucidscan.cli.arguments import build_parser
12
+ from lucidscan.cli.exit_codes import (
13
+ EXIT_SUCCESS,
14
+ EXIT_ISSUES_FOUND,
15
+ EXIT_SCANNER_ERROR,
16
+ EXIT_INVALID_USAGE,
17
+ EXIT_BOOTSTRAP_FAILURE,
18
+ )
19
+
20
+
21
+ def main(argv: Optional[Iterable[str]] = None) -> int:
22
+ """CLI entrypoint.
23
+
24
+ Returns an exit code suitable for use as a console script.
25
+
26
+ Args:
27
+ argv: Command-line arguments (defaults to sys.argv).
28
+
29
+ Returns:
30
+ Exit code.
31
+ """
32
+ runner = CLIRunner()
33
+ return runner.run(argv)
34
+
35
+
36
+ __all__ = [
37
+ "main",
38
+ "build_parser",
39
+ "get_version",
40
+ "CLIRunner",
41
+ "EXIT_SUCCESS",
42
+ "EXIT_ISSUES_FOUND",
43
+ "EXIT_SCANNER_ERROR",
44
+ "EXIT_INVALID_USAGE",
45
+ "EXIT_BOOTSTRAP_FAILURE",
46
+ ]
47
+
48
+
49
+ if __name__ == "__main__": # pragma: no cover
50
+ raise SystemExit(main())
@@ -0,0 +1,8 @@
1
+ """Entry point for running lucidscan.cli as a module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from lucidscan.cli import main
6
+
7
+ if __name__ == "__main__":
8
+ raise SystemExit(main())