kalibur 0.1.0__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.
- kalibur-0.1.0/PKG-INFO +89 -0
- kalibur-0.1.0/README.md +78 -0
- kalibur-0.1.0/kalibur/__init__.py +0 -0
- kalibur-0.1.0/kalibur/__main__.py +320 -0
- kalibur-0.1.0/kalibur/api.py +250 -0
- kalibur-0.1.0/kalibur/end.py +77 -0
- kalibur-0.1.0/kalibur/logo.txt +22 -0
- kalibur-0.1.0/kalibur/output.py +117 -0
- kalibur-0.1.0/kalibur/pull.py +219 -0
- kalibur-0.1.0/kalibur/push.py +153 -0
- kalibur-0.1.0/kalibur/report.py +374 -0
- kalibur-0.1.0/kalibur/scan.py +392 -0
- kalibur-0.1.0/kalibur/tarball.py +103 -0
- kalibur-0.1.0/kalibur.egg-info/PKG-INFO +89 -0
- kalibur-0.1.0/kalibur.egg-info/SOURCES.txt +22 -0
- kalibur-0.1.0/kalibur.egg-info/dependency_links.txt +1 -0
- kalibur-0.1.0/kalibur.egg-info/entry_points.txt +2 -0
- kalibur-0.1.0/kalibur.egg-info/requires.txt +1 -0
- kalibur-0.1.0/kalibur.egg-info/top_level.txt +1 -0
- kalibur-0.1.0/pyproject.toml +25 -0
- kalibur-0.1.0/setup.cfg +4 -0
- kalibur-0.1.0/tests/test_output.py +65 -0
- kalibur-0.1.0/tests/test_report.py +133 -0
- kalibur-0.1.0/tests/test_tarball.py +60 -0
kalibur-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kalibur
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI whitebox pentest assistant
|
|
5
|
+
Author-email: Scapin Ltd <contact@scapin.co.uk>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://kalibur.ai
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: certifi>=2024.0.0
|
|
11
|
+
|
|
12
|
+
# kalibur
|
|
13
|
+
|
|
14
|
+
AI-powered whitebox security scanner. Run `kalibur scan` against any local codebase.
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- Python 3.9 or later
|
|
19
|
+
- A Kalibur API key (get one at kalibur.ai)
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
**macOS**
|
|
24
|
+
```bash
|
|
25
|
+
brew install kalibur
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Linux / Windows**
|
|
29
|
+
```bash
|
|
30
|
+
pip install kalibur
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
export KALIBUR_API_KEY=your_api_key
|
|
37
|
+
|
|
38
|
+
# Scan current directory
|
|
39
|
+
kalibur scan
|
|
40
|
+
|
|
41
|
+
# Scan a specific directory
|
|
42
|
+
kalibur scan -t /path/to/project
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
| Flag | Env var | Description |
|
|
48
|
+
|------|---------|-------------|
|
|
49
|
+
| `-k KEY` | `KALIBUR_API_KEY` | API key (required) |
|
|
50
|
+
| `-t PATH` | | Directory to scan (default: `.`) |
|
|
51
|
+
|
|
52
|
+
## Output
|
|
53
|
+
|
|
54
|
+
Results are written to a timestamped subfolder inside a `kalibur/` directory in the scanned project:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
<target>/kalibur/
|
|
58
|
+
2026-05-03_14-23-45/
|
|
59
|
+
report.md
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Nothing is written if no findings are detected.
|
|
63
|
+
|
|
64
|
+
Add `kalibur/` to your `.gitignore` to keep scan output out of version control.
|
|
65
|
+
|
|
66
|
+
## Generating a PDF report
|
|
67
|
+
|
|
68
|
+
After reviewing and triaging findings in `report.md`, generate a branded PDF:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
kalibur report \
|
|
72
|
+
-r ~/project/kalibur/2026-05-03_14-23-45/report.md \
|
|
73
|
+
-f "Your Firm" \
|
|
74
|
+
-c "Client Name" \
|
|
75
|
+
-a "Your Name" \
|
|
76
|
+
-l ./logo.png \
|
|
77
|
+
-v "1.0"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
| Flag | Description |
|
|
81
|
+
|------|-------------|
|
|
82
|
+
| `-r FILE` | Path to `report.md` (required) |
|
|
83
|
+
| `-f NAME` | Assessor firm name (required) |
|
|
84
|
+
| `-c NAME` | Client name (required) |
|
|
85
|
+
| `-a NAME` | Assessor name (required) |
|
|
86
|
+
| `-l FILE` | Logo file, `.png` or `.jpg` (required) |
|
|
87
|
+
| `-v VER` | Report version, e.g. `1.0` (required) |
|
|
88
|
+
|
|
89
|
+
The PDF is saved as `report.pdf` alongside `report.md`. Valid triage statuses in `report.md` are `Open` and `Resolved`.
|
kalibur-0.1.0/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# kalibur
|
|
2
|
+
|
|
3
|
+
AI-powered whitebox security scanner. Run `kalibur scan` against any local codebase.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Python 3.9 or later
|
|
8
|
+
- A Kalibur API key (get one at kalibur.ai)
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
**macOS**
|
|
13
|
+
```bash
|
|
14
|
+
brew install kalibur
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Linux / Windows**
|
|
18
|
+
```bash
|
|
19
|
+
pip install kalibur
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
export KALIBUR_API_KEY=your_api_key
|
|
26
|
+
|
|
27
|
+
# Scan current directory
|
|
28
|
+
kalibur scan
|
|
29
|
+
|
|
30
|
+
# Scan a specific directory
|
|
31
|
+
kalibur scan -t /path/to/project
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
| Flag | Env var | Description |
|
|
37
|
+
|------|---------|-------------|
|
|
38
|
+
| `-k KEY` | `KALIBUR_API_KEY` | API key (required) |
|
|
39
|
+
| `-t PATH` | | Directory to scan (default: `.`) |
|
|
40
|
+
|
|
41
|
+
## Output
|
|
42
|
+
|
|
43
|
+
Results are written to a timestamped subfolder inside a `kalibur/` directory in the scanned project:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
<target>/kalibur/
|
|
47
|
+
2026-05-03_14-23-45/
|
|
48
|
+
report.md
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Nothing is written if no findings are detected.
|
|
52
|
+
|
|
53
|
+
Add `kalibur/` to your `.gitignore` to keep scan output out of version control.
|
|
54
|
+
|
|
55
|
+
## Generating a PDF report
|
|
56
|
+
|
|
57
|
+
After reviewing and triaging findings in `report.md`, generate a branded PDF:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
kalibur report \
|
|
61
|
+
-r ~/project/kalibur/2026-05-03_14-23-45/report.md \
|
|
62
|
+
-f "Your Firm" \
|
|
63
|
+
-c "Client Name" \
|
|
64
|
+
-a "Your Name" \
|
|
65
|
+
-l ./logo.png \
|
|
66
|
+
-v "1.0"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| Flag | Description |
|
|
70
|
+
|------|-------------|
|
|
71
|
+
| `-r FILE` | Path to `report.md` (required) |
|
|
72
|
+
| `-f NAME` | Assessor firm name (required) |
|
|
73
|
+
| `-c NAME` | Client name (required) |
|
|
74
|
+
| `-a NAME` | Assessor name (required) |
|
|
75
|
+
| `-l FILE` | Logo file, `.png` or `.jpg` (required) |
|
|
76
|
+
| `-v VER` | Report version, e.g. `1.0` (required) |
|
|
77
|
+
|
|
78
|
+
The PDF is saved as `report.pdf` alongside `report.md`. Valid triage statuses in `report.md` are `Open` and `Resolved`.
|
|
File without changes
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import getpass
|
|
3
|
+
import os
|
|
4
|
+
import ssl
|
|
5
|
+
import sys
|
|
6
|
+
import urllib.error
|
|
7
|
+
import urllib.request
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import certifi
|
|
11
|
+
|
|
12
|
+
from .end import run_end
|
|
13
|
+
from .pull import run_pull
|
|
14
|
+
from .push import run_push, _detect_project
|
|
15
|
+
from .report import run_report
|
|
16
|
+
from .scan import run_scan, print_banner, _USE_COLOR, _DIM, _RESET
|
|
17
|
+
|
|
18
|
+
API_URL = os.environ.get("KALIBUR_API_URL", "https://kalibur.ai")
|
|
19
|
+
_CONFIG_PATH = Path.home() / ".config" / "kalibur" / "key"
|
|
20
|
+
|
|
21
|
+
_RED = "\033[31m" if _USE_COLOR else ""
|
|
22
|
+
_RST = "\033[0m" if _USE_COLOR else ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _error(message: str) -> None:
|
|
26
|
+
print(f"{_RED}Error: {message}{_RST}", file=sys.stderr)
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _load_saved_key() -> str | None:
|
|
31
|
+
try:
|
|
32
|
+
if _CONFIG_PATH.exists():
|
|
33
|
+
key = _CONFIG_PATH.read_text().strip()
|
|
34
|
+
return key if key else None
|
|
35
|
+
except OSError:
|
|
36
|
+
pass
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _resolve_api_key(flag_value: str | None) -> str | None:
|
|
41
|
+
return flag_value or os.environ.get("KALIBUR_API_KEY") or _load_saved_key()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
_KEY_ERROR = "No API key found. Run 'kalibur login' to save your key, or pass it with -k <key>."
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _print_usage(desc: str, usage: str, flags: list[tuple[str, str]]) -> None:
|
|
48
|
+
lines: list[tuple[str, str]] = [("Usage:", usage), ("", "")] + flags
|
|
49
|
+
if _USE_COLOR:
|
|
50
|
+
print(f"{desc}\n")
|
|
51
|
+
for flag, desc_text in lines:
|
|
52
|
+
if flag == "Usage:":
|
|
53
|
+
print(f"{_DIM}{flag}{_RESET} {desc_text}")
|
|
54
|
+
elif flag == "":
|
|
55
|
+
print()
|
|
56
|
+
else:
|
|
57
|
+
print(f" \033[1m{flag}{_RESET} {desc_text}")
|
|
58
|
+
else:
|
|
59
|
+
print(f"{desc}\n")
|
|
60
|
+
for flag, desc_text in lines:
|
|
61
|
+
if flag == "":
|
|
62
|
+
print()
|
|
63
|
+
elif flag == "Usage:":
|
|
64
|
+
print(f"{flag} {desc_text}")
|
|
65
|
+
else:
|
|
66
|
+
print(f" {flag} {desc_text}")
|
|
67
|
+
print()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
_KEY_FLAG = ("-k", "Kalibur API key (overrides saved key and KALIBUR_API_KEY)")
|
|
71
|
+
_PROJECT_FLAG = ("-e", "Project name (skip interactive picker)")
|
|
72
|
+
|
|
73
|
+
_COMMAND_HELP: dict[str, tuple[str, str, list[tuple[str, str]]]] = {
|
|
74
|
+
"login": (
|
|
75
|
+
"Save your Kalibur API key locally so you do not need to pass -k or set KALIBUR_API_KEY on every command.",
|
|
76
|
+
"kalibur login",
|
|
77
|
+
[],
|
|
78
|
+
),
|
|
79
|
+
"scan": (
|
|
80
|
+
"Analyse the codebase in the current directory for security vulnerabilities. Kalibur thinks like an attacker, finds complex exploitable paths most scanners would miss, and generates a full markdown report ready for manual validation.",
|
|
81
|
+
"kalibur scan [-t <path>] [-k <key>]",
|
|
82
|
+
[
|
|
83
|
+
("-t", "Directory to scan (default: .)"),
|
|
84
|
+
_KEY_FLAG,
|
|
85
|
+
],
|
|
86
|
+
),
|
|
87
|
+
"report": (
|
|
88
|
+
"Generate a PDF pentest report from a completed report.md. Kalibur proofreads the findings, generates a relevant executive summary automatically, and produces a client-ready branded PDF.",
|
|
89
|
+
"kalibur report -r <report.md> -f <firm> -a <assessor> -c <client> -l <logo> -v <version>",
|
|
90
|
+
[
|
|
91
|
+
("-r", "Path to report.md"),
|
|
92
|
+
("-f", "Assessor firm name"),
|
|
93
|
+
("-a", "Assessor name"),
|
|
94
|
+
("-c", "Client name"),
|
|
95
|
+
("-l", "Logo file (.png or .jpg)"),
|
|
96
|
+
("-v", "Report version (e.g. 1.0)"),
|
|
97
|
+
_KEY_FLAG,
|
|
98
|
+
],
|
|
99
|
+
),
|
|
100
|
+
"push": (
|
|
101
|
+
"Save a snapshot of your local ./kalibur/ folder. Use this to back up your local edits between sessions or share them with teammates.",
|
|
102
|
+
"kalibur push [-k <key>]",
|
|
103
|
+
[_KEY_FLAG],
|
|
104
|
+
),
|
|
105
|
+
"pull": (
|
|
106
|
+
"Restore a previously saved snapshot to your local ./kalibur/ folder. Useful for resuming work on another machine or recovering a backup.",
|
|
107
|
+
"kalibur pull [-e <project>] [-k <key>]",
|
|
108
|
+
[_PROJECT_FLAG, _KEY_FLAG],
|
|
109
|
+
),
|
|
110
|
+
"end": (
|
|
111
|
+
"End an engagement and permanently delete all its data: remote scans, findings, snapshots, and the local ./kalibur/ folder. This cannot be undone.",
|
|
112
|
+
"kalibur end [-e <project>] [-k <key>]",
|
|
113
|
+
[_PROJECT_FLAG, _KEY_FLAG],
|
|
114
|
+
),
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _print_report_usage() -> None:
|
|
119
|
+
desc, usage, flags = _COMMAND_HELP["report"]
|
|
120
|
+
_print_usage(desc, usage, flags)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _print_help() -> None:
|
|
124
|
+
commands = [
|
|
125
|
+
("login", "Save your API key locally"),
|
|
126
|
+
("scan", "Run a whitebox security assessment of the current directory"),
|
|
127
|
+
("report", "Generate a PDF pentest report from a report.md file"),
|
|
128
|
+
("push", "Create a snapshot of your local kalibur folder"),
|
|
129
|
+
("pull", "Restore a snapshot to your local kalibur folder"),
|
|
130
|
+
("end", "End an engagement and permanently delete all its data"),
|
|
131
|
+
]
|
|
132
|
+
if _USE_COLOR:
|
|
133
|
+
print(f"{_DIM}Usage:{_RESET} kalibur <command> [options]\n")
|
|
134
|
+
print("Commands:\n")
|
|
135
|
+
for name, desc in commands:
|
|
136
|
+
print(f" \033[1m{name:<8}{_RESET} {desc}")
|
|
137
|
+
print(f"\n{_DIM}Run 'kalibur <command> --help' for options.{_RESET}")
|
|
138
|
+
else:
|
|
139
|
+
print("Usage: kalibur <command> [options]\n")
|
|
140
|
+
print("Commands:\n")
|
|
141
|
+
for name, desc in commands:
|
|
142
|
+
print(f" {name:<8} {desc}")
|
|
143
|
+
print("\nRun 'kalibur <command> --help' for options.")
|
|
144
|
+
print()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _validate_key(key: str) -> str | None:
|
|
148
|
+
ssl_ctx = ssl.create_default_context(cafile=certifi.where())
|
|
149
|
+
req = urllib.request.Request(
|
|
150
|
+
f"{API_URL}/api/cli/me",
|
|
151
|
+
method="GET",
|
|
152
|
+
headers={"Authorization": f"Bearer {key}", "User-Agent": "kalibur-cli"},
|
|
153
|
+
)
|
|
154
|
+
try:
|
|
155
|
+
with urllib.request.urlopen(req, timeout=10, context=ssl_ctx) as resp:
|
|
156
|
+
import json
|
|
157
|
+
data = json.loads(resp.read())
|
|
158
|
+
return data.get("name")
|
|
159
|
+
except urllib.error.HTTPError as e:
|
|
160
|
+
if e.code == 401:
|
|
161
|
+
return None
|
|
162
|
+
raise
|
|
163
|
+
except Exception:
|
|
164
|
+
raise
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def run_login() -> None:
|
|
168
|
+
_GREEN = "\033[32m" if _USE_COLOR else ""
|
|
169
|
+
try:
|
|
170
|
+
key = getpass.getpass("Kalibur API key: ").strip()
|
|
171
|
+
except (EOFError, KeyboardInterrupt):
|
|
172
|
+
print()
|
|
173
|
+
print(f"{_RED}Cancelled.{_RST}", file=sys.stderr)
|
|
174
|
+
sys.exit(130)
|
|
175
|
+
if not key:
|
|
176
|
+
_error("API key cannot be empty")
|
|
177
|
+
print()
|
|
178
|
+
try:
|
|
179
|
+
name = _validate_key(key)
|
|
180
|
+
except Exception as e:
|
|
181
|
+
_error(f"Could not reach Kalibur API: {e}")
|
|
182
|
+
if name is None:
|
|
183
|
+
_error("Invalid API key")
|
|
184
|
+
_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
185
|
+
_CONFIG_PATH.write_text(key)
|
|
186
|
+
_CONFIG_PATH.chmod(0o600)
|
|
187
|
+
print(f"{_GREEN}Logged in. API key saved to {_CONFIG_PATH}{_RST}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class _Parser(argparse.ArgumentParser):
|
|
191
|
+
def error(self, message: str) -> None:
|
|
192
|
+
sys.stdout.flush()
|
|
193
|
+
self.print_usage(sys.stderr)
|
|
194
|
+
print(f"\n{_RED}Error: {message}{_RST}", file=sys.stderr)
|
|
195
|
+
sys.exit(2)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def main() -> None:
|
|
199
|
+
print_banner(Path(".").resolve())
|
|
200
|
+
sys.stdout.flush()
|
|
201
|
+
|
|
202
|
+
if len(sys.argv) == 1 or (len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help")):
|
|
203
|
+
_print_help()
|
|
204
|
+
sys.exit(0)
|
|
205
|
+
|
|
206
|
+
if (
|
|
207
|
+
len(sys.argv) >= 3
|
|
208
|
+
and sys.argv[1] in _COMMAND_HELP
|
|
209
|
+
and sys.argv[2] in ("-h", "--help")
|
|
210
|
+
):
|
|
211
|
+
desc, usage, flags = _COMMAND_HELP[sys.argv[1]]
|
|
212
|
+
_print_usage(desc, usage, flags)
|
|
213
|
+
sys.exit(0)
|
|
214
|
+
|
|
215
|
+
if len(sys.argv) == 2 and sys.argv[1] == "login":
|
|
216
|
+
run_login()
|
|
217
|
+
sys.exit(0)
|
|
218
|
+
|
|
219
|
+
parser = _Parser(prog="kalibur", description="AI whitebox pentest assistant")
|
|
220
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
221
|
+
|
|
222
|
+
sub.add_parser("login", help="Save your API key locally")
|
|
223
|
+
|
|
224
|
+
scan_cmd = sub.add_parser("scan", help="Run a whitebox assessment of the current directory")
|
|
225
|
+
scan_cmd.add_argument("-t", default=".", metavar="PATH", dest="target", help="Directory to scan (default: .)")
|
|
226
|
+
scan_cmd.add_argument("-k", metavar="KEY", dest="api_key", help="Kalibur API key")
|
|
227
|
+
|
|
228
|
+
report_cmd = sub.add_parser("report", help="Generate a pentest report from a report.md")
|
|
229
|
+
report_cmd.add_argument("-r", metavar="FILE", dest="report", help="Path to report.md")
|
|
230
|
+
report_cmd.add_argument("-f", metavar="NAME", dest="firm", help="Assessor firm name")
|
|
231
|
+
report_cmd.add_argument("-a", metavar="NAME", dest="assessor", help="Assessor name")
|
|
232
|
+
report_cmd.add_argument("-c", metavar="NAME", dest="client", help="Client name")
|
|
233
|
+
report_cmd.add_argument("-l", metavar="FILE", dest="logo", help="Logo file (.png or .jpg)")
|
|
234
|
+
report_cmd.add_argument("-v", metavar="VER", dest="version", help="Report version (e.g. 1.0)")
|
|
235
|
+
report_cmd.add_argument("-k", metavar="KEY", dest="api_key", help="Kalibur API key")
|
|
236
|
+
|
|
237
|
+
push_cmd = sub.add_parser("push", help="Push local kalibur folders")
|
|
238
|
+
push_cmd.add_argument("-k", metavar="KEY", dest="api_key", help="Kalibur API key")
|
|
239
|
+
|
|
240
|
+
pull_cmd = sub.add_parser("pull", help="Pull a saved snapshot")
|
|
241
|
+
pull_cmd.add_argument("-e", metavar="PROJECT", dest="project", help="Project name (skip interactive picker)")
|
|
242
|
+
pull_cmd.add_argument("-k", metavar="KEY", dest="api_key", help="Kalibur API key")
|
|
243
|
+
|
|
244
|
+
end_cmd = sub.add_parser("end", help="End an engagement and permanently delete all its data")
|
|
245
|
+
end_cmd.add_argument("-e", metavar="PROJECT", dest="project", help="Project name (skip interactive picker)")
|
|
246
|
+
end_cmd.add_argument("-k", metavar="KEY", dest="api_key", help="Kalibur API key")
|
|
247
|
+
|
|
248
|
+
args = parser.parse_args()
|
|
249
|
+
|
|
250
|
+
if args.command == "login":
|
|
251
|
+
run_login()
|
|
252
|
+
sys.exit(0)
|
|
253
|
+
|
|
254
|
+
api_key = _resolve_api_key(args.api_key)
|
|
255
|
+
|
|
256
|
+
if args.command == "scan":
|
|
257
|
+
if not api_key:
|
|
258
|
+
_error(_KEY_ERROR)
|
|
259
|
+
|
|
260
|
+
target = Path(args.target).resolve()
|
|
261
|
+
if not target.exists():
|
|
262
|
+
_error(f"target directory does not exist: {target}")
|
|
263
|
+
|
|
264
|
+
output_dir = target / "kalibur"
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
run_scan(
|
|
268
|
+
target_dir=target,
|
|
269
|
+
api_key=api_key,
|
|
270
|
+
api_url=API_URL,
|
|
271
|
+
output_dir=output_dir,
|
|
272
|
+
project=_detect_project(),
|
|
273
|
+
)
|
|
274
|
+
except KeyboardInterrupt:
|
|
275
|
+
print("\nInterrupted, quitting.", file=sys.stderr)
|
|
276
|
+
sys.exit(130)
|
|
277
|
+
|
|
278
|
+
elif args.command == "report":
|
|
279
|
+
required_flags = [("-r", "report"), ("-f", "firm"), ("-a", "assessor"), ("-c", "client"), ("-l", "logo"), ("-v", "version")]
|
|
280
|
+
missing = [flag for flag, attr in required_flags if not getattr(args, attr, None)]
|
|
281
|
+
if missing:
|
|
282
|
+
_print_report_usage()
|
|
283
|
+
sys.stdout.flush()
|
|
284
|
+
if len(missing) < len(required_flags):
|
|
285
|
+
print(f"\n{_RED}Error: missing required option(s): {', '.join(missing)}{_RST}", file=sys.stderr)
|
|
286
|
+
sys.exit(1)
|
|
287
|
+
sys.exit(0)
|
|
288
|
+
if not api_key:
|
|
289
|
+
_print_report_usage()
|
|
290
|
+
sys.stdout.flush()
|
|
291
|
+
_error(_KEY_ERROR)
|
|
292
|
+
run_report(
|
|
293
|
+
report_path=Path(args.report).resolve(),
|
|
294
|
+
firm=args.firm,
|
|
295
|
+
assessor=args.assessor,
|
|
296
|
+
client=args.client,
|
|
297
|
+
logo_path=Path(args.logo).resolve(),
|
|
298
|
+
version=args.version,
|
|
299
|
+
api_key=api_key,
|
|
300
|
+
api_url=API_URL,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
elif args.command == "push":
|
|
304
|
+
if not api_key:
|
|
305
|
+
_error(_KEY_ERROR)
|
|
306
|
+
run_push(api_key=api_key, api_url=API_URL)
|
|
307
|
+
|
|
308
|
+
elif args.command == "pull":
|
|
309
|
+
if not api_key:
|
|
310
|
+
_error(_KEY_ERROR)
|
|
311
|
+
run_pull(project_flag=getattr(args, "project", None), api_key=api_key, api_url=API_URL)
|
|
312
|
+
|
|
313
|
+
elif args.command == "end":
|
|
314
|
+
if not api_key:
|
|
315
|
+
_error(_KEY_ERROR)
|
|
316
|
+
run_end(project_flag=getattr(args, "project", None), api_key=api_key, api_url=API_URL)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
if __name__ == "__main__":
|
|
320
|
+
main()
|