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.
@@ -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()