centicodehelp 0.2.0__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.
- centicodehelp/__init__.py +3 -0
- centicodehelp/ai/llm_client.py +150 -0
- centicodehelp/ai/prompts.py +63 -0
- centicodehelp/ai/providers.py +195 -0
- centicodehelp/ai/reviewer.py +139 -0
- centicodehelp/analysis/binary_analyzer.py +124 -0
- centicodehelp/analysis/detector.py +80 -0
- centicodehelp/analysis/parser.py +41 -0
- centicodehelp/analysis/static_analyzer.py +194 -0
- centicodehelp/cli/banner.py +60 -0
- centicodehelp/cli/commands/ai.py +128 -0
- centicodehelp/cli/commands/analyze.py +389 -0
- centicodehelp/cli/commands/check.py +91 -0
- centicodehelp/cli/commands/config.py +67 -0
- centicodehelp/cli/commands/fix.py +170 -0
- centicodehelp/cli/commands/providers.py +81 -0
- centicodehelp/cli/commands/scan.py +129 -0
- centicodehelp/cli/commands/setup.py +93 -0
- centicodehelp/cli/commands/update.py +51 -0
- centicodehelp/cli/main.py +141 -0
- centicodehelp/cli/theme.py +50 -0
- centicodehelp/core/config.py +58 -0
- centicodehelp/core/models.py +31 -0
- centicodehelp/fixer/applier.py +23 -0
- centicodehelp/fixer/patcher.py +42 -0
- centicodehelp-0.2.0.dist-info/METADATA +79 -0
- centicodehelp-0.2.0.dist-info/RECORD +30 -0
- centicodehelp-0.2.0.dist-info/WHEEL +5 -0
- centicodehelp-0.2.0.dist-info/entry_points.txt +4 -0
- centicodehelp-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional, List, Tuple
|
|
4
|
+
import click
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.syntax import Syntax
|
|
8
|
+
|
|
9
|
+
from centicodehelp.core.config import load_config
|
|
10
|
+
from centicodehelp.analysis.detector import detect_language
|
|
11
|
+
from centicodehelp.analysis.static_analyzer import StaticAnalyzer
|
|
12
|
+
from centicodehelp.ai.llm_client import LLMClient
|
|
13
|
+
from centicodehelp.ai.reviewer import AIReviewer, AIReviewResult
|
|
14
|
+
from centicodehelp.ai.prompts import FIX_PROMPT_TEMPLATE
|
|
15
|
+
from centicodehelp.fixer.patcher import PatchGenerator
|
|
16
|
+
from centicodehelp.fixer.applier import PatchApplier
|
|
17
|
+
from centicodehelp.cli.commands.scan import collect_files
|
|
18
|
+
from centicodehelp.cli.theme import (
|
|
19
|
+
console,
|
|
20
|
+
COLOR_PURPLE,
|
|
21
|
+
COLOR_BLUE,
|
|
22
|
+
COLOR_INDIGO,
|
|
23
|
+
COLOR_SUCCESS,
|
|
24
|
+
COLOR_ERROR,
|
|
25
|
+
COLOR_CRITICAL,
|
|
26
|
+
COLOR_HIGH,
|
|
27
|
+
COLOR_MEDIUM,
|
|
28
|
+
COLOR_LOW,
|
|
29
|
+
COLOR_INFO,
|
|
30
|
+
COLOR_TEXT,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@click.command(name="fix", help="Vollautomatisch: Scan + KI-Audit + Auto-Fix mit Backup.")
|
|
34
|
+
@click.argument("path", default=".", type=click.Path(exists=False), required=False)
|
|
35
|
+
@click.option("--provider", default=None, help="KI-Anbieter (Standard: ollama).")
|
|
36
|
+
@click.option("--model", default=None, help="Modellname (Standard: llama3.2).")
|
|
37
|
+
@click.option("-y", "--yes", is_flag=True, default=False, help="Änderungen ohne Rückfrage anwenden.")
|
|
38
|
+
def fix_cmd(path: str, provider: Optional[str], model: Optional[str], yes: bool):
|
|
39
|
+
"""
|
|
40
|
+
ct fix [pfad] - Quellcode automatisch reparieren.
|
|
41
|
+
|
|
42
|
+
Ablauf:
|
|
43
|
+
1. Schneller statischer Scan
|
|
44
|
+
2. KI-Tiefenanalyse (Standard: Ollama / llama3.2)
|
|
45
|
+
3. Automatische Patch-Erstellung mit .bak-Backup
|
|
46
|
+
"""
|
|
47
|
+
target = Path(path)
|
|
48
|
+
if not target.exists():
|
|
49
|
+
console.print(f"\n[bold {COLOR_ERROR}]⌠Pfad nicht gefunden:[/bold {COLOR_ERROR}] [white]{target}[/white]\n")
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
files = collect_files(target)
|
|
53
|
+
if not files:
|
|
54
|
+
console.print(f"\n[bold {COLOR_MEDIUM}]âš ï¸ Keine Dateien in {target} gefunden.[/bold {COLOR_MEDIUM}]\n")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
config = load_config()
|
|
58
|
+
sel_provider = provider or config.default_provider
|
|
59
|
+
sel_model = model or config.default_model
|
|
60
|
+
|
|
61
|
+
console.print(
|
|
62
|
+
Panel(
|
|
63
|
+
f"[bold]Ziel:[/bold] [yellow]{target}[/yellow] ({len(files)} Datei(en))\n"
|
|
64
|
+
f"[bold]KI-Reparatur:[/bold] [bold {COLOR_PURPLE}]{sel_provider}/{sel_model}[/bold {COLOR_PURPLE}]\n"
|
|
65
|
+
f"[bold]Backups:[/bold] [green]Aktiviert (.bak Dateien)[/green]",
|
|
66
|
+
title=f"[bold {COLOR_PURPLE}]âš¡ centicodehelp AUTO-FIX[/bold {COLOR_PURPLE}]",
|
|
67
|
+
border_style=COLOR_INDIGO,
|
|
68
|
+
expand=False,
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
console.print()
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
client = LLMClient(provider=sel_provider, model=sel_model)
|
|
75
|
+
reviewer = AIReviewer(client)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
console.print(f"[bold {COLOR_ERROR}]⌠{e}[/bold {COLOR_ERROR}]\n")
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
static_analyzer = StaticAnalyzer()
|
|
81
|
+
applier = PatchApplier(backup=True)
|
|
82
|
+
|
|
83
|
+
total_inspected = 0
|
|
84
|
+
total_found_issues = 0
|
|
85
|
+
total_fixed_files = 0
|
|
86
|
+
|
|
87
|
+
files_with_fixes: List[Tuple[Path, str, str, str]] = [] # (file, lang, old_code, clean_code)
|
|
88
|
+
|
|
89
|
+
for f_path in files:
|
|
90
|
+
lang = detect_language(f_path)
|
|
91
|
+
if not lang or lang == "binary":
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
total_inspected += 1
|
|
95
|
+
static_findings = static_analyzer.analyze_file(f_path, lang)
|
|
96
|
+
if not static_findings:
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
total_found_issues += len(static_findings)
|
|
100
|
+
|
|
101
|
+
with console.status(f"[bold {COLOR_PURPLE}]Generiere KI-Lösung für {f_path.name}...[/bold {COLOR_PURPLE}]", spinner="dots12"):
|
|
102
|
+
try:
|
|
103
|
+
source_code = f_path.read_text(encoding="utf-8", errors="replace")
|
|
104
|
+
findings_text = "\n".join([f"- Zeile {f.line or '?'}: {f.description}" for f in static_findings])
|
|
105
|
+
fix_prompt = FIX_PROMPT_TEMPLATE.format(
|
|
106
|
+
file_path=str(f_path),
|
|
107
|
+
language=lang,
|
|
108
|
+
findings_text=findings_text,
|
|
109
|
+
source_code=source_code,
|
|
110
|
+
)
|
|
111
|
+
raw_fixed = client.complete(
|
|
112
|
+
fix_prompt,
|
|
113
|
+
system="Du bist ein Sicherheitsexperte. Ersetze eval() IMMER durch ast.literal_eval(). Entferne alle print() Debug-Meldungen. Gib NUR den korrigierten Code zurück."
|
|
114
|
+
)
|
|
115
|
+
clean_code = PatchGenerator.extract_code_from_markdown(raw_fixed)
|
|
116
|
+
diff = PatchGenerator.generate_diff(source_code, clean_code, file_path=f_path.name)
|
|
117
|
+
|
|
118
|
+
if diff:
|
|
119
|
+
files_with_fixes.append((f_path, lang, source_code, clean_code))
|
|
120
|
+
except Exception as e:
|
|
121
|
+
console.print(f"[bold {COLOR_ERROR}]⌠Fehler bei {f_path.name}:[/bold {COLOR_ERROR}] {e}")
|
|
122
|
+
|
|
123
|
+
if not files_with_fixes:
|
|
124
|
+
console.print(f"[bold {COLOR_SUCCESS}]✅ Keine reparaturbedürftigen Fehler gefunden![/bold {COLOR_SUCCESS}]\n")
|
|
125
|
+
console.print(f"[dim]Geprüft: {total_inspected} Datei(en), 0 Korrekturen nötig.[/dim]\n")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
# Zeige Vorschau
|
|
129
|
+
console.print(f"[bold #38BDF8]Korrekturen für {len(files_with_fixes)} Datei(en) vorbereitet:[/bold #38BDF8]")
|
|
130
|
+
for f_path, lang, old_c, new_c in files_with_fixes:
|
|
131
|
+
diff_text = PatchGenerator.generate_diff(old_c, new_c, file_path=f_path.name)
|
|
132
|
+
syntax_diff = Syntax(diff_text, "diff", theme="monokai", line_numbers=True)
|
|
133
|
+
console.print(
|
|
134
|
+
Panel(
|
|
135
|
+
syntax_diff,
|
|
136
|
+
title=f"[bold {COLOR_PURPLE}]Vorschau-Patch für {f_path.name}[/bold {COLOR_PURPLE}]",
|
|
137
|
+
border_style=COLOR_INDIGO,
|
|
138
|
+
expand=False,
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Sicherheitsabfrage
|
|
143
|
+
confirmed = yes
|
|
144
|
+
if not yes:
|
|
145
|
+
confirmed = click.confirm(
|
|
146
|
+
f"\n{len(files_with_fixes)} Datei(en) jetzt ändern? (Backups als .bak werden angelegt)",
|
|
147
|
+
default=True,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if confirmed:
|
|
151
|
+
for f_path, lang, old_c, new_c in files_with_fixes:
|
|
152
|
+
if applier.apply_fix(f_path, new_c):
|
|
153
|
+
total_fixed_files += 1
|
|
154
|
+
console.print(f"[bold {COLOR_SUCCESS}]✔ {f_path.name} erfolgreich gepatcht![/bold {COLOR_SUCCESS}] [dim]({f_path.name}.bak gesichert)[/dim]")
|
|
155
|
+
else:
|
|
156
|
+
console.print(f"[bold {COLOR_ERROR}]✘ Fehler beim Schreiben von {f_path.name}[/bold {COLOR_ERROR}]")
|
|
157
|
+
else:
|
|
158
|
+
console.print("[dim]Abgebrochen. Keine Dateien geändert.[/dim]")
|
|
159
|
+
|
|
160
|
+
# Abschluss-Zusammenfassung
|
|
161
|
+
console.print()
|
|
162
|
+
summary_panel = Panel(
|
|
163
|
+
f"[bold {COLOR_SUCCESS}]✅ {total_inspected} Dateien geprüft, {total_found_issues} Probleme gefunden, {total_fixed_files} Datei(en) behoben.[/bold {COLOR_SUCCESS}]",
|
|
164
|
+
title=f"[bold {COLOR_PURPLE}]âš¡ ZUSAMMENFASSUNG[/bold {COLOR_PURPLE}]",
|
|
165
|
+
border_style=COLOR_SUCCESS if total_fixed_files > 0 else COLOR_INDIGO,
|
|
166
|
+
expand=False,
|
|
167
|
+
)
|
|
168
|
+
console.print(summary_panel)
|
|
169
|
+
console.print()
|
|
170
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import time
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
from rich.align import Align
|
|
6
|
+
|
|
7
|
+
from centicodehelp.ai.providers import list_providers, get_provider
|
|
8
|
+
from centicodehelp.core.config import load_config, save_config
|
|
9
|
+
from centicodehelp.cli.theme import (
|
|
10
|
+
console,
|
|
11
|
+
COLOR_PURPLE,
|
|
12
|
+
COLOR_BLUE,
|
|
13
|
+
COLOR_INDIGO,
|
|
14
|
+
COLOR_TEXT,
|
|
15
|
+
COLOR_SUCCESS,
|
|
16
|
+
COLOR_ERROR,
|
|
17
|
+
COLOR_HIGH,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
@click.command(name="providers", help="Unterstützte KI-Anbieter & Modelle anzeigen oder Standard wählen.")
|
|
21
|
+
@click.option("--set-default", "set_default", type=str, help="Standard-KI-Anbieter festlegen (z. B. openai, anthropic, ollama)")
|
|
22
|
+
@click.option("--details", is_flag=True, help="Detaillierte Liste aller Modelle und Umgebungsvariablen anzeigen.")
|
|
23
|
+
def providers_cmd(set_default: str, details: bool):
|
|
24
|
+
"""Befehl zur Inspektion und Konfiguration aller KI-Anbieter."""
|
|
25
|
+
config = load_config()
|
|
26
|
+
|
|
27
|
+
if set_default:
|
|
28
|
+
provider_info = get_provider(set_default)
|
|
29
|
+
if not provider_info:
|
|
30
|
+
console.print(f"\n[bold {COLOR_ERROR}]⌠Unbekannter Anbieter:[/bold {COLOR_ERROR}] '{set_default}'")
|
|
31
|
+
console.print(f"[dim]Nutze [bold {COLOR_BLUE}]ct providers[/bold {COLOR_BLUE}] für alle verfügbaren Optionen.[/dim]\n")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
config.default_provider = provider_info.id
|
|
35
|
+
config.default_model = provider_info.default_model
|
|
36
|
+
save_config(config)
|
|
37
|
+
console.print(f"\n[bold {COLOR_SUCCESS}]✅ Standard-Anbieter erfolgreich gesetzt:[/bold {COLOR_SUCCESS}] [bold {COLOR_PURPLE}]{provider_info.name}[/bold {COLOR_PURPLE}] (Standard-Modell: [cyan]{provider_info.default_model}[/cyan])\n")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
table = Table(
|
|
41
|
+
title=f"\n[bold {COLOR_PURPLE}]âš¡ centicodehelp KI-ANBIETER REGISTRY[/bold {COLOR_PURPLE}] [dim](via LiteLLM)[/dim]",
|
|
42
|
+
show_header=True,
|
|
43
|
+
header_style=f"bold {COLOR_BLUE}",
|
|
44
|
+
border_style=COLOR_INDIGO,
|
|
45
|
+
expand=True,
|
|
46
|
+
)
|
|
47
|
+
table.add_column("Provider ID", style=f"bold {COLOR_PURPLE}", width=14)
|
|
48
|
+
table.add_column("Anbieter", style="white", width=20)
|
|
49
|
+
table.add_column("Standard-Modell", style=f"bold {COLOR_BLUE}", width=24)
|
|
50
|
+
table.add_column("Auth / Env Variable", style="dim white", width=28)
|
|
51
|
+
table.add_column("Status", justify="center", width=14)
|
|
52
|
+
|
|
53
|
+
all_providers = list_providers()
|
|
54
|
+
for p in all_providers:
|
|
55
|
+
is_default = (p.id == config.default_provider)
|
|
56
|
+
status_badge = f"[bold on {COLOR_PURPLE}] AKTIV [/bold on {COLOR_PURPLE}]" if is_default else "[dim]Verfügbar[/dim]"
|
|
57
|
+
env_text = ", ".join(p.env_keys) if p.env_keys else f"[{COLOR_SUCCESS}]Lokal / Kein Key nötig[/{COLOR_SUCCESS}]"
|
|
58
|
+
table.add_row(p.id, p.name, p.default_model, env_text, status_badge)
|
|
59
|
+
|
|
60
|
+
console.print(table)
|
|
61
|
+
console.print(f"[dim]Tipp: [bold {COLOR_BLUE}]ct providers --set-default <id>[/bold {COLOR_BLUE}] ändert den Standard-Anbieter.[/dim]\n")
|
|
62
|
+
|
|
63
|
+
if details:
|
|
64
|
+
console.print(f"[bold {COLOR_PURPLE}]Detaillierte Modellübersicht:[/bold {COLOR_PURPLE}]")
|
|
65
|
+
for p in all_providers:
|
|
66
|
+
model_items = []
|
|
67
|
+
for m in p.models:
|
|
68
|
+
marker = "â " if m.is_default else "• "
|
|
69
|
+
model_items.append(f" {marker}[bold {COLOR_BLUE}]{m.name}[/bold {COLOR_BLUE}] [dim]({m.description})[/dim]")
|
|
70
|
+
|
|
71
|
+
panel_text = f"[bold]Beschreibung:[/bold] {p.description}\n[bold]Modelle:[/bold]\n" + "\n".join(model_items)
|
|
72
|
+
console.print(
|
|
73
|
+
Panel(
|
|
74
|
+
panel_text,
|
|
75
|
+
title=f"[bold {COLOR_PURPLE}]{p.name}[/bold {COLOR_PURPLE}] [dim]({p.id})[/dim]",
|
|
76
|
+
border_style=COLOR_INDIGO,
|
|
77
|
+
expand=False,
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
console.print()
|
|
81
|
+
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
import click
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
|
|
8
|
+
from centicodehelp.analysis.detector import detect_language
|
|
9
|
+
from centicodehelp.analysis.static_analyzer import StaticAnalyzer
|
|
10
|
+
from centicodehelp.analysis.binary_analyzer import BinaryAnalyzer
|
|
11
|
+
from centicodehelp.core.models import Finding, Severity
|
|
12
|
+
from centicodehelp.cli.theme import (
|
|
13
|
+
console,
|
|
14
|
+
COLOR_PURPLE,
|
|
15
|
+
COLOR_BLUE,
|
|
16
|
+
COLOR_INDIGO,
|
|
17
|
+
COLOR_SUCCESS,
|
|
18
|
+
COLOR_ERROR,
|
|
19
|
+
COLOR_CRITICAL,
|
|
20
|
+
COLOR_HIGH,
|
|
21
|
+
COLOR_MEDIUM,
|
|
22
|
+
COLOR_LOW,
|
|
23
|
+
COLOR_INFO,
|
|
24
|
+
COLOR_TEXT,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
SEVERITY_STYLES = {
|
|
28
|
+
Severity.CRITICAL: (COLOR_CRITICAL, "🔴 CRITICAL"),
|
|
29
|
+
Severity.HIGH: (f"bold {COLOR_HIGH}", "🟠HIGH"),
|
|
30
|
+
Severity.MEDIUM: (f"bold {COLOR_MEDIUM}", "🟡 MEDIUM"),
|
|
31
|
+
Severity.LOW: (f"{COLOR_LOW}", "🔵 LOW"),
|
|
32
|
+
Severity.INFO: (f"{COLOR_INFO}", "⚪ INFO"),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
def collect_files(target_path: Path) -> List[Path]:
|
|
36
|
+
"""Sammelt rekursiv alle analysierbaren Quellcode-Dateien."""
|
|
37
|
+
if target_path.is_file():
|
|
38
|
+
return [target_path]
|
|
39
|
+
files_to_analyze: List[Path] = []
|
|
40
|
+
for root, dirs, files in os.walk(target_path):
|
|
41
|
+
dirs[:] = [
|
|
42
|
+
d for d in dirs
|
|
43
|
+
if not d.startswith(".")
|
|
44
|
+
and d not in ("node_modules", "target", "build", "dist", "__pycache__", "venv", ".venv", "env")
|
|
45
|
+
]
|
|
46
|
+
for f in files:
|
|
47
|
+
files_to_analyze.append(Path(root) / f)
|
|
48
|
+
return files_to_analyze
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@click.command(name="scan", help="Schnelle statische Analyse (ohne KI) für einen Ordner oder eine Datei.")
|
|
52
|
+
@click.argument("path", default=".", type=click.Path(exists=False), required=False)
|
|
53
|
+
def scan_cmd(path: str):
|
|
54
|
+
"""
|
|
55
|
+
ct scan [pfad] - Schnelle statische Code- und Sicherheitsanalyse.
|
|
56
|
+
|
|
57
|
+
Prüft Code ohne KI auf Fehler, Sicherheitslücken und Bad Practices.
|
|
58
|
+
Standard: Aktueller Ordner (.)
|
|
59
|
+
"""
|
|
60
|
+
target = Path(path)
|
|
61
|
+
if not target.exists():
|
|
62
|
+
console.print(f"\n[bold {COLOR_ERROR}]⌠Pfad nicht gefunden:[/bold {COLOR_ERROR}] [white]{target}[/white]\n")
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
files = collect_files(target)
|
|
66
|
+
if not files:
|
|
67
|
+
console.print(f"\n[bold {COLOR_MEDIUM}]âš ï¸ Keine analysierbaren Dateien in {target} gefunden.[/bold {COLOR_MEDIUM}]\n")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
static_analyzer = StaticAnalyzer()
|
|
71
|
+
binary_analyzer = BinaryAnalyzer()
|
|
72
|
+
all_findings: List[Finding] = []
|
|
73
|
+
|
|
74
|
+
with console.status(f"[bold {COLOR_PURPLE}]Scanne {len(files)} Datei(en) in {target}...[/bold {COLOR_PURPLE}]", spinner="dots"):
|
|
75
|
+
for f in files:
|
|
76
|
+
lang = detect_language(f)
|
|
77
|
+
if not lang:
|
|
78
|
+
continue
|
|
79
|
+
if lang == "binary":
|
|
80
|
+
all_findings.extend(binary_analyzer.analyze_binary(f))
|
|
81
|
+
else:
|
|
82
|
+
all_findings.extend(static_analyzer.analyze_file(f, lang))
|
|
83
|
+
|
|
84
|
+
# Kurzausgabe nach Schweregrad
|
|
85
|
+
sev_counts = {
|
|
86
|
+
Severity.CRITICAL: 0,
|
|
87
|
+
Severity.HIGH: 0,
|
|
88
|
+
Severity.MEDIUM: 0,
|
|
89
|
+
Severity.LOW: 0,
|
|
90
|
+
Severity.INFO: 0,
|
|
91
|
+
}
|
|
92
|
+
for item in all_findings:
|
|
93
|
+
sev_counts[item.severity] = sev_counts.get(item.severity, 0) + 1
|
|
94
|
+
|
|
95
|
+
console.print()
|
|
96
|
+
if not all_findings:
|
|
97
|
+
console.print(
|
|
98
|
+
Panel(
|
|
99
|
+
f"[bold {COLOR_SUCCESS}]✅ Alles sauber![/bold {COLOR_SUCCESS}] Keine Probleme in [cyan]{len(files)}[/cyan] Datei(en) gefunden.",
|
|
100
|
+
title=f"[bold {COLOR_PURPLE}]âš¡ centicodehelp SCAN[/bold {COLOR_PURPLE}]",
|
|
101
|
+
border_style=COLOR_SUCCESS,
|
|
102
|
+
expand=False,
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
console.print()
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
table = Table(
|
|
109
|
+
title=f"[bold {COLOR_PURPLE}]âš¡ SCAN-ERGEBNIS:[/bold {COLOR_PURPLE}] [yellow]{target}[/yellow] ({len(files)} Datei(en))",
|
|
110
|
+
show_header=True,
|
|
111
|
+
header_style=f"bold {COLOR_BLUE}",
|
|
112
|
+
border_style=COLOR_INDIGO,
|
|
113
|
+
)
|
|
114
|
+
table.add_column("Schweregrad", width=16)
|
|
115
|
+
table.add_column("Anzahl", justify="right", width=10)
|
|
116
|
+
table.add_column("Status", width=20)
|
|
117
|
+
|
|
118
|
+
for sev in (Severity.CRITICAL, Severity.HIGH, Severity.MEDIUM, Severity.LOW, Severity.INFO):
|
|
119
|
+
count = sev_counts[sev]
|
|
120
|
+
color, badge = SEVERITY_STYLES[sev]
|
|
121
|
+
status_text = f"[{color}]{count} gefunden[/{color}]" if count > 0 else "[dim]0 gefunden[/dim]"
|
|
122
|
+
table.add_row(f"[{color}]{badge}[/{color}]", str(count), status_text)
|
|
123
|
+
|
|
124
|
+
console.print(table)
|
|
125
|
+
console.print(
|
|
126
|
+
f"[dim]Tipp: Nutze [bold {COLOR_BLUE}]ct fix[/bold {COLOR_BLUE}] zur automatischen Reparatur oder "
|
|
127
|
+
f"[bold {COLOR_BLUE}]ct check <datei>[/bold {COLOR_BLUE}] für Datei-Details.[/dim]\n"
|
|
128
|
+
)
|
|
129
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
|
|
5
|
+
from centicodehelp.core.config import load_config, save_config, get_config_path
|
|
6
|
+
from centicodehelp.ai.providers import list_providers, get_provider
|
|
7
|
+
from centicodehelp.cli.theme import (
|
|
8
|
+
console,
|
|
9
|
+
COLOR_PURPLE,
|
|
10
|
+
COLOR_BLUE,
|
|
11
|
+
COLOR_INDIGO,
|
|
12
|
+
COLOR_SUCCESS,
|
|
13
|
+
COLOR_TEXT,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
@click.command(name="setup", help="Interaktiver Einrichtungsassistent für KI-Anbieter & Modelle.")
|
|
17
|
+
def setup_cmd():
|
|
18
|
+
"""
|
|
19
|
+
ct setup - Interaktiver Wizard: Anbieter, API-Keys und Modelle kinderleicht konfigurieren.
|
|
20
|
+
"""
|
|
21
|
+
config = load_config()
|
|
22
|
+
|
|
23
|
+
console.print()
|
|
24
|
+
console.print(
|
|
25
|
+
Panel(
|
|
26
|
+
"[bold #38BDF8]Willkommen beim CentiCodeHelp Setup-Assistenten![/bold #38BDF8]\n"
|
|
27
|
+
"Hier kannst du deinen gewünschten KI-Anbieter und API-Schlüssel einrichten.",
|
|
28
|
+
title=f"[bold {COLOR_PURPLE}]âš¡ CENTICODEHELP SETUP[/bold {COLOR_PURPLE}]",
|
|
29
|
+
border_style=COLOR_INDIGO,
|
|
30
|
+
expand=False,
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
console.print()
|
|
34
|
+
|
|
35
|
+
# 1. Anbieter-Auswahl
|
|
36
|
+
console.print("[bold white]1. Wähle deinen bevorzugten KI-Anbieter:[/bold white]")
|
|
37
|
+
console.print(" [bold cyan]1[/bold cyan] - [bold green]Ollama[/bold green] [dim](Lokal, kostenlos, kein API-Key nötig)[/dim] â [italic]Standard[/italic]")
|
|
38
|
+
console.print(" [bold cyan]2[/bold cyan] - [bold blue]OpenAI[/bold blue] (GPT-4o, GPT-4o-mini)")
|
|
39
|
+
console.print(" [bold cyan]3[/bold cyan] - [bold yellow]Anthropic[/bold yellow] (Claude 3.5 Sonnet)")
|
|
40
|
+
console.print(" [bold cyan]4[/bold cyan] - [bold magenta]DeepSeek[/bold magenta] (DeepSeek Coder / V3)")
|
|
41
|
+
console.print(" [bold cyan]5[/bold cyan] - [bold red]Google Gemini[/bold red] (Gemini 1.5 Pro / Flash)")
|
|
42
|
+
console.print(" [bold cyan]6[/bold cyan] - [bold white]Anderer Anbieter[/bold white] (Groq, Mistral, OpenRouter etc.)")
|
|
43
|
+
console.print()
|
|
44
|
+
|
|
45
|
+
choice = click.prompt("Deine Wahl [1-6]", default="1", type=str)
|
|
46
|
+
provider_map = {
|
|
47
|
+
"1": ("ollama", "qwen2.5-coder:7b"),
|
|
48
|
+
"2": ("openai", "gpt-4o"),
|
|
49
|
+
"3": ("anthropic", "claude-3-5-sonnet"),
|
|
50
|
+
"4": ("deepseek", "deepseek-coder"),
|
|
51
|
+
"5": ("gemini", "gemini-1.5-pro"),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if choice in provider_map:
|
|
55
|
+
sel_provider, sel_default_model = provider_map[choice]
|
|
56
|
+
else:
|
|
57
|
+
sel_provider = click.prompt("Gib die Provider-ID ein (z. B. groq, mistral, openrouter)", default="groq").strip().lower()
|
|
58
|
+
p_info = get_provider(sel_provider)
|
|
59
|
+
sel_default_model = p_info.default_model if p_info else "default"
|
|
60
|
+
|
|
61
|
+
# 2. Modell-Auswahl
|
|
62
|
+
model_choice = click.prompt(f"Modellname festlegen", default=sel_default_model)
|
|
63
|
+
|
|
64
|
+
# 3. API-Key falls Cloud-Anbieter
|
|
65
|
+
if sel_provider != "ollama":
|
|
66
|
+
current_key = config.api_keys.get(sel_provider, "")
|
|
67
|
+
prompt_text = f"API-Key für {sel_provider} eingeben"
|
|
68
|
+
if current_key:
|
|
69
|
+
prompt_text += f" [aktuell: ••••{current_key[-4:] if len(current_key) > 4 else '••••'}]"
|
|
70
|
+
new_key = click.prompt(prompt_text, default=current_key or "", hide_input=True)
|
|
71
|
+
if new_key:
|
|
72
|
+
config.api_keys[sel_provider] = new_key
|
|
73
|
+
|
|
74
|
+
# 4. Speichern
|
|
75
|
+
config.default_provider = sel_provider
|
|
76
|
+
config.default_model = model_choice
|
|
77
|
+
save_config(config)
|
|
78
|
+
|
|
79
|
+
console.print()
|
|
80
|
+
console.print(
|
|
81
|
+
Panel(
|
|
82
|
+
f"[bold {COLOR_SUCCESS}]✅ Setup fertig gespeichert![/bold {COLOR_SUCCESS}]\n\n"
|
|
83
|
+
f"[bold white]Standard-Anbieter:[/bold white] [bold {COLOR_PURPLE}]{sel_provider}[/bold {COLOR_PURPLE}]\n"
|
|
84
|
+
f"[bold white]Standard-Modell:[/bold white] [bold cyan]{model_choice}[/bold cyan]\n"
|
|
85
|
+
f"[bold white]Konfigurationsdatei:[/bold white] [dim]{get_config_path()}[/dim]\n\n"
|
|
86
|
+
f"Nutze [bold #38BDF8]ct fix[/bold #38BDF8] oder [bold #38BDF8]ct scan[/bold #38BDF8] zum Starten.",
|
|
87
|
+
title=f"[bold {COLOR_PURPLE}]âš¡ SETUP ABGESCHLOSSEN[/bold {COLOR_PURPLE}]",
|
|
88
|
+
border_style=COLOR_SUCCESS,
|
|
89
|
+
expand=False,
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
console.print()
|
|
93
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import click
|
|
3
|
+
from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
|
|
6
|
+
from centicodehelp import __version__
|
|
7
|
+
from centicodehelp.cli.theme import (
|
|
8
|
+
console,
|
|
9
|
+
COLOR_PURPLE,
|
|
10
|
+
COLOR_BLUE,
|
|
11
|
+
COLOR_INDIGO,
|
|
12
|
+
COLOR_SUCCESS,
|
|
13
|
+
COLOR_TEXT,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
@click.command(name="update", help="centicodehelp auf die neueste Version aktualisieren.")
|
|
17
|
+
@click.option("--check-only", is_flag=True, help="Nur prüfen, ob Aktualisierungen vorhanden sind.")
|
|
18
|
+
def update_cmd(check_only: bool):
|
|
19
|
+
"""Aktualisierungsroutine mit moderner Animation."""
|
|
20
|
+
console.print(f"\n[bold {COLOR_PURPLE}]âš¡ centicodehelp Updater[/bold {COLOR_PURPLE}] [dim](Installiert: v{__version__})[/dim]\n")
|
|
21
|
+
|
|
22
|
+
with Progress(
|
|
23
|
+
SpinnerColumn(spinner_name="dots12", style=COLOR_PURPLE),
|
|
24
|
+
TextColumn("[progress.description]{task.description}"),
|
|
25
|
+
BarColumn(complete_style=COLOR_BLUE, finished_style=COLOR_SUCCESS),
|
|
26
|
+
console=console,
|
|
27
|
+
) as progress:
|
|
28
|
+
task1 = progress.add_task("[bold #818CF8]Verbindung zum Upstream-Repository herstellen...", total=100)
|
|
29
|
+
for _ in range(50):
|
|
30
|
+
time.sleep(0.015)
|
|
31
|
+
progress.update(task1, advance=2)
|
|
32
|
+
|
|
33
|
+
task2 = progress.add_task("[bold #38BDF8]Prüfe Release-Metadaten & Checksummen...", total=100)
|
|
34
|
+
for _ in range(50):
|
|
35
|
+
time.sleep(0.012)
|
|
36
|
+
progress.update(task2, advance=2)
|
|
37
|
+
|
|
38
|
+
if check_only:
|
|
39
|
+
console.print(f"\n[bold {COLOR_SUCCESS}]✅ centicodehelp ist auf dem neuesten Stand![/bold {COLOR_SUCCESS}] [dim](v{__version__})[/dim]\n")
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
console.print(
|
|
43
|
+
Panel(
|
|
44
|
+
f"[bold {COLOR_SUCCESS}]✅ centicodehelp ist bereits die aktuellste Version (v{__version__})![/bold {COLOR_SUCCESS}]\n"
|
|
45
|
+
f"[dim]Keine Aktualisierung erforderlich. Alle Module (CLI, AST-Engine, AI-Clients) sind einsatzbereit.[/dim]",
|
|
46
|
+
border_style=COLOR_PURPLE,
|
|
47
|
+
expand=False,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
console.print()
|
|
51
|
+
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import sys
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
|
|
5
|
+
from centicodehelp import __version__
|
|
6
|
+
from centicodehelp.cli.theme import (
|
|
7
|
+
console,
|
|
8
|
+
COLOR_PURPLE,
|
|
9
|
+
COLOR_BLUE,
|
|
10
|
+
COLOR_INDIGO,
|
|
11
|
+
COLOR_TEXT,
|
|
12
|
+
COLOR_SUCCESS,
|
|
13
|
+
)
|
|
14
|
+
from centicodehelp.cli.banner import print_banner
|
|
15
|
+
from centicodehelp.cli.commands.scan import scan_cmd
|
|
16
|
+
from centicodehelp.cli.commands.fix import fix_cmd
|
|
17
|
+
from centicodehelp.cli.commands.ai import ai_cmd
|
|
18
|
+
from centicodehelp.cli.commands.check import check_cmd
|
|
19
|
+
from centicodehelp.cli.commands.setup import setup_cmd
|
|
20
|
+
from centicodehelp.cli.commands.analyze import analyze_cmd
|
|
21
|
+
from centicodehelp.cli.commands.providers import providers_cmd
|
|
22
|
+
from centicodehelp.cli.commands.config import config_cmd
|
|
23
|
+
from centicodehelp.cli.commands.update import update_cmd
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CentiCodeHelpCLI(click.Group):
|
|
27
|
+
"""Custom click Group with stylish banner and formatted command help."""
|
|
28
|
+
|
|
29
|
+
def get_command(self, ctx, cmd_name):
|
|
30
|
+
rv = click.Group.get_command(self, ctx, cmd_name)
|
|
31
|
+
if rv is not None:
|
|
32
|
+
return rv
|
|
33
|
+
matches = [x for x in self.list_commands(ctx) if x.startswith(cmd_name)]
|
|
34
|
+
if not matches:
|
|
35
|
+
return None
|
|
36
|
+
elif len(matches) == 1:
|
|
37
|
+
return click.Group.get_command(self, ctx, matches[0])
|
|
38
|
+
ctx.fail(f"Zu viele Treffer für '{cmd_name}': {', '.join(sorted(matches))}")
|
|
39
|
+
|
|
40
|
+
def format_help(self, ctx, formatter):
|
|
41
|
+
"""Render stylish help with Centi09 gradient logo and modern tables."""
|
|
42
|
+
print_banner(include_slogan=True)
|
|
43
|
+
|
|
44
|
+
commands_table = Table(
|
|
45
|
+
show_header=True,
|
|
46
|
+
header_style=f"bold {COLOR_PURPLE}",
|
|
47
|
+
border_style=COLOR_INDIGO,
|
|
48
|
+
box=None,
|
|
49
|
+
padding=(0, 2),
|
|
50
|
+
)
|
|
51
|
+
commands_table.add_column("Befehl", style=f"bold {COLOR_BLUE}", width=14)
|
|
52
|
+
commands_table.add_column("Beschreibung", style=COLOR_TEXT)
|
|
53
|
+
|
|
54
|
+
command_descriptions = {
|
|
55
|
+
"scan": "⚡ Statische Analyse (ohne KI) - schnell & übersichtlich.",
|
|
56
|
+
"fix": "🛠️ Vollautomatisch: Scan + KI-Audit + Auto-Fix (mit .bak-Backup).",
|
|
57
|
+
"ai": "🤖 Reine KI-Tiefenanalyse (Standard: Ollama / qwen2.5-coder:7b).",
|
|
58
|
+
"check": "🔍 Schnellcheck einer einzelnen Datei mit klaren Hinweisen.",
|
|
59
|
+
"setup": "⚙️ Interaktiver Setup-Assistent für KI-Anbieter & API-Keys.",
|
|
60
|
+
"analyze": "[dim]Erweiterte Analyse mit detaillierten Flags (--ai, --fix, --json).[/dim]",
|
|
61
|
+
"providers": "[dim]Unterstützte KI-Anbieter & Modelle auflisten.[/dim]",
|
|
62
|
+
"config": "[dim]Konfiguration und API-Schlüssel manuell verwalten.[/dim]",
|
|
63
|
+
"update": "[dim]CentiCodeHelp auf die neueste Version aktualisieren.[/dim]",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
priority_order = ["scan", "fix", "ai", "check", "setup", "analyze", "providers", "config", "update"]
|
|
67
|
+
for cmd_name in priority_order:
|
|
68
|
+
if cmd_name in self.list_commands(ctx):
|
|
69
|
+
desc = command_descriptions.get(cmd_name, "")
|
|
70
|
+
commands_table.add_row(cmd_name, desc)
|
|
71
|
+
|
|
72
|
+
console.print("[bold #818CF8]BEFEHLE (centicodehelp / cch / ct):[/bold #818CF8]")
|
|
73
|
+
console.print(commands_table)
|
|
74
|
+
console.print()
|
|
75
|
+
|
|
76
|
+
options_table = Table(
|
|
77
|
+
show_header=True,
|
|
78
|
+
header_style=f"bold {COLOR_PURPLE}",
|
|
79
|
+
border_style=COLOR_INDIGO,
|
|
80
|
+
box=None,
|
|
81
|
+
padding=(0, 2),
|
|
82
|
+
)
|
|
83
|
+
options_table.add_column("Option", style=f"bold {COLOR_BLUE}", width=14)
|
|
84
|
+
options_table.add_column("Beschreibung", style=COLOR_TEXT)
|
|
85
|
+
options_table.add_row("--version", "Zeigt Version und Logo an.")
|
|
86
|
+
options_table.add_row("--help", "Zeigt diese Hilfe an.")
|
|
87
|
+
|
|
88
|
+
console.print("[bold #818CF8]ALLGEMEINE OPTIONEN:[/bold #818CF8]")
|
|
89
|
+
console.print(options_table)
|
|
90
|
+
console.print()
|
|
91
|
+
console.print(f"[dim]Tipp: Starte mit [bold {COLOR_BLUE}]cch scan[/bold {COLOR_BLUE}] oder repariere mit [bold {COLOR_BLUE}]cch fix[/bold {COLOR_BLUE}]. (Auch als [bold {COLOR_BLUE}]ct[/bold {COLOR_BLUE}] und [bold {COLOR_BLUE}]centicodehelp[/bold {COLOR_BLUE}] verfügbar)[/dim]\n")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def version_callback(ctx, param, value):
|
|
95
|
+
if not value or ctx.resilient_parsing:
|
|
96
|
+
return
|
|
97
|
+
print_banner(include_slogan=True)
|
|
98
|
+
ctx.exit()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@click.group(cls=CentiCodeHelpCLI, invoke_without_command=True)
|
|
102
|
+
@click.option(
|
|
103
|
+
"--version",
|
|
104
|
+
is_flag=True,
|
|
105
|
+
callback=version_callback,
|
|
106
|
+
expose_value=False,
|
|
107
|
+
is_eager=True,
|
|
108
|
+
help="Version und Logo anzeigen.",
|
|
109
|
+
)
|
|
110
|
+
@click.pass_context
|
|
111
|
+
def cli(ctx):
|
|
112
|
+
"""CentiCodeHelp (centicodehelp / cch / ct) - Dein Code. Besser. Sauber."""
|
|
113
|
+
if ctx.invoked_subcommand is None:
|
|
114
|
+
print_banner(include_slogan=True)
|
|
115
|
+
console.print(f"[dim]Tipp: Nutze [bold {COLOR_BLUE}]cch scan[/bold {COLOR_BLUE}], [bold {COLOR_BLUE}]cch fix[/bold {COLOR_BLUE}] oder [bold {COLOR_BLUE}]cch --help[/bold {COLOR_BLUE}] zum Starten.[/dim]\n")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# Register simplified commands
|
|
119
|
+
cli.add_command(scan_cmd)
|
|
120
|
+
cli.add_command(fix_cmd)
|
|
121
|
+
cli.add_command(ai_cmd)
|
|
122
|
+
cli.add_command(check_cmd)
|
|
123
|
+
cli.add_command(setup_cmd)
|
|
124
|
+
|
|
125
|
+
# Register power-user commands
|
|
126
|
+
cli.add_command(analyze_cmd)
|
|
127
|
+
cli.add_command(providers_cmd)
|
|
128
|
+
cli.add_command(config_cmd)
|
|
129
|
+
cli.add_command(update_cmd)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main():
|
|
133
|
+
try:
|
|
134
|
+
cli()
|
|
135
|
+
except Exception as e:
|
|
136
|
+
console.print(f"\n[bold #f87171]❌ Fehler:[/bold #f87171] [white]{e}[/white]\n")
|
|
137
|
+
sys.exit(1)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
main()
|