prismor 0.1.0__tar.gz → 0.1.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.
- {prismor-0.1.0/prismor.egg-info → prismor-0.1.2}/PKG-INFO +1 -1
- {prismor-0.1.0 → prismor-0.1.2}/prismor/__init__.py +1 -1
- prismor-0.1.2/prismor/api.py +242 -0
- prismor-0.1.2/prismor/cli.py +351 -0
- {prismor-0.1.0 → prismor-0.1.2/prismor.egg-info}/PKG-INFO +1 -1
- {prismor-0.1.0 → prismor-0.1.2}/setup.py +1 -1
- prismor-0.1.0/prismor/api.py +0 -118
- prismor-0.1.0/prismor/cli.py +0 -210
- {prismor-0.1.0 → prismor-0.1.2}/LICENSE +0 -0
- {prismor-0.1.0 → prismor-0.1.2}/MANIFEST.in +0 -0
- {prismor-0.1.0 → prismor-0.1.2}/README.md +0 -0
- {prismor-0.1.0 → prismor-0.1.2}/prismor.egg-info/SOURCES.txt +0 -0
- {prismor-0.1.0 → prismor-0.1.2}/prismor.egg-info/dependency_links.txt +0 -0
- {prismor-0.1.0 → prismor-0.1.2}/prismor.egg-info/entry_points.txt +0 -0
- {prismor-0.1.0 → prismor-0.1.2}/prismor.egg-info/requires.txt +0 -0
- {prismor-0.1.0 → prismor-0.1.2}/prismor.egg-info/top_level.txt +0 -0
- {prismor-0.1.0 → prismor-0.1.2}/requirements.txt +0 -0
- {prismor-0.1.0 → prismor-0.1.2}/setup.cfg +0 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""API client for Prismor security scanning service."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import requests
|
|
5
|
+
from typing import Optional, Dict, Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PrismorAPIError(Exception):
|
|
9
|
+
"""Custom exception for Prismor API errors."""
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PrismorClient:
|
|
14
|
+
"""Client for interacting with Prismor API."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, api_key: Optional[str] = None):
|
|
17
|
+
"""Initialize the Prismor API client.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
api_key: Prismor API key. If not provided, will look for PRISMOR_API_KEY env var.
|
|
21
|
+
"""
|
|
22
|
+
self.api_key = api_key or os.environ.get("PRISMOR_API_KEY")
|
|
23
|
+
if not self.api_key:
|
|
24
|
+
raise PrismorAPIError(
|
|
25
|
+
"PRISMOR_API_KEY environment variable is not set. "
|
|
26
|
+
"Please set it with: export PRISMOR_API_KEY=your_api_key"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# self.base_url = "http://localhost:3000"
|
|
30
|
+
self.base_url = "https://prismor.dev"
|
|
31
|
+
self.headers = {
|
|
32
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
33
|
+
"Content-Type": "application/json"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def authenticate(self) -> Dict[str, Any]:
|
|
37
|
+
"""Authenticate with the Prismor API using the API key.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dictionary containing user information and repositories
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
PrismorAPIError: If authentication fails
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
response = requests.post(
|
|
47
|
+
f"{self.base_url}/api/cli/auth",
|
|
48
|
+
json={"apiKey": self.api_key},
|
|
49
|
+
headers={"Content-Type": "application/json"},
|
|
50
|
+
timeout=30
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if response.status_code == 401:
|
|
54
|
+
raise PrismorAPIError("Invalid API key. Please check your PRISMOR_API_KEY.")
|
|
55
|
+
|
|
56
|
+
if response.status_code == 400:
|
|
57
|
+
raise PrismorAPIError("API key is required.")
|
|
58
|
+
|
|
59
|
+
if response.status_code >= 400:
|
|
60
|
+
error_msg = response.json().get("error", "Authentication failed")
|
|
61
|
+
raise PrismorAPIError(f"Authentication error: {error_msg}")
|
|
62
|
+
|
|
63
|
+
response.raise_for_status()
|
|
64
|
+
return response.json()
|
|
65
|
+
|
|
66
|
+
except requests.exceptions.Timeout:
|
|
67
|
+
raise PrismorAPIError("Authentication request timed out.")
|
|
68
|
+
except requests.exceptions.ConnectionError:
|
|
69
|
+
raise PrismorAPIError(
|
|
70
|
+
"Failed to connect to Prismor API. Please check your internet connection."
|
|
71
|
+
)
|
|
72
|
+
except requests.exceptions.RequestException as e:
|
|
73
|
+
raise PrismorAPIError(f"Authentication request failed: {str(e)}")
|
|
74
|
+
|
|
75
|
+
def normalize_repo_url(self, repo: str) -> str:
|
|
76
|
+
"""Normalize repository input to a full GitHub URL.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
repo: Repository in format 'username/repo' or full GitHub URL
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Full GitHub repository URL
|
|
83
|
+
"""
|
|
84
|
+
if repo.startswith("http://") or repo.startswith("https://"):
|
|
85
|
+
return repo
|
|
86
|
+
|
|
87
|
+
# Assume it's in username/repo format
|
|
88
|
+
if "/" in repo:
|
|
89
|
+
return f"https://github.com/{repo}"
|
|
90
|
+
|
|
91
|
+
raise PrismorAPIError(
|
|
92
|
+
f"Invalid repository format: {repo}. "
|
|
93
|
+
"Please use 'username/repo' or full GitHub URL"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def scan(
|
|
97
|
+
self,
|
|
98
|
+
repo: str,
|
|
99
|
+
vex: bool = False,
|
|
100
|
+
sbom: bool = False,
|
|
101
|
+
detect_secret: bool = False,
|
|
102
|
+
fullscan: bool = False,
|
|
103
|
+
branch: Optional[str] = None
|
|
104
|
+
) -> Dict[str, Any]:
|
|
105
|
+
"""Perform security scan on a GitHub repository.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
repo: Repository URL or username/repo format
|
|
109
|
+
vex: Enable vulnerability scanning
|
|
110
|
+
sbom: Enable SBOM generation
|
|
111
|
+
detect_secret: Enable secret detection
|
|
112
|
+
fullscan: Enable all scan types
|
|
113
|
+
branch: Specific branch to scan (defaults to main/master)
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Dictionary containing scan results
|
|
117
|
+
"""
|
|
118
|
+
repo_url = self.normalize_repo_url(repo)
|
|
119
|
+
|
|
120
|
+
# First authenticate to get user info
|
|
121
|
+
auth_response = self.authenticate()
|
|
122
|
+
user_info = auth_response.get("user", {})
|
|
123
|
+
|
|
124
|
+
# Prepare request payload for CLI scan
|
|
125
|
+
payload = {
|
|
126
|
+
"repo_url": repo_url,
|
|
127
|
+
"api_key": self.api_key,
|
|
128
|
+
"vex": vex or fullscan,
|
|
129
|
+
"sbom": sbom or fullscan,
|
|
130
|
+
"detect_secret": detect_secret or fullscan,
|
|
131
|
+
"fullscan": fullscan,
|
|
132
|
+
"branch": branch
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
response = requests.post(
|
|
137
|
+
f"{self.base_url}/api/cli/scan",
|
|
138
|
+
json=payload,
|
|
139
|
+
headers={"Content-Type": "application/json"},
|
|
140
|
+
timeout=300 # 5 minute timeout
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if response.status_code == 401:
|
|
144
|
+
error_data = response.json()
|
|
145
|
+
if error_data.get("action") == "integrate_github":
|
|
146
|
+
raise PrismorAPIError(
|
|
147
|
+
f"{error_data.get('message', 'GitHub integration required')}\n"
|
|
148
|
+
f"Please visit: {error_data.get('integration_url', 'https://prismor.dev/dashboard')}"
|
|
149
|
+
)
|
|
150
|
+
raise PrismorAPIError("Invalid API key. Please check your PRISMOR_API_KEY.")
|
|
151
|
+
|
|
152
|
+
if response.status_code == 404:
|
|
153
|
+
raise PrismorAPIError("CLI scan endpoint not found. Please check if CLI endpoints are available.")
|
|
154
|
+
|
|
155
|
+
if response.status_code >= 400:
|
|
156
|
+
error_msg = response.json().get("error", "Unknown error")
|
|
157
|
+
raise PrismorAPIError(f"API error: {error_msg}")
|
|
158
|
+
|
|
159
|
+
response.raise_for_status()
|
|
160
|
+
result = response.json()
|
|
161
|
+
|
|
162
|
+
# Handle the new response format from CLI endpoint
|
|
163
|
+
if result.get("ok") and "results" in result:
|
|
164
|
+
return result["results"]
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
except requests.exceptions.Timeout:
|
|
168
|
+
raise PrismorAPIError(
|
|
169
|
+
"Request timed out. The repository scan is taking longer than expected."
|
|
170
|
+
)
|
|
171
|
+
except requests.exceptions.ConnectionError:
|
|
172
|
+
raise PrismorAPIError(
|
|
173
|
+
"Failed to connect to Prismor API. Please check your internet connection."
|
|
174
|
+
)
|
|
175
|
+
except requests.exceptions.RequestException as e:
|
|
176
|
+
raise PrismorAPIError(f"Request failed: {str(e)}")
|
|
177
|
+
|
|
178
|
+
def get_repositories(self) -> Dict[str, Any]:
|
|
179
|
+
"""Get user's repositories.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Dictionary containing user repositories
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
PrismorAPIError: If request fails
|
|
186
|
+
"""
|
|
187
|
+
auth_response = self.authenticate()
|
|
188
|
+
user_info = auth_response.get("user", {})
|
|
189
|
+
return {
|
|
190
|
+
"repositories": user_info.get("repositories", []),
|
|
191
|
+
"user": {
|
|
192
|
+
"id": user_info.get("id"),
|
|
193
|
+
"email": user_info.get("email"),
|
|
194
|
+
"name": user_info.get("name")
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
def get_repository_by_name(self, repo_name: str) -> Dict[str, Any]:
|
|
199
|
+
"""Get repository ID by repository name.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
repo_name: Repository name (e.g., "username/repo")
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Dictionary containing repository information including ID
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
PrismorAPIError: If request fails
|
|
209
|
+
"""
|
|
210
|
+
try:
|
|
211
|
+
response = requests.post(
|
|
212
|
+
f"{self.base_url}/api/repositories/by-name",
|
|
213
|
+
json={
|
|
214
|
+
"apiKey": self.api_key,
|
|
215
|
+
"repoName": repo_name
|
|
216
|
+
},
|
|
217
|
+
headers={"Content-Type": "application/json"},
|
|
218
|
+
timeout=30
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if response.status_code == 401:
|
|
222
|
+
raise PrismorAPIError("Invalid API key. Please check your PRISMOR_API_KEY.")
|
|
223
|
+
|
|
224
|
+
if response.status_code == 404:
|
|
225
|
+
raise PrismorAPIError(f"Repository '{repo_name}' not found.")
|
|
226
|
+
|
|
227
|
+
if response.status_code >= 400:
|
|
228
|
+
error_msg = response.json().get("error", "Unknown error")
|
|
229
|
+
raise PrismorAPIError(f"API error: {error_msg}")
|
|
230
|
+
|
|
231
|
+
response.raise_for_status()
|
|
232
|
+
return response.json()
|
|
233
|
+
|
|
234
|
+
except requests.exceptions.Timeout:
|
|
235
|
+
raise PrismorAPIError("Request timed out.")
|
|
236
|
+
except requests.exceptions.ConnectionError:
|
|
237
|
+
raise PrismorAPIError(
|
|
238
|
+
"Failed to connect to Prismor API. Please check your internet connection."
|
|
239
|
+
)
|
|
240
|
+
except requests.exceptions.RequestException as e:
|
|
241
|
+
raise PrismorAPIError(f"Request failed: {str(e)}")
|
|
242
|
+
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""Command-line interface for Prismor security scanning tool."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import click
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from .api import PrismorClient, PrismorAPIError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def print_success(message: str):
|
|
11
|
+
"""Print success message in green."""
|
|
12
|
+
click.secho(f"✓ {message}", fg="green")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def print_error(message: str):
|
|
16
|
+
"""Print error message in red."""
|
|
17
|
+
click.secho(f"✗ {message}", fg="red", err=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def print_info(message: str):
|
|
21
|
+
"""Print info message in blue."""
|
|
22
|
+
click.secho(f"ℹ {message}", fg="blue")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def print_warning(message: str):
|
|
26
|
+
"""Print warning message in yellow."""
|
|
27
|
+
click.secho(f"⚠ {message}", fg="yellow")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def format_scan_results(results: dict, scan_type: str):
|
|
31
|
+
"""Format and display scan results."""
|
|
32
|
+
click.echo("\n" + "=" * 60)
|
|
33
|
+
click.secho(f" Scan Results - {scan_type}", fg="cyan", bold=True)
|
|
34
|
+
click.echo("=" * 60 + "\n")
|
|
35
|
+
|
|
36
|
+
# Display repository information
|
|
37
|
+
if "repository" in results:
|
|
38
|
+
click.secho("Repository:", fg="yellow", bold=True)
|
|
39
|
+
click.echo(f" {results['repository']}\n")
|
|
40
|
+
|
|
41
|
+
if "branch" in results:
|
|
42
|
+
click.secho("Branch:", fg="yellow", bold=True)
|
|
43
|
+
click.echo(f" {results['branch']}\n")
|
|
44
|
+
|
|
45
|
+
if "commit_sha" in results:
|
|
46
|
+
click.secho("Commit SHA:", fg="yellow", bold=True)
|
|
47
|
+
click.echo(f" {results['commit_sha']}\n")
|
|
48
|
+
|
|
49
|
+
# Display scan results for each scan type
|
|
50
|
+
if "scans" in results:
|
|
51
|
+
scans = results["scans"]
|
|
52
|
+
|
|
53
|
+
# Vulnerability scan results
|
|
54
|
+
if "vulnerability" in scans:
|
|
55
|
+
vuln_scan = scans["vulnerability"]
|
|
56
|
+
click.secho("Vulnerability Scan:", fg="yellow", bold=True)
|
|
57
|
+
status_color = "green" if vuln_scan.get("status") == "success" else "red"
|
|
58
|
+
click.secho(f" Status: {vuln_scan.get('status', 'unknown')}", fg=status_color)
|
|
59
|
+
|
|
60
|
+
if "scan_results" in vuln_scan:
|
|
61
|
+
scan_data = vuln_scan["scan_results"]
|
|
62
|
+
if "vulnerabilities" in scan_data or "Results" in scan_data:
|
|
63
|
+
vuln_data = scan_data.get("vulnerabilities", scan_data.get("Results", []))
|
|
64
|
+
if isinstance(vuln_data, list):
|
|
65
|
+
click.echo(f" Vulnerabilities Found: {len(vuln_data)}")
|
|
66
|
+
else:
|
|
67
|
+
click.echo(f" Vulnerabilities: Data available")
|
|
68
|
+
|
|
69
|
+
if "public_url" in vuln_scan:
|
|
70
|
+
click.echo(f" Results URL: {vuln_scan['public_url']}")
|
|
71
|
+
click.echo()
|
|
72
|
+
|
|
73
|
+
# SBOM results
|
|
74
|
+
if "sbom" in scans:
|
|
75
|
+
sbom_scan = scans["sbom"]
|
|
76
|
+
click.secho("SBOM Generation:", fg="yellow", bold=True)
|
|
77
|
+
status_color = "green" if sbom_scan.get("status") == "success" else "red"
|
|
78
|
+
click.secho(f" Status: {sbom_scan.get('status', 'unknown')}", fg=status_color)
|
|
79
|
+
|
|
80
|
+
if "sbom" in sbom_scan:
|
|
81
|
+
sbom_data = sbom_scan["sbom"]
|
|
82
|
+
if isinstance(sbom_data, list):
|
|
83
|
+
click.echo(f" Artifacts Found: {len(sbom_data)}")
|
|
84
|
+
else:
|
|
85
|
+
click.echo(f" SBOM: Data available")
|
|
86
|
+
|
|
87
|
+
if "public_url" in sbom_scan:
|
|
88
|
+
click.echo(f" Results URL: {sbom_scan['public_url']}")
|
|
89
|
+
click.echo()
|
|
90
|
+
|
|
91
|
+
# Secret scan results
|
|
92
|
+
if "secret" in scans:
|
|
93
|
+
secret_scan = scans["secret"]
|
|
94
|
+
click.secho("Secret Detection:", fg="yellow", bold=True)
|
|
95
|
+
status_color = "green" if secret_scan.get("status") == "success" else "red"
|
|
96
|
+
click.secho(f" Status: {secret_scan.get('status', 'unknown')}", fg=status_color)
|
|
97
|
+
|
|
98
|
+
if "summary" in secret_scan:
|
|
99
|
+
summary = secret_scan["summary"]
|
|
100
|
+
if isinstance(summary, dict):
|
|
101
|
+
for key, value in summary.items():
|
|
102
|
+
click.echo(f" {key}: {value}")
|
|
103
|
+
else:
|
|
104
|
+
click.echo(f" Summary: {summary}")
|
|
105
|
+
|
|
106
|
+
if "public_url" in secret_scan:
|
|
107
|
+
click.echo(f" Results URL: {secret_scan['public_url']}")
|
|
108
|
+
click.echo()
|
|
109
|
+
|
|
110
|
+
# Display scan timestamp
|
|
111
|
+
if "scanned_at" in results:
|
|
112
|
+
click.secho("Scanned At:", fg="yellow", bold=True)
|
|
113
|
+
click.echo(f" {results['scanned_at']}\n")
|
|
114
|
+
|
|
115
|
+
click.echo("=" * 60 + "\n")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@click.group(invoke_without_command=True)
|
|
119
|
+
@click.option(
|
|
120
|
+
"--scan",
|
|
121
|
+
type=str,
|
|
122
|
+
help="Repository to scan (username/repo or full GitHub URL)"
|
|
123
|
+
)
|
|
124
|
+
@click.option("--vex", is_flag=True, help="Perform vulnerability scanning")
|
|
125
|
+
@click.option("--sbom", is_flag=True, help="Generate Software Bill of Materials")
|
|
126
|
+
@click.option("--detect-secret", is_flag=True, help="Detect secrets in repository")
|
|
127
|
+
@click.option("--fullscan", is_flag=True, help="Perform all scan types")
|
|
128
|
+
@click.option("--branch", type=str, help="Specific branch to scan (defaults to main/master)")
|
|
129
|
+
@click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
|
|
130
|
+
@click.version_option(version="0.1.2", prog_name="prismor")
|
|
131
|
+
@click.pass_context
|
|
132
|
+
def cli(ctx, scan: Optional[str], vex: bool, sbom: bool, detect_secret: bool,
|
|
133
|
+
fullscan: bool, branch: Optional[str], output_json: bool):
|
|
134
|
+
"""Prismor CLI - Security scanning tool for GitHub repositories.
|
|
135
|
+
|
|
136
|
+
Examples:
|
|
137
|
+
prismor --scan username/repo --vex
|
|
138
|
+
prismor --scan username/repo --fullscan
|
|
139
|
+
prismor --scan https://github.com/username/repo --detect-secret
|
|
140
|
+
prismor --scan username/repo --sbom --branch develop
|
|
141
|
+
prismor status
|
|
142
|
+
prismor repos
|
|
143
|
+
"""
|
|
144
|
+
# If no command and no scan option, show help
|
|
145
|
+
if ctx.invoked_subcommand is None and not scan:
|
|
146
|
+
click.echo(ctx.get_help())
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
# If scan option is provided, perform the scan
|
|
150
|
+
if scan:
|
|
151
|
+
# Check if at least one scan type is selected
|
|
152
|
+
if not any([vex, sbom, detect_secret, fullscan]):
|
|
153
|
+
print_error("Please specify at least one scan type: --vex, --sbom, --detect-secret, or --fullscan")
|
|
154
|
+
sys.exit(1)
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
# Initialize API client
|
|
158
|
+
print_info(f"Initializing Prismor scan for: {scan}")
|
|
159
|
+
client = PrismorClient()
|
|
160
|
+
|
|
161
|
+
# Determine scan type for display
|
|
162
|
+
scan_types = []
|
|
163
|
+
if fullscan:
|
|
164
|
+
scan_types.append("Full Scan (VEX + SBOM + Secret Detection)")
|
|
165
|
+
else:
|
|
166
|
+
if vex:
|
|
167
|
+
scan_types.append("VEX")
|
|
168
|
+
if sbom:
|
|
169
|
+
scan_types.append("SBOM")
|
|
170
|
+
if detect_secret:
|
|
171
|
+
scan_types.append("Secret Detection")
|
|
172
|
+
|
|
173
|
+
print_info(f"Scan type: {', '.join(scan_types)}")
|
|
174
|
+
print_info("Starting scan... (this may take a few minutes)")
|
|
175
|
+
|
|
176
|
+
# Perform scan
|
|
177
|
+
results = client.scan(
|
|
178
|
+
repo=scan,
|
|
179
|
+
vex=vex,
|
|
180
|
+
sbom=sbom,
|
|
181
|
+
detect_secret=detect_secret,
|
|
182
|
+
fullscan=fullscan,
|
|
183
|
+
branch=branch
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Output results
|
|
187
|
+
if output_json:
|
|
188
|
+
click.echo(json.dumps(results, indent=2))
|
|
189
|
+
else:
|
|
190
|
+
print_success("Scan completed successfully!")
|
|
191
|
+
format_scan_results(results, ', '.join(scan_types))
|
|
192
|
+
|
|
193
|
+
# Try to get repository ID and display dashboard link
|
|
194
|
+
try:
|
|
195
|
+
# Extract repo name from scan input
|
|
196
|
+
repo_name = scan
|
|
197
|
+
if scan.startswith("http://") or scan.startswith("https://"):
|
|
198
|
+
# Extract from GitHub URL
|
|
199
|
+
if "github.com/" in scan:
|
|
200
|
+
repo_name = scan.split("github.com/")[1].rstrip("/")
|
|
201
|
+
|
|
202
|
+
# Get repository ID
|
|
203
|
+
repo_info = client.get_repository_by_name(repo_name)
|
|
204
|
+
if repo_info.get("success") and "repository" in repo_info:
|
|
205
|
+
repo_id = repo_info["repository"]["id"]
|
|
206
|
+
dashboard_url = f"https://prismor.dev/repositories/{repo_id}"
|
|
207
|
+
|
|
208
|
+
click.echo("\n" + "=" * 60)
|
|
209
|
+
click.secho(" 📊 Dashboard Analysis", fg="cyan", bold=True)
|
|
210
|
+
click.echo("=" * 60)
|
|
211
|
+
click.secho(f"🔗 View detailed analysis and insights:", fg="blue")
|
|
212
|
+
click.secho(f" {dashboard_url}", fg="green", bold=True)
|
|
213
|
+
click.echo("\n💡 The dashboard provides:")
|
|
214
|
+
click.echo(" • Interactive visualizations and charts")
|
|
215
|
+
click.echo(" • Historical vulnerability trends")
|
|
216
|
+
click.echo(" • Detailed security reports")
|
|
217
|
+
click.echo(" • Team collaboration features")
|
|
218
|
+
click.echo(" • Export capabilities")
|
|
219
|
+
click.echo("=" * 60 + "\n")
|
|
220
|
+
|
|
221
|
+
except PrismorAPIError as e:
|
|
222
|
+
# Repository might not be found, continue without dashboard link
|
|
223
|
+
print_warning(f"Could not generate dashboard link: {str(e)}")
|
|
224
|
+
except Exception as e:
|
|
225
|
+
# Any other error, continue without dashboard link
|
|
226
|
+
print_warning(f"Could not generate dashboard link: {str(e)}")
|
|
227
|
+
|
|
228
|
+
except PrismorAPIError as e:
|
|
229
|
+
print_error(str(e))
|
|
230
|
+
sys.exit(1)
|
|
231
|
+
except Exception as e:
|
|
232
|
+
print_error(f"Unexpected error: {str(e)}")
|
|
233
|
+
sys.exit(1)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@cli.command()
|
|
237
|
+
def version():
|
|
238
|
+
"""Display the version of Prismor CLI."""
|
|
239
|
+
click.echo("Prismor CLI v0.1.0")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@cli.command()
|
|
243
|
+
def config():
|
|
244
|
+
"""Display current configuration."""
|
|
245
|
+
import os
|
|
246
|
+
|
|
247
|
+
click.echo("\n" + "=" * 60)
|
|
248
|
+
click.secho(" Prismor CLI Configuration", fg="cyan", bold=True)
|
|
249
|
+
click.echo("=" * 60 + "\n")
|
|
250
|
+
|
|
251
|
+
# Check API key
|
|
252
|
+
api_key = os.environ.get("PRISMOR_API_KEY")
|
|
253
|
+
if api_key:
|
|
254
|
+
# Show only first and last 4 characters
|
|
255
|
+
masked_key = f"{api_key[:4]}...{api_key[-4:]}" if len(api_key) > 8 else "***"
|
|
256
|
+
print_success(f"PRISMOR_API_KEY: {masked_key}")
|
|
257
|
+
else:
|
|
258
|
+
print_error("PRISMOR_API_KEY: Not set")
|
|
259
|
+
click.echo("\nTo set your API key, run:")
|
|
260
|
+
click.echo(" export PRISMOR_API_KEY=your_api_key")
|
|
261
|
+
|
|
262
|
+
# click.echo("\nAPI Endpoint: http://localhost:3000")
|
|
263
|
+
click.echo("=" * 60 + "\n")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@cli.command()
|
|
267
|
+
def repos():
|
|
268
|
+
"""List your connected repositories."""
|
|
269
|
+
try:
|
|
270
|
+
client = PrismorClient()
|
|
271
|
+
repos_data = client.get_repositories()
|
|
272
|
+
|
|
273
|
+
click.echo("\n" + "=" * 60)
|
|
274
|
+
click.secho(" Your Repositories", fg="cyan", bold=True)
|
|
275
|
+
click.echo("=" * 60 + "\n")
|
|
276
|
+
|
|
277
|
+
user_info = repos_data.get("user", {})
|
|
278
|
+
if user_info:
|
|
279
|
+
click.secho(f"User: {user_info.get('name', 'Unknown')} ({user_info.get('email', 'No email')})", fg="yellow")
|
|
280
|
+
click.echo()
|
|
281
|
+
|
|
282
|
+
repositories = repos_data.get("repositories", [])
|
|
283
|
+
if repositories:
|
|
284
|
+
for repo in repositories:
|
|
285
|
+
click.secho(f"• {repo.get('name', 'Unknown')}", fg="green")
|
|
286
|
+
click.echo(f" URL: {repo.get('htmlUrl', 'No URL')}")
|
|
287
|
+
click.echo(f" Owner: {repo.get('githubOwner', 'Unknown')}")
|
|
288
|
+
click.echo()
|
|
289
|
+
else:
|
|
290
|
+
print_warning("No repositories found. Connect repositories through the web interface.")
|
|
291
|
+
|
|
292
|
+
click.echo("=" * 60 + "\n")
|
|
293
|
+
|
|
294
|
+
except PrismorAPIError as e:
|
|
295
|
+
print_error(str(e))
|
|
296
|
+
sys.exit(1)
|
|
297
|
+
except Exception as e:
|
|
298
|
+
print_error(f"Unexpected error: {str(e)}")
|
|
299
|
+
sys.exit(1)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@cli.command()
|
|
303
|
+
def status():
|
|
304
|
+
"""Check your account status and GitHub integration."""
|
|
305
|
+
try:
|
|
306
|
+
client = PrismorClient()
|
|
307
|
+
auth_data = client.authenticate()
|
|
308
|
+
|
|
309
|
+
click.echo("\n" + "=" * 60)
|
|
310
|
+
click.secho(" Account Status", fg="cyan", bold=True)
|
|
311
|
+
click.echo("=" * 60 + "\n")
|
|
312
|
+
|
|
313
|
+
user_info = auth_data.get("user", {})
|
|
314
|
+
if user_info:
|
|
315
|
+
click.secho(f"User: {user_info.get('name', 'Unknown')} ({user_info.get('email', 'No email')})", fg="yellow")
|
|
316
|
+
click.echo()
|
|
317
|
+
|
|
318
|
+
repositories = user_info.get("repositories", [])
|
|
319
|
+
click.secho(f"Connected Repositories: {len(repositories)}", fg="green")
|
|
320
|
+
|
|
321
|
+
if repositories:
|
|
322
|
+
click.echo("\nRepository List:")
|
|
323
|
+
for repo in repositories:
|
|
324
|
+
click.echo(f" • {repo.get('name', 'Unknown')} ({repo.get('htmlUrl', 'No URL')})")
|
|
325
|
+
else:
|
|
326
|
+
print_warning("No repositories connected.")
|
|
327
|
+
click.echo("\nTo connect repositories:")
|
|
328
|
+
click.echo(" 1. Visit https://prismor.dev/dashboard")
|
|
329
|
+
click.echo(" 2. Connect your GitHub account")
|
|
330
|
+
click.echo(" 3. Select repositories to scan")
|
|
331
|
+
else:
|
|
332
|
+
print_error("Failed to retrieve account information.")
|
|
333
|
+
|
|
334
|
+
click.echo("\n" + "=" * 60 + "\n")
|
|
335
|
+
|
|
336
|
+
except PrismorAPIError as e:
|
|
337
|
+
print_error(str(e))
|
|
338
|
+
sys.exit(1)
|
|
339
|
+
except Exception as e:
|
|
340
|
+
print_error(f"Unexpected error: {str(e)}")
|
|
341
|
+
sys.exit(1)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def main():
|
|
345
|
+
"""Entry point for the CLI."""
|
|
346
|
+
cli()
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
if __name__ == "__main__":
|
|
350
|
+
main()
|
|
351
|
+
|
|
@@ -17,7 +17,7 @@ if os.path.exists("README.md"):
|
|
|
17
17
|
|
|
18
18
|
setup(
|
|
19
19
|
name="prismor",
|
|
20
|
-
version="0.1.
|
|
20
|
+
version="0.1.2",
|
|
21
21
|
author="Prismor",
|
|
22
22
|
author_email="support@prismor.dev",
|
|
23
23
|
description="A CLI tool for scanning GitHub repositories for vulnerabilities, secrets, and generating SBOMs",
|
prismor-0.1.0/prismor/api.py
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
"""API client for Prismor security scanning service."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import requests
|
|
5
|
-
from typing import Optional, Dict, Any
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class PrismorAPIError(Exception):
|
|
9
|
-
"""Custom exception for Prismor API errors."""
|
|
10
|
-
pass
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class PrismorClient:
|
|
14
|
-
"""Client for interacting with Prismor API."""
|
|
15
|
-
|
|
16
|
-
def __init__(self, api_key: Optional[str] = None):
|
|
17
|
-
"""Initialize the Prismor API client.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
api_key: Prismor API key. If not provided, will look for PRISMOR_API_KEY env var.
|
|
21
|
-
"""
|
|
22
|
-
self.api_key = api_key or os.environ.get("PRISMOR_API_KEY")
|
|
23
|
-
if not self.api_key:
|
|
24
|
-
raise PrismorAPIError(
|
|
25
|
-
"PRISMOR_API_KEY environment variable is not set. "
|
|
26
|
-
"Please set it with: export PRISMOR_API_KEY=your_api_key"
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
self.base_url = "https://api.prismor.dev"
|
|
30
|
-
self.headers = {
|
|
31
|
-
"Authorization": f"Bearer {self.api_key}",
|
|
32
|
-
"Content-Type": "application/json"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
def normalize_repo_url(self, repo: str) -> str:
|
|
36
|
-
"""Normalize repository input to a full GitHub URL.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
repo: Repository in format 'username/repo' or full GitHub URL
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
Full GitHub repository URL
|
|
43
|
-
"""
|
|
44
|
-
if repo.startswith("http://") or repo.startswith("https://"):
|
|
45
|
-
return repo
|
|
46
|
-
|
|
47
|
-
# Assume it's in username/repo format
|
|
48
|
-
if "/" in repo:
|
|
49
|
-
return f"https://github.com/{repo}"
|
|
50
|
-
|
|
51
|
-
raise PrismorAPIError(
|
|
52
|
-
f"Invalid repository format: {repo}. "
|
|
53
|
-
"Please use 'username/repo' or full GitHub URL"
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
def scan(
|
|
57
|
-
self,
|
|
58
|
-
repo: str,
|
|
59
|
-
vex: bool = False,
|
|
60
|
-
sbom: bool = False,
|
|
61
|
-
detect_secret: bool = False,
|
|
62
|
-
fullscan: bool = False
|
|
63
|
-
) -> Dict[str, Any]:
|
|
64
|
-
"""Perform security scan on a GitHub repository.
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
repo: Repository URL or username/repo format
|
|
68
|
-
vex: Enable vulnerability scanning
|
|
69
|
-
sbom: Enable SBOM generation
|
|
70
|
-
detect_secret: Enable secret detection
|
|
71
|
-
fullscan: Enable all scan types
|
|
72
|
-
|
|
73
|
-
Returns:
|
|
74
|
-
Dictionary containing scan results
|
|
75
|
-
"""
|
|
76
|
-
repo_url = self.normalize_repo_url(repo)
|
|
77
|
-
|
|
78
|
-
# Prepare request payload
|
|
79
|
-
payload = {
|
|
80
|
-
"repo_url": repo_url,
|
|
81
|
-
"vex": vex or fullscan,
|
|
82
|
-
"sbom": sbom or fullscan,
|
|
83
|
-
"detect_secret": detect_secret or fullscan,
|
|
84
|
-
"fullscan": fullscan
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
try:
|
|
88
|
-
response = requests.post(
|
|
89
|
-
f"{self.base_url}/scan",
|
|
90
|
-
json=payload,
|
|
91
|
-
headers=self.headers,
|
|
92
|
-
timeout=300 # 5 minute timeout
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
if response.status_code == 401:
|
|
96
|
-
raise PrismorAPIError("Invalid API key. Please check your PRISMOR_API_KEY.")
|
|
97
|
-
|
|
98
|
-
if response.status_code == 404:
|
|
99
|
-
raise PrismorAPIError("API endpoint not found. Please check the API URL.")
|
|
100
|
-
|
|
101
|
-
if response.status_code >= 400:
|
|
102
|
-
error_msg = response.json().get("error", "Unknown error")
|
|
103
|
-
raise PrismorAPIError(f"API error: {error_msg}")
|
|
104
|
-
|
|
105
|
-
response.raise_for_status()
|
|
106
|
-
return response.json()
|
|
107
|
-
|
|
108
|
-
except requests.exceptions.Timeout:
|
|
109
|
-
raise PrismorAPIError(
|
|
110
|
-
"Request timed out. The repository scan is taking longer than expected."
|
|
111
|
-
)
|
|
112
|
-
except requests.exceptions.ConnectionError:
|
|
113
|
-
raise PrismorAPIError(
|
|
114
|
-
"Failed to connect to Prismor API. Please check your internet connection."
|
|
115
|
-
)
|
|
116
|
-
except requests.exceptions.RequestException as e:
|
|
117
|
-
raise PrismorAPIError(f"Request failed: {str(e)}")
|
|
118
|
-
|
prismor-0.1.0/prismor/cli.py
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
"""Command-line interface for Prismor security scanning tool."""
|
|
2
|
-
|
|
3
|
-
import sys
|
|
4
|
-
import json
|
|
5
|
-
import click
|
|
6
|
-
from typing import Optional
|
|
7
|
-
from .api import PrismorClient, PrismorAPIError
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def print_success(message: str):
|
|
11
|
-
"""Print success message in green."""
|
|
12
|
-
click.secho(f"✓ {message}", fg="green")
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def print_error(message: str):
|
|
16
|
-
"""Print error message in red."""
|
|
17
|
-
click.secho(f"✗ {message}", fg="red", err=True)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def print_info(message: str):
|
|
21
|
-
"""Print info message in blue."""
|
|
22
|
-
click.secho(f"ℹ {message}", fg="blue")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def print_warning(message: str):
|
|
26
|
-
"""Print warning message in yellow."""
|
|
27
|
-
click.secho(f"⚠ {message}", fg="yellow")
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def format_scan_results(results: dict, scan_type: str):
|
|
31
|
-
"""Format and display scan results."""
|
|
32
|
-
click.echo("\n" + "=" * 60)
|
|
33
|
-
click.secho(f" Scan Results - {scan_type}", fg="cyan", bold=True)
|
|
34
|
-
click.echo("=" * 60 + "\n")
|
|
35
|
-
|
|
36
|
-
# Display repository information
|
|
37
|
-
if "repository" in results:
|
|
38
|
-
click.secho("Repository:", fg="yellow", bold=True)
|
|
39
|
-
click.echo(f" {results['repository']}\n")
|
|
40
|
-
|
|
41
|
-
# Display scan status
|
|
42
|
-
if "status" in results:
|
|
43
|
-
status_color = "green" if results["status"] == "success" else "red"
|
|
44
|
-
click.secho(f"Status: ", fg="yellow", bold=True, nl=False)
|
|
45
|
-
click.secho(results["status"], fg=status_color)
|
|
46
|
-
click.echo()
|
|
47
|
-
|
|
48
|
-
# Display scan results based on type
|
|
49
|
-
if "scan_results" in results:
|
|
50
|
-
scan_data = results["scan_results"]
|
|
51
|
-
|
|
52
|
-
# Vulnerability scan results
|
|
53
|
-
if "vulnerabilities" in scan_data or "Results" in scan_data:
|
|
54
|
-
click.secho("Vulnerabilities Found:", fg="yellow", bold=True)
|
|
55
|
-
vuln_data = scan_data.get("vulnerabilities", scan_data.get("Results", []))
|
|
56
|
-
if isinstance(vuln_data, list):
|
|
57
|
-
click.echo(f" Total: {len(vuln_data)}")
|
|
58
|
-
else:
|
|
59
|
-
click.echo(f" Data available in detailed output")
|
|
60
|
-
click.echo()
|
|
61
|
-
|
|
62
|
-
# Secret scan results
|
|
63
|
-
if "secrets" in scan_data or "findings_summary" in scan_data:
|
|
64
|
-
click.secho("Secrets Detected:", fg="yellow", bold=True)
|
|
65
|
-
secrets = scan_data.get("secrets", scan_data.get("findings_summary", {}))
|
|
66
|
-
if isinstance(secrets, dict):
|
|
67
|
-
for key, value in secrets.items():
|
|
68
|
-
click.echo(f" {key}: {value}")
|
|
69
|
-
else:
|
|
70
|
-
click.echo(f" {len(secrets) if isinstance(secrets, list) else 'Data available'}")
|
|
71
|
-
click.echo()
|
|
72
|
-
|
|
73
|
-
# SBOM results
|
|
74
|
-
if "sbom" in scan_data or "artifacts" in scan_data:
|
|
75
|
-
click.secho("SBOM Generated:", fg="yellow", bold=True)
|
|
76
|
-
sbom_data = scan_data.get("sbom", scan_data.get("artifacts", []))
|
|
77
|
-
if isinstance(sbom_data, list):
|
|
78
|
-
click.echo(f" Total artifacts: {len(sbom_data)}")
|
|
79
|
-
else:
|
|
80
|
-
click.echo(f" SBOM data available")
|
|
81
|
-
click.echo()
|
|
82
|
-
|
|
83
|
-
# Display result URLs if available
|
|
84
|
-
if "public_url" in results:
|
|
85
|
-
click.secho("Results URL:", fg="yellow", bold=True)
|
|
86
|
-
click.echo(f" {results['public_url']}\n")
|
|
87
|
-
|
|
88
|
-
if "presigned_url" in results:
|
|
89
|
-
click.secho("Download URL:", fg="yellow", bold=True)
|
|
90
|
-
click.echo(f" {results['presigned_url']}\n")
|
|
91
|
-
|
|
92
|
-
click.echo("=" * 60 + "\n")
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
@click.group(invoke_without_command=True)
|
|
96
|
-
@click.option(
|
|
97
|
-
"--scan",
|
|
98
|
-
type=str,
|
|
99
|
-
help="Repository to scan (username/repo or full GitHub URL)"
|
|
100
|
-
)
|
|
101
|
-
@click.option("--vex", is_flag=True, help="Perform vulnerability scanning")
|
|
102
|
-
@click.option("--sbom", is_flag=True, help="Generate Software Bill of Materials")
|
|
103
|
-
@click.option("--detect-secret", is_flag=True, help="Detect secrets in repository")
|
|
104
|
-
@click.option("--fullscan", is_flag=True, help="Perform all scan types")
|
|
105
|
-
@click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
|
|
106
|
-
@click.version_option(version="0.1.0", prog_name="prismor")
|
|
107
|
-
@click.pass_context
|
|
108
|
-
def cli(ctx, scan: Optional[str], vex: bool, sbom: bool, detect_secret: bool,
|
|
109
|
-
fullscan: bool, output_json: bool):
|
|
110
|
-
"""Prismor CLI - Security scanning tool for GitHub repositories.
|
|
111
|
-
|
|
112
|
-
Examples:
|
|
113
|
-
prismor --scan username/repo --vex
|
|
114
|
-
prismor --scan username/repo --fullscan
|
|
115
|
-
prismor --scan https://github.com/username/repo --detect-secret
|
|
116
|
-
"""
|
|
117
|
-
# If no command and no scan option, show help
|
|
118
|
-
if ctx.invoked_subcommand is None and not scan:
|
|
119
|
-
click.echo(ctx.get_help())
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
# If scan option is provided, perform the scan
|
|
123
|
-
if scan:
|
|
124
|
-
# Check if at least one scan type is selected
|
|
125
|
-
if not any([vex, sbom, detect_secret, fullscan]):
|
|
126
|
-
print_error("Please specify at least one scan type: --vex, --sbom, --detect-secret, or --fullscan")
|
|
127
|
-
sys.exit(1)
|
|
128
|
-
|
|
129
|
-
try:
|
|
130
|
-
# Initialize API client
|
|
131
|
-
print_info(f"Initializing Prismor scan for: {scan}")
|
|
132
|
-
client = PrismorClient()
|
|
133
|
-
|
|
134
|
-
# Determine scan type for display
|
|
135
|
-
scan_types = []
|
|
136
|
-
if fullscan:
|
|
137
|
-
scan_types.append("Full Scan (VEX + SBOM + Secret Detection)")
|
|
138
|
-
else:
|
|
139
|
-
if vex:
|
|
140
|
-
scan_types.append("VEX")
|
|
141
|
-
if sbom:
|
|
142
|
-
scan_types.append("SBOM")
|
|
143
|
-
if detect_secret:
|
|
144
|
-
scan_types.append("Secret Detection")
|
|
145
|
-
|
|
146
|
-
print_info(f"Scan type: {', '.join(scan_types)}")
|
|
147
|
-
print_info("Starting scan... (this may take a few minutes)")
|
|
148
|
-
|
|
149
|
-
# Perform scan
|
|
150
|
-
results = client.scan(
|
|
151
|
-
repo=scan,
|
|
152
|
-
vex=vex,
|
|
153
|
-
sbom=sbom,
|
|
154
|
-
detect_secret=detect_secret,
|
|
155
|
-
fullscan=fullscan
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
# Output results
|
|
159
|
-
if output_json:
|
|
160
|
-
click.echo(json.dumps(results, indent=2))
|
|
161
|
-
else:
|
|
162
|
-
print_success("Scan completed successfully!")
|
|
163
|
-
format_scan_results(results, ', '.join(scan_types))
|
|
164
|
-
|
|
165
|
-
except PrismorAPIError as e:
|
|
166
|
-
print_error(str(e))
|
|
167
|
-
sys.exit(1)
|
|
168
|
-
except Exception as e:
|
|
169
|
-
print_error(f"Unexpected error: {str(e)}")
|
|
170
|
-
sys.exit(1)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
@cli.command()
|
|
174
|
-
def version():
|
|
175
|
-
"""Display the version of Prismor CLI."""
|
|
176
|
-
click.echo("Prismor CLI v0.1.0")
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
@cli.command()
|
|
180
|
-
def config():
|
|
181
|
-
"""Display current configuration."""
|
|
182
|
-
import os
|
|
183
|
-
|
|
184
|
-
click.echo("\n" + "=" * 60)
|
|
185
|
-
click.secho(" Prismor CLI Configuration", fg="cyan", bold=True)
|
|
186
|
-
click.echo("=" * 60 + "\n")
|
|
187
|
-
|
|
188
|
-
# Check API key
|
|
189
|
-
api_key = os.environ.get("PRISMOR_API_KEY")
|
|
190
|
-
if api_key:
|
|
191
|
-
# Show only first and last 4 characters
|
|
192
|
-
masked_key = f"{api_key[:4]}...{api_key[-4:]}" if len(api_key) > 8 else "***"
|
|
193
|
-
print_success(f"PRISMOR_API_KEY: {masked_key}")
|
|
194
|
-
else:
|
|
195
|
-
print_error("PRISMOR_API_KEY: Not set")
|
|
196
|
-
click.echo("\nTo set your API key, run:")
|
|
197
|
-
click.echo(" export PRISMOR_API_KEY=your_api_key")
|
|
198
|
-
|
|
199
|
-
click.echo("\nAPI Endpoint: https://api.prismor.dev")
|
|
200
|
-
click.echo("=" * 60 + "\n")
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def main():
|
|
204
|
-
"""Entry point for the CLI."""
|
|
205
|
-
cli()
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if __name__ == "__main__":
|
|
209
|
-
main()
|
|
210
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|