kekkai-cli 1.0.2__py3-none-any.whl → 1.0.4__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.
- kekkai/cli.py +4 -9
- kekkai/output.py +64 -156
- {kekkai_cli-1.0.2.dist-info → kekkai_cli-1.0.4.dist-info}/METADATA +1 -1
- {kekkai_cli-1.0.2.dist-info → kekkai_cli-1.0.4.dist-info}/RECORD +7 -7
- {kekkai_cli-1.0.2.dist-info → kekkai_cli-1.0.4.dist-info}/WHEEL +0 -0
- {kekkai_cli-1.0.2.dist-info → kekkai_cli-1.0.4.dist-info}/entry_points.txt +0 -0
- {kekkai_cli-1.0.2.dist-info → kekkai_cli-1.0.4.dist-info}/top_level.txt +0 -0
kekkai/cli.py
CHANGED
|
@@ -16,11 +16,10 @@ from .output import (
|
|
|
16
16
|
VERSION,
|
|
17
17
|
ScanSummaryRow,
|
|
18
18
|
console,
|
|
19
|
-
|
|
19
|
+
print_dashboard,
|
|
20
20
|
print_scan_summary,
|
|
21
21
|
sanitize_error,
|
|
22
22
|
sanitize_for_terminal,
|
|
23
|
-
splash,
|
|
24
23
|
)
|
|
25
24
|
from .paths import app_base_dir, config_path, ensure_dir, is_within_base, safe_join
|
|
26
25
|
from .policy import (
|
|
@@ -259,10 +258,7 @@ def _handle_no_args() -> int:
|
|
|
259
258
|
cfg_path = config_path()
|
|
260
259
|
if not cfg_path.exists():
|
|
261
260
|
return _command_init(None, False)
|
|
262
|
-
|
|
263
|
-
console.print("Config exists. Run one of:")
|
|
264
|
-
console.print(" [green]kekkai scan[/green]")
|
|
265
|
-
console.print(" [green]kekkai init --force[/green]")
|
|
261
|
+
print_dashboard()
|
|
266
262
|
return 0
|
|
267
263
|
|
|
268
264
|
|
|
@@ -278,9 +274,8 @@ def _command_init(config_override: str | None, force: bool) -> int:
|
|
|
278
274
|
ensure_dir(cfg_path.parent)
|
|
279
275
|
|
|
280
276
|
cfg_path.write_text(load_config_text(base_dir))
|
|
281
|
-
|
|
282
|
-
console.print(f"Initialized config at [cyan]{cfg_path}[/cyan]")
|
|
283
|
-
console.print(print_quick_start())
|
|
277
|
+
print_dashboard()
|
|
278
|
+
console.print(f"\n[success]Initialized config at[/success] [cyan]{cfg_path}[/cyan]\n")
|
|
284
279
|
return 0
|
|
285
280
|
|
|
286
281
|
|
kekkai/output.py
CHANGED
|
@@ -11,9 +11,9 @@ from dataclasses import dataclass
|
|
|
11
11
|
from typing import TYPE_CHECKING
|
|
12
12
|
|
|
13
13
|
from rich import box
|
|
14
|
-
from rich.
|
|
15
|
-
from rich.
|
|
16
|
-
from rich.
|
|
14
|
+
from rich.console import Console, Group
|
|
15
|
+
from rich.padding import Padding
|
|
16
|
+
from rich.rule import Rule
|
|
17
17
|
from rich.table import Table
|
|
18
18
|
from rich.text import Text
|
|
19
19
|
from rich.theme import Theme
|
|
@@ -23,12 +23,13 @@ if TYPE_CHECKING:
|
|
|
23
23
|
|
|
24
24
|
__all__ = [
|
|
25
25
|
"console",
|
|
26
|
-
"
|
|
27
|
-
"print_splash",
|
|
26
|
+
"print_dashboard",
|
|
28
27
|
"print_scan_summary",
|
|
29
28
|
"sanitize_for_terminal",
|
|
30
29
|
"sanitize_error",
|
|
31
30
|
"ScanSummaryRow",
|
|
31
|
+
"VERSION",
|
|
32
|
+
"splash",
|
|
32
33
|
]
|
|
33
34
|
|
|
34
35
|
ANSI_ESCAPE_PATTERN = re.compile(r"\x1b\[[0-9;]*[a-zA-Z]")
|
|
@@ -36,114 +37,88 @@ ANSI_ESCAPE_PATTERN = re.compile(r"\x1b\[[0-9;]*[a-zA-Z]")
|
|
|
36
37
|
KEKKAI_THEME = Theme(
|
|
37
38
|
{
|
|
38
39
|
"info": "dim cyan",
|
|
39
|
-
"warning": "
|
|
40
|
+
"warning": "yellow",
|
|
40
41
|
"danger": "bold red",
|
|
41
42
|
"success": "bold green",
|
|
42
43
|
"header": "bold white",
|
|
43
|
-
"title": "bold cyan",
|
|
44
|
-
"text": "white",
|
|
45
44
|
"muted": "dim white",
|
|
46
45
|
"brand": "bold cyan",
|
|
46
|
+
"command": "bold white",
|
|
47
|
+
"desc": "dim white",
|
|
47
48
|
}
|
|
48
49
|
)
|
|
49
50
|
|
|
50
51
|
console = Console(theme=KEKKAI_THEME)
|
|
51
52
|
|
|
52
53
|
BANNER_ASCII = r"""
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
▒██▒ █▄░▒████▒▒██▒ █▄▒██▒ █▄▓█ ▓██▒░██░
|
|
58
|
-
▒ ▒▒ ▓▒░░ ▒░ ░▒ ▒▒ ▓▒▒ ▒▒ ▓▒▒▒ ▓▒█░░▓
|
|
59
|
-
░ ░▒ ▒░ ░ ░ ░░ ░▒ ▒░░ ░▒ ▒░ ▒ ▒▒ ░ ▒ ░
|
|
60
|
-
░ ░░ ░ ░ ░ ░░ ░ ░ ░░ ░ ░ ▒
|
|
54
|
+
__ __ __ _
|
|
55
|
+
/ /_____ / /__/ /_____ _(_)
|
|
56
|
+
/ '_/ _ \/ '_/ '_/ _ `/ /
|
|
57
|
+
/_/\_\\___/_/\_/_/\_\\_,_/_/
|
|
61
58
|
"""
|
|
62
59
|
|
|
63
|
-
VERSION = "1.0.
|
|
64
|
-
|
|
60
|
+
VERSION = "1.0.4"
|
|
65
61
|
|
|
66
|
-
def print_splash() -> None:
|
|
67
|
-
"""Print the Kekkai splash screen with menu and tips."""
|
|
68
|
-
header_text = Text(BANNER_ASCII, style="header")
|
|
69
|
-
subtitle = Text("Local-first AppSec Orchestrator", style="info")
|
|
70
|
-
subtitle.justify = "center"
|
|
71
|
-
|
|
72
|
-
header_panel = Panel(
|
|
73
|
-
Align.center(Text.assemble(header_text, "\n", subtitle)),
|
|
74
|
-
box=box.HEAVY,
|
|
75
|
-
style="info",
|
|
76
|
-
padding=(1, 2),
|
|
77
|
-
)
|
|
78
62
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
63
|
+
def print_dashboard() -> None:
|
|
64
|
+
"""Render the professional Kekkai dashboard."""
|
|
65
|
+
if not console.is_terminal:
|
|
66
|
+
print(f"Kekkai v{VERSION} - Local-First AppSec Orchestrator")
|
|
67
|
+
return
|
|
82
68
|
|
|
83
|
-
|
|
84
|
-
menu_table.add_row("kekkai dojo", "Interact with your DefectDojo instance (import/export).")
|
|
85
|
-
menu_table.add_row("kekkai report", "Generate compliance and audit reports.")
|
|
86
|
-
menu_table.add_row("kekkai config", "Configure local settings and API keys.")
|
|
69
|
+
header_table = Table.grid(padding=(0, 2), expand=False)
|
|
87
70
|
|
|
88
|
-
|
|
89
|
-
menu_table,
|
|
90
|
-
title="[header]COMMAND MENU[/]",
|
|
91
|
-
border_style="dim cyan",
|
|
92
|
-
padding=(1, 2),
|
|
93
|
-
)
|
|
71
|
+
logo = Text(BANNER_ASCII.strip("\n"), style="brand")
|
|
94
72
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
tips_content.append(
|
|
100
|
-
" - Keep your CLI updated to catch the latest CVE signatures.\n\n", style="text"
|
|
101
|
-
)
|
|
73
|
+
meta = Text()
|
|
74
|
+
meta.append(f"\nv{VERSION}", style="muted")
|
|
75
|
+
meta.append("\nLocal-First AppSec Orchestrator", style="bold white")
|
|
76
|
+
meta.append("\nhttps://github.com/kademoslabs/kekkai", style="blue link")
|
|
102
77
|
|
|
103
|
-
|
|
104
|
-
tips_content.append(
|
|
105
|
-
" We are looking for collaborators! Star us on GitHub or submit a PR.\n\n", style="text"
|
|
106
|
-
)
|
|
78
|
+
header_table.add_row(logo, meta)
|
|
107
79
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
80
|
+
menu_table = Table(
|
|
81
|
+
box=None,
|
|
82
|
+
padding=(0, 2),
|
|
83
|
+
show_header=True,
|
|
84
|
+
header_style="dim cyan",
|
|
85
|
+
expand=True,
|
|
111
86
|
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
87
|
+
menu_table.add_column("Command", style="command", ratio=1)
|
|
88
|
+
menu_table.add_column("Description", style="desc", ratio=3)
|
|
89
|
+
|
|
90
|
+
menu_table.add_row("kekkai scan", "Run security scan in current directory")
|
|
91
|
+
menu_table.add_row("kekkai threatflow", "Generate AI-powered threat model")
|
|
92
|
+
menu_table.add_row("kekkai dojo", "Manage local DefectDojo instance")
|
|
93
|
+
menu_table.add_row("kekkai triage", "Interactive finding review (TUI)")
|
|
94
|
+
menu_table.add_row("kekkai report", "Generate compliance reports")
|
|
95
|
+
menu_table.add_row("kekkai config", "Manage settings and keys")
|
|
96
|
+
|
|
97
|
+
tips_grid = Table.grid(padding=(0, 1))
|
|
98
|
+
tips_grid.add_row("⚡", "[dim]Run scans locally before pushing to CI to save time.[/dim]")
|
|
99
|
+
tips_grid.add_row("🔒", "[dim]ThreatFlow requires an API key for remote models.[/dim]")
|
|
100
|
+
tips_grid.add_row("🤝", "[dim]Star us on GitHub to support open source development.[/dim]")
|
|
101
|
+
|
|
102
|
+
dashboard = Group(
|
|
103
|
+
Padding(header_table, (1, 1, 1, 1)),
|
|
104
|
+
Rule(style="dim cyan"),
|
|
105
|
+
Padding(menu_table, (1, 2)),
|
|
106
|
+
Rule(style="dim cyan"),
|
|
107
|
+
Padding(tips_grid, (1, 2, 1, 2)),
|
|
108
|
+
Text("\n"),
|
|
118
109
|
)
|
|
119
110
|
|
|
120
|
-
console.print(
|
|
121
|
-
console.print(menu_panel)
|
|
122
|
-
console.print(tips_panel)
|
|
123
|
-
console.print()
|
|
111
|
+
console.print(dashboard)
|
|
124
112
|
|
|
125
113
|
|
|
126
114
|
def splash(*, force_plain: bool = False) -> str:
|
|
127
|
-
"""
|
|
128
|
-
|
|
129
|
-
Args:
|
|
130
|
-
force_plain: If True, return plain text regardless of TTY.
|
|
131
|
-
|
|
132
|
-
Returns:
|
|
133
|
-
Banner string for display.
|
|
134
|
-
"""
|
|
135
|
-
if force_plain or not console.is_terminal:
|
|
136
|
-
return f"Kekkai v{VERSION} - Local-First AppSec Orchestrator"
|
|
137
|
-
|
|
138
|
-
with console.capture() as capture:
|
|
139
|
-
print_splash()
|
|
140
|
-
result: str = capture.get()
|
|
141
|
-
return result
|
|
115
|
+
"""Deprecated: Use print_dashboard() instead."""
|
|
116
|
+
return f"Kekkai v{VERSION} - Local-First AppSec Orchestrator"
|
|
142
117
|
|
|
143
118
|
|
|
144
|
-
def
|
|
145
|
-
"""
|
|
146
|
-
return
|
|
119
|
+
def print_quick_start() -> str:
|
|
120
|
+
"""Deprecated: Content moved to print_dashboard()."""
|
|
121
|
+
return ""
|
|
147
122
|
|
|
148
123
|
|
|
149
124
|
@dataclass
|
|
@@ -161,15 +136,7 @@ def print_scan_summary(
|
|
|
161
136
|
*,
|
|
162
137
|
force_plain: bool = False,
|
|
163
138
|
) -> str:
|
|
164
|
-
"""Render scan results as a formatted table.
|
|
165
|
-
|
|
166
|
-
Args:
|
|
167
|
-
rows: Scan result rows to display.
|
|
168
|
-
force_plain: If True, return plain text regardless of TTY.
|
|
169
|
-
|
|
170
|
-
Returns:
|
|
171
|
-
Formatted table string.
|
|
172
|
-
"""
|
|
139
|
+
"""Render scan results as a formatted table."""
|
|
173
140
|
if force_plain or not console.is_terminal:
|
|
174
141
|
lines = ["Scan Summary:"]
|
|
175
142
|
for row in rows:
|
|
@@ -180,7 +147,7 @@ def print_scan_summary(
|
|
|
180
147
|
)
|
|
181
148
|
return "\n".join(lines)
|
|
182
149
|
|
|
183
|
-
table = Table(title="Scan Summary", show_header=True, header_style="bold")
|
|
150
|
+
table = Table(title="Scan Summary", show_header=True, header_style="bold", box=box.SIMPLE)
|
|
184
151
|
table.add_column("Scanner", style="cyan")
|
|
185
152
|
table.add_column("Status", style="green")
|
|
186
153
|
table.add_column("Findings", justify="right")
|
|
@@ -202,33 +169,12 @@ def print_scan_summary(
|
|
|
202
169
|
|
|
203
170
|
|
|
204
171
|
def sanitize_for_terminal(text: str) -> str:
|
|
205
|
-
"""Strip ANSI escape sequences from untrusted content.
|
|
206
|
-
|
|
207
|
-
Prevents terminal escape injection attacks where malicious content
|
|
208
|
-
could manipulate terminal display or hide warnings.
|
|
209
|
-
|
|
210
|
-
Args:
|
|
211
|
-
text: Potentially untrusted text to sanitize.
|
|
212
|
-
|
|
213
|
-
Returns:
|
|
214
|
-
Text with all ANSI escape sequences removed.
|
|
215
|
-
"""
|
|
172
|
+
"""Strip ANSI escape sequences from untrusted content."""
|
|
216
173
|
return ANSI_ESCAPE_PATTERN.sub("", text)
|
|
217
174
|
|
|
218
175
|
|
|
219
176
|
def sanitize_error(error: str | Exception, *, max_length: int = 200) -> str:
|
|
220
|
-
"""Sanitize error messages for user display.
|
|
221
|
-
|
|
222
|
-
Removes sensitive information like full paths and stack traces
|
|
223
|
-
to prevent information disclosure.
|
|
224
|
-
|
|
225
|
-
Args:
|
|
226
|
-
error: Error message or exception to sanitize.
|
|
227
|
-
max_length: Maximum length of returned message.
|
|
228
|
-
|
|
229
|
-
Returns:
|
|
230
|
-
Sanitized, truncated error message.
|
|
231
|
-
"""
|
|
177
|
+
"""Sanitize error messages for user display."""
|
|
232
178
|
message = str(error) if isinstance(error, Exception) else error
|
|
233
179
|
message = ANSI_ESCAPE_PATTERN.sub("", message)
|
|
234
180
|
message = re.sub(r"/[^\s:]+", "[path]", message)
|
|
@@ -239,41 +185,3 @@ def sanitize_error(error: str | Exception, *, max_length: int = 200) -> str:
|
|
|
239
185
|
message = message[:max_length] + "..."
|
|
240
186
|
|
|
241
187
|
return message
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def print_quick_start() -> str:
|
|
245
|
-
"""Render Quick Start guide panel.
|
|
246
|
-
|
|
247
|
-
Returns:
|
|
248
|
-
Formatted Quick Start panel string.
|
|
249
|
-
"""
|
|
250
|
-
if not console.is_terminal:
|
|
251
|
-
return (
|
|
252
|
-
"Quick Start:\n"
|
|
253
|
-
" 1. kekkai scan --repo . # Scan current directory\n"
|
|
254
|
-
" 2. kekkai threatflow # Generate threat model\n"
|
|
255
|
-
" 3. kekkai dojo up # Start DefectDojo\n"
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
content = Text()
|
|
259
|
-
content.append("1. ", style="bold cyan")
|
|
260
|
-
content.append("kekkai scan --repo .", style="green")
|
|
261
|
-
content.append(" # Scan current directory\n")
|
|
262
|
-
content.append("2. ", style="bold cyan")
|
|
263
|
-
content.append("kekkai threatflow", style="green")
|
|
264
|
-
content.append(" # Generate threat model\n")
|
|
265
|
-
content.append("3. ", style="bold cyan")
|
|
266
|
-
content.append("kekkai dojo up", style="green")
|
|
267
|
-
content.append(" # Start DefectDojo")
|
|
268
|
-
|
|
269
|
-
panel = Panel(
|
|
270
|
-
content,
|
|
271
|
-
title="[bold]Quick Start[/bold]",
|
|
272
|
-
border_style="green",
|
|
273
|
-
padding=(1, 2),
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
with console.capture() as capture:
|
|
277
|
-
console.print(panel)
|
|
278
|
-
result: str = capture.get()
|
|
279
|
-
return result
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
kekkai/__init__.py,sha256=_VrBvJRyqHiXs31S8HOhATk_O2iy-ac0_9X7rHH75j8,143
|
|
2
|
-
kekkai/cli.py,sha256=
|
|
2
|
+
kekkai/cli.py,sha256=f_IsxjlmzYKwl_x_BNIlVYBnBnSNfKskwclJdGwhWAo,35705
|
|
3
3
|
kekkai/config.py,sha256=LE7bKsmv5dim5KnZya0V7_LtviNQ1V0pMN_6FyAsMpc,13084
|
|
4
4
|
kekkai/dojo.py,sha256=DchLaTnDBwX0D14lTRdCtwql_II8aDEZ0JEq9F-n4MI,15887
|
|
5
5
|
kekkai/dojo_import.py,sha256=oI-vwpLITA7-U2_MxhaTp_PYfr5HqvcFy3VzKsWA6IY,6911
|
|
6
6
|
kekkai/manifest.py,sha256=Ph5xGDKuVxMW1GVIisRhxUelaiVZQe-W5sZWsq4lHqs,1887
|
|
7
|
-
kekkai/output.py,sha256=
|
|
7
|
+
kekkai/output.py,sha256=IJUZJK_Txhs7WPtSjtAR1eLes5Oqv2X7M8E3wmTO35M,5572
|
|
8
8
|
kekkai/paths.py,sha256=EcyG3CEOQFQygowu7O5Mp85dKkXWWvnm1h0j_BetGxY,1190
|
|
9
9
|
kekkai/policy.py,sha256=0XCUH-SbnO1PsM-exjSFHYHRnLkiNa50QfkyPakwNko,9792
|
|
10
10
|
kekkai/runner.py,sha256=MBFUiJ4sSVEGNbJ6cv-8p1WHaHqjio6yWEfr_K4GuTs,2037
|
|
@@ -83,8 +83,8 @@ portal/ops/monitoring.py,sha256=xhLbKjVaob709K4x0dEsOo4lh7Ddm2A4UE2ZmhfmMtI,1790
|
|
|
83
83
|
portal/ops/restore.py,sha256=rgzKoBIilgoPPv5gZhSSBuLKG1skKw5ryoCRR3d7CPQ,17058
|
|
84
84
|
portal/ops/secrets.py,sha256=wu2bUfJGctbGjyuGUgvUc_Y6IH1SCW16dExtqcKu_kg,14338
|
|
85
85
|
portal/ops/upgrade.py,sha256=fXsIXCJYYABdWDECDXkt7F2PidzNtO6Zr-g0Y5PLlVU,20106
|
|
86
|
-
kekkai_cli-1.0.
|
|
87
|
-
kekkai_cli-1.0.
|
|
88
|
-
kekkai_cli-1.0.
|
|
89
|
-
kekkai_cli-1.0.
|
|
90
|
-
kekkai_cli-1.0.
|
|
86
|
+
kekkai_cli-1.0.4.dist-info/METADATA,sha256=1pSlurcZ2U9rmTL-lu0cZwDrr8v0eWJZwQKlBXxIW7s,3652
|
|
87
|
+
kekkai_cli-1.0.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
88
|
+
kekkai_cli-1.0.4.dist-info/entry_points.txt,sha256=WUEX6IISnRcwlQAdhisPfIIV3Us2MYCwtJoyPpLJO44,75
|
|
89
|
+
kekkai_cli-1.0.4.dist-info/top_level.txt,sha256=u0J4T-Rnb0cgs0LfzZAUNt6nx1d5l7wKn8vOuo9FBEY,26
|
|
90
|
+
kekkai_cli-1.0.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|