#!/usr/bin/env python3
"""
toolbox — interaktiver Terminal-Tool-Launcher mit Kategorie-Navigation
Läuft auf macOS (iTerm2) und Linux (Xubuntu/xfce4-terminal, gnome-terminal, …)
"""

import subprocess
import shutil
import sys
import os
import platform
import shlex
import json
import base64
from dataclasses import dataclass

IS_MAC   = platform.system() == "Darwin"
IS_LINUX = platform.system() == "Linux"

CURRENT_DIR       = os.getcwd()
CUSTOM_TOOLS_FILE = os.path.expanduser("~/.config/toolbox/custom_tools.json")


@dataclass
class Tool:
    category:    str
    name:        str
    description: str        # Kurztext für die Listenansicht
    details:     str        # Ausführliche Beschreibung + Beispiele für den Preview
    command:     str
    binary:      str
    mac:         bool = True
    linux:       bool = True
    brew:        str  = ""  # Paketname für brew install (falls ≠ binary)
    apt:         str  = ""  # Paketname für apt install  (falls ≠ binary)


TOOLS = [
    # ── Git ──────────────────────────────────────────────────────────────────
    Tool("Git", "lazygit",
         "Git TUI: stagen, committen, pushen, rebasen",
         "Vollständige Git-Oberfläche im Terminal: Dateien interaktiv stagen, "
         "committen, pushen, Branches wechseln, interaktives Rebase, Stash verwalten, "
         "Merge-Konflikte lösen — alles ohne git-Befehle tippen.",
         "lazygit", "lazygit"),

    Tool("Git", "tig",
         "Git-Log TUI: Commits durchsuchen und Blame anzeigen",
         "Interaktiver Git-Log-Browser: Commit-History navigieren, vollständige Diffs "
         "anzeigen, Branches vergleichen, Blame-Ansicht, Stash und Refs erkunden.\n"
         "Bsp: tig  /  tig blame src/main.py  /  tig HEAD..origin/main",
         "tig", "tig"),

    Tool("Git", "gh",
         "GitHub CLI: PRs, Issues und Repos im Terminal",
         "Offizielle GitHub CLI: Pull Requests erstellen und reviewen, Issues verwalten, "
         "Repos klonen, Actions-Logs abrufen, Releases erstellen.\n"
         "Bsp: gh pr create  /  gh pr list  /  gh issue list  /  gh repo clone owner/repo  "
         "/  gh run list",
         "gh pr list", "gh"),

    Tool("Git", "gitcheck",
         "Status aller lokalen Repos auf einen Blick prüfen",
         "Durchläuft alle Git-Repos im Home-Verzeichnis und zeigt an, welche "
         "uncommittete Änderungen, unpushed Commits oder fetch-fähige Updates haben.",
         "~/git-check-all.sh", "git"),

    Tool("Git", "gitsync",
         "Alle Repos mit Gitea synchronisieren",
         "Führt für alle konfigurierten Git-Repos ein fetch, pull und ggf. push "
         "gegen den Gitea-Server durch — hält alle lokalen Klone aktuell.",
         "~/git-projekte/dotfiles-rene/bin/git-sync-all.sh", "git"),

    Tool("Git", "git log",
         "Commit-History mit vollem Diff (delta, side-by-side)",
         "Zeigt die vollständige Commit-History inklusive Diffs — nutzt delta für "
         "Syntax-Highlighting und optionalen Side-by-Side-Vergleich.\n"
         "Bsp: git log -p  /  git log --oneline  /  git log -p --since=1.week  "
         "/  git log --author='Name'",
         "git log -p", "git"),

    Tool("Git", "git diff",
         "Änderungen zum letzten Commit anzeigen (delta)",
         "Zeigt alle nicht gestagten Änderungen mit Syntax-Highlighting via delta.\n"
         "Bsp: git diff  /  git diff HEAD~1  /  git diff main..feature  "
         "/  git diff -- src/foo.py",
         "git diff", "git"),

    Tool("Git", "onefetch",
         "Repo-Übersicht: Sprachen, Commits, Autoren",
         "Zeigt eine kompakte Zusammenfassung des aktuellen Git-Repos: "
         "Top-Programmiersprachen nach Anteil, Anzahl Commits, aktivste Autoren, "
         "Lizenz, letzter Commit, Repo-Größe — wie neofetch für Git-Repos.",
         "onefetch", "onefetch"),

    # ── Dateien ──────────────────────────────────────────────────────────────
    Tool("Dateien", "yazi",
         "TUI-Dateimanager mit Bildvorschau und Plugins",
         "Moderner Dateimanager mit Vorschau für Bilder, Videos, PDFs und Code, "
         "Bulk-Rename, Plugin-System, Async-Operationen und vi-Keybindings.\n"
         "Steuerung: hjkl navigieren  /  Enter öffnen  /  y kopieren  /  d ausschneiden  "
         "/  p einfügen  /  r umbenennen  /  q beenden",
         "yazi", "yazi"),

    Tool("Dateien", "lf",
         "Schneller Vim-artiger Dateimanager",
         "Schlanker, schneller Dateimanager mit vi-Keybindings und Shell-Integration. "
         "Vollständig konfigurierbar über ~/.config/lf/lfrc — unterstützt Vorschauen, "
         "benutzerdefinierte Befehle und Lesezeichen.\n"
         "Steuerung: hjkl navigieren  /  Enter öffnen  /  q beenden",
         "lf", "lf"),

    Tool("Dateien", "broot",
         "Navigierbarer Verzeichnisbaum mit Inline-Fuzzy-Suche",
         "Zeigt den Verzeichnisbaum und sucht gleichzeitig während man tippt. "
         "Öffnet Dateien im Editor, unterstützt Kopieren/Verschieben und zeigt "
         "Größen an. Alias 'br' für komfortablen Start.\n"
         "Bsp: broot  /  broot /etc  /  bsp: 'br' dann '.py' tippen zum Filtern",
         "broot", "broot"),

    Tool("Dateien", "fzf",
         "Universeller interaktiver Fuzzy-Finder",
         "Filtert beliebige Listen interaktiv mit Fuzzy-Matching. Integration in "
         "Shell-History (Ctrl-R), Dateisuche und als Filter in Pipelines.\n"
         "Bsp: fzf  /  vim $(fzf)  /  history | fzf  /  git checkout $(git branch | fzf)",
         "fzf", "fzf"),

    Tool("Dateien", "fd",
         "Schnelles find mit intuitiver Syntax",
         "Findet Dateien schneller als find, ignoriert standardmäßig .gitignore-Einträge "
         "und versteckte Verzeichnisse, unterstützt reguläre Ausdrücke und Typenfilter.\n"
         "Bsp: fd .py  /  fd -e rs src/  /  fd -H .env  /  fd --type d  /  fd -x cp {} backup/",
         "fd", "fd", apt="fd-find"),

    Tool("Dateien", "rg",
         "Blitzschnelles grep mit PCRE-Regex und gitignore-Support",
         "Durchsucht Verzeichnisbäume nach Muster — deutlich schneller als grep/ag, "
         "respektiert .gitignore, unterstützt PCRE2-Regex und zeigt Kontext.\n"
         "Bsp: rg TODO  /  rg -t py 'def main'  /  rg -l pattern  /  rg -C 3 'Error'  "
         "/  rg --hidden '.env'",
         "rg", "rg", brew="ripgrep", apt="ripgrep"),

    Tool("Dateien", "ncdu",
         "Interaktive Festplattennutzung verwalten",
         "Zeigt Verzeichnisgrößen interaktiv und lässt Dateien/Ordner direkt löschen. "
         "Ideal zum Aufspüren von Platzverschwendern.\n"
         "Steuerung: Pfeiltasten navigieren  /  d löschen  /  i Info  /  q beenden",
         "ncdu ~", "ncdu"),

    Tool("Dateien", "dust",
         "Verzeichnisgrößen als farbiges Balkendiagramm",
         "Zeigt Verzeichnisgrößen als Balken, automatisch nach Größe sortiert — "
         "schneller und übersichtlicher als 'du -sh *'.\n"
         "Bsp: dust  /  dust ~  /  dust -d 2 /var  /  dust -n 20",
         "dust ~", "dust", apt="du-dust"),

    Tool("Dateien", "duf",
         "Freier Speicherplatz aller Laufwerke auf einen Blick",
         "Zeigt alle Laufwerke, Partitionen und Netzwerk-Mounts mit belegtem und "
         "freiem Speicherplatz in einer übersichtlichen Tabelle.\n"
         "Bsp: duf  /  duf /home /var  /  duf --only local",
         "duf", "duf"),

    Tool("Dateien", "zoxide",
         "Intelligentes cd — merkt sich häufig besuchte Verzeichnisse",
         "Lernt welche Verzeichnisse man häufig besucht und springt mit 'z <teilname>' "
         "direkt dorthin — ohne den vollen Pfad zu tippen.\n"
         "Bsp: z proj  (springt zu ~/git-projekte/…)  /  z dot  (springt zu dotfiles)  "
         "/  zi  (interaktive Auswahl mit fzf)",
         "zoxide query --list", "zoxide"),

    # ── Anzeige ──────────────────────────────────────────────────────────────
    Tool("Anzeige", "bat",
         "cat mit Syntax-Highlighting und Git-Markierungen",
         "cat-Ersatz mit automatischem Syntax-Highlighting für hunderte Sprachen, "
         "Zeilennummern, Git-Änderungsmarkierungen am Rand und automatischem Paging.\n"
         "Bsp: bat README.md  /  bat src/main.py  /  bat --language json data  "
         "/  cat large.log | bat",
         "bat", "bat", apt="batcat"),

    Tool("Anzeige", "eza -la",
         "Modernes ls mit Farben, Icons und Git-Status",
         "Ersatz für ls mit Farben, optionalen Icons, Git-Status pro Datei, "
         "Berechtigungen, Größen und Zeitstempeln — übersichtlicher als ls -la.\n"
         "Bsp: eza -la  /  eza -la --git  /  eza -la --icons  /  eza --sort=size",
         "eza -la", "eza"),

    Tool("Anzeige", "eza -T",
         "Farbiger Verzeichnisbaum mit Git-Status",
         "Zeigt einen Verzeichnisbaum in Farbe mit Git-Status, Icons und "
         "konfigurierbarer Tiefe — besser als tree.\n"
         "Bsp: eza -T  /  eza -T --level=2  /  eza -T --git-ignore  /  eza -T src/",
         "eza -T", "eza"),

    Tool("Anzeige", "delta",
         "Schöne farbige Git-Diffs mit Syntax-Highlighting",
         "Verschönert Git-Diffs mit Syntax-Highlighting, Zeilennummern, "
         "Side-by-Side-Vergleich und konfigurierbaren Themes — wird automatisch von "
         "git diff/log/show verwendet wenn konfiguriert.\n"
         "Bsp: git diff HEAD~1 | delta  /  git show abc1234 | delta",
         "git diff HEAD~1 | delta", "delta"),

    Tool("Anzeige", "glow",
         "Markdown schön gerendert im Terminal",
         "Rendert Markdown-Dateien mit Überschriften, Tabellen, Code-Blöcken und "
         "Hervorhebungen direkt im Terminal — ideal für READMEs und Dokumentation.\n"
         "Bsp: glow README.md  /  glow -p CHANGELOG.md  /  glow docs/  "
         "/  curl -s raw-url | glow -",
         "glow", "glow"),

    Tool("Anzeige", "hexyl",
         "Hex-Viewer mit farbiger ASCII-Darstellung",
         "Zeigt Binärdateien als Hex-Dump mit farbiger Markierung verschiedener "
         "Byte-Typen (NULL, ASCII, nicht-druckbar) und paralleler ASCII-Darstellung.\n"
         "Bsp: hexyl /bin/ls  /  hexyl -n 256 file.bin  /  hexyl --skip 512 img.raw",
         "hexyl", "hexyl"),

    Tool("Anzeige", "tokei",
         "Codezeilen-Statistik nach Programmiersprache",
         "Zählt Codezeilen, Kommentare und Leerzeilen aufgeteilt nach "
         "Programmiersprache — schnell auch bei großen Repos.\n"
         "Bsp: tokei  /  tokei src/  /  tokei --sort lines  /  tokei -e vendor/",
         "tokei", "tokei"),

    # ── System ───────────────────────────────────────────────────────────────
    Tool("System", "btop",
         "Systemmonitor TUI: CPU, RAM, Netz, Prozesse",
         "Vollständiger Ressourcenmonitor: CPU-Auslastung pro Kern, RAM- und Swap-Nutzung, "
         "Netzwerkdurchsatz, Disk-I/O und Prozessliste mit Baumansicht und Filter.\n"
         "Steuerung: Maus oder Tastatur  /  f filtern  /  k beenden  /  q quit",
         "btop", "btop"),

    Tool("System", "htop",
         "Klassischer interaktiver Prozess-Monitor",
         "Interaktiver Prozess-Monitor mit Baumansicht, Sortierung nach CPU/RAM, "
         "Filter-Funktion und der Möglichkeit Prozesse direkt zu beenden oder zu renicen.\n"
         "Steuerung: F6 sortieren  /  F4 filtern  /  F9 beenden  /  t Baumansicht",
         "htop", "htop"),

    Tool("System", "procs",
         "Modernes ps mit Farben und Baumansicht",
         "Prozessliste mit Farben, Baumansicht, Suchfilter nach Name/PID/Port und "
         "übersichtlicher Darstellung von CPU, RAM und Laufzeit.\n"
         "Bsp: procs  /  procs nginx  /  procs --tree  /  procs 8080  (nach Port)",
         "procs", "procs"),

    Tool("System", "fastfetch",
         "Systeminfo-Übersicht mit ASCII-Logo",
         "Zeigt Systeminformationen schnell und übersichtlich: OS, Kernel-Version, "
         "CPU, RAM, GPU, Shell, Terminal, Uptime, IP-Adresse — mit ASCII-Logo des OS.\n"
         "Bsp: fastfetch  /  fastfetch --logo none  /  fastfetch -c all",
         "fastfetch", "fastfetch"),

    Tool("System", "hyperfine",
         "Präzises Benchmarking für Shell-Befehle",
         "Misst die Ausführungszeit von Befehlen mit statistischer Auswertung "
         "(Mittelwert, Standardabweichung), Warmup-Runs und direktem Vergleich.\n"
         "Bsp: hyperfine 'ls -la' 'eza -la'  /  hyperfine --warmup 5 'grep -r TODO .'  "
         "/  hyperfine --export-markdown bench.md 'cmd1' 'cmd2'",
         "hyperfine --help", "hyperfine"),

    Tool("System", "lsof ports",
         "Offene TCP-Ports und zugehörige Prozesse anzeigen",
         "Zeigt alle Prozesse, die aktuell auf TCP-Ports lauschen — nützlich zum "
         "Debuggen von Port-Konflikten und zum Überprüfen aktiver Services.\n"
         "Bsp: lsof -nP -iTCP -sTCP:LISTEN  /  lsof -i :8080  /  lsof -p 1234",
         "lsof -nP -iTCP -sTCP:LISTEN", "lsof"),

    Tool("System", "temps",
         "CPU/GPU-Temperatur und Akku-Status (Mac)",
         "Liest CPU- und GPU-Temperatur, Leistungsaufnahme und Akku-Status via "
         "powermetrics aus — nur auf macOS verfügbar, benötigt sudo.",
         "sudo powermetrics -s cpu_power,gpu_power,thermal,battery -i 1000 -n 1",
         "sudo", linux=False),

    # ── Netzwerk ─────────────────────────────────────────────────────────────
    Tool("Netzwerk", "nmap",
         "Netzwerk-Scanner: Hosts und Ports entdecken",
         "Scannt Netzwerke nach aktiven Hosts, offenen Ports und laufenden Services. "
         "Unverzichtbar für Netzwerk-Diagnose und Sicherheitsüberprüfungen.\n"
         "Bsp: nmap -sn 192.168.1.0/24  (Ping-Scan)  /  nmap -p 22,80,443 host  "
         "/  nmap -sV host  (Service-Versionen)  /  nmap -A host  (aggressiv)",
         "nmap -sn 10.47.11.0/24", "nmap"),

    Tool("Netzwerk", "mtr",
         "Live-Traceroute mit Ping-Statistik pro Hop",
         "Kombiniert traceroute und ping: zeigt jeden Hop auf dem Weg zum Ziel "
         "mit Latenz, Jitter und Paketverlust in Echtzeit — ideal zur Netzwerkdiagnose.\n"
         "Bsp: mtr 8.8.8.8  /  mtr --report google.com  /  mtr -n 192.168.1.1",
         "mtr 8.8.8.8", "mtr"),

    Tool("Netzwerk", "gping",
         "Grafischer Ping mit Live-Kurve für mehrere Hosts",
         "Pingt einen oder mehrere Hosts gleichzeitig und zeigt die Latenz als "
         "Live-Kurve im Terminal — sofort sichtbar wenn ein Host langsam oder ausgefallen ist.\n"
         "Bsp: gping 1.1.1.1  /  gping 1.1.1.1 8.8.8.8 google.com",
         "gping 1.1.1.1", "gping"),

    Tool("Netzwerk", "bandwhich",
         "Netzwerkauslastung nach Prozess in Echtzeit",
         "Zeigt welche Prozesse und Verbindungen aktuell wie viel Netzwerkbandbreite "
         "verbrauchen — aufgeteilt nach Remote-Host und lokalem Prozess.\n"
         "Benötigt sudo für vollständige Prozessinfos.",
         "sudo bandwhich", "bandwhich"),

    Tool("Netzwerk", "xh",
         "Moderner HTTP-Client mit schöner Ausgabe",
         "HTTP-Requests senden wie HTTPie aber schneller — intuitiver als curl, "
         "farbige JSON-Ausgabe, Session-Support, automatisches Setzen von Content-Type.\n"
         "Bsp: xh get httpbin.org/ip  /  xh post api.example.com name=Alice age:=30  "
         "/  xh -d get example.com/file.zip  /  xh --auth user:pass get api.example.com",
         "xh get httpbin.org/ip", "xh"),

    Tool("Netzwerk", "dog",
         "Modernes dig mit farbiger strukturierter Ausgabe",
         "DNS-Lookups mit farbiger Ausgabe, strukturierten Ergebnissen und Unterstützung "
         "für alle Record-Typen — intuitiver als dig.\n"
         "Bsp: dog example.com  /  dog example.com A MX  /  dog @1.1.1.1 example.com  "
         "/  dog --json example.com | jq",
         "dog example.com", "dog", linux=False),

    # ── Text & Daten ─────────────────────────────────────────────────────────
    Tool("Text/Daten", "jq",
         "JSON-Prozessor für Kommandozeile",
         "Filtert, transformiert und formatiert JSON — unverzichtbar beim Arbeiten "
         "mit APIs und JSON-Dateien. Unterstützt komplexe Abfragen und Transformationen.\n"
         "Bsp: curl api.x.com | jq '.name'  /  jq '.[] | select(.active)' file.json  "
         "/  jq -r '.users[].email' data.json  /  jq '{name, age}' input.json",
         "jq", "jq"),

    Tool("Text/Daten", "yq",
         "YAML/JSON/XML-Prozessor (jq-Syntax für YAML)",
         "Verarbeitet YAML, JSON und XML mit jq-ähnlicher Syntax — ideal für "
         "Kubernetes-Manifeste, Docker Compose und Konfigurationsdateien.\n"
         "Bsp: yq '.version' app.yaml  /  yq -i '.replicas = 3' deploy.yaml  "
         "/  yq -o json config.yaml  /  yq '.services | keys' docker-compose.yml",
         "yq", "yq"),

    Tool("Text/Daten", "fx",
         "Interaktiver JSON-Viewer mit Navigation",
         "Öffnet JSON interaktiv: Felder auf- und zuklappen, navigieren, mit "
         "JavaScript-Ausdrücken filtern und transformieren.\n"
         "Bsp: echo '{\"a\":1}' | fx  /  curl api.x.com | fx  /  fx data.json .users",
         r"""echo '{"name":"Alice","items":[1,2,3]}' | fx""", "fx"),

    Tool("Text/Daten", "miller (mlr)",
         "CSV/TSV/JSON-Verarbeitung für tabellarische Daten",
         "Schweizer Taschenmesser für strukturierte Daten: sortieren, filtern, "
         "gruppieren, aggregieren — unterstützt CSV, TSV, JSON, NIDX.\n"
         "Bsp: mlr --csv head -n 5 data.csv  /  mlr --csv sort-f name data.csv  "
         "/  mlr --csv filter '$age > 30' data.csv  /  mlr --csv stats1 -a mean -f price",
         "mlr --help", "mlr", brew="miller", apt="miller"),

    Tool("Text/Daten", "gron",
         "Macht JSON grep-fähig durch Abflachung",
         "Konvertiert verschachteltes JSON in flache Pfad=Wert-Zeilen — "
         "danach mit grep/rg durchsuchbar, danach mit 'gron --ungron' zurückkonvertierbar.\n"
         "Bsp: gron file.json | grep email  /  curl api.x.com | gron | grep '\\[0\\]'  "
         "/  gron data.json | grep name | gron --ungron",
         r"""echo '{"user":{"name":"Alice","email":"a@b.com"}}' | gron""", "gron"),

    # ── Rechnen ──────────────────────────────────────────────────────────────
    Tool("Rechnen", "qalc",
         "Wissenschaftlicher Rechner mit Einheiten und Währungen",
         "Vollständiger Taschenrechner mit physikalischen Einheiten, Einheitenumrechnung "
         "und Live-Wechselkursen — versteht natürliche Ausdrücke.\n"
         "Bsp: qalc '100 USD to EUR'  /  qalc '5 km/h to mph'  /  qalc 'sin(pi/4)'  "
         "/  qalc '1 lightyear to km'  /  qalc '100 MiB to MB'",
         "qalc", "qalc", brew="qalculate", apt="qalc"),

    Tool("Rechnen", "units",
         "Präzise Einheitenumrechnung für hunderte Einheiten",
         "Konvertiert zwischen physikalischen Einheiten mit hoher Genauigkeit — "
         "kennt SI, imperiale, historische und zusammengesetzte Einheiten.\n"
         "Bsp: units '1kWh' 'MJ'  /  units '1 mile' km  /  units '5 acres' 'm^2'  "
         "/  units '100 km/h' 'miles/hour'",
         "units", "units"),

    Tool("Rechnen", "python3",
         "Python REPL für Berechnungen und Skripte",
         "Interaktiver Python-Interpreter — ideal für schnelle Berechnungen, "
         "String-Manipulation, JSON-Parsing und kleine Skripte.\n"
         "Bsp: python3  dann: import math; math.pi  /  2**32  /  hex(255)  "
         "/  import json; json.loads('{\"a\":1}')",
         "python3", "python3"),

    Tool("Rechnen", "bc",
         "Taschenrechner für große Zahlen und hohe Präzision",
         "Mathematische Skriptsprache für beliebig genaue Berechnungen — "
         "gut für große Zahlen, Binär/Hex und trigonometrische Funktionen.\n"
         "Bsp: echo '2^64' | bc  /  echo 'scale=20; 4*a(1)' | bc -l  (Pi)  "
         "/  echo 'obase=16; 255' | bc  (→ FF)  /  echo 'obase=2; 42' | bc  (→ Binär)",
         "bc -l", "bc"),

    # ── Produktivität ────────────────────────────────────────────────────────
    Tool("Produktiv", "tldr",
         "Vereinfachte Manpages mit Praxis-Beispielen",
         "Community-gepflegte Kurzreferenz für hunderte Befehle — zeigt nur die "
         "wichtigsten Optionen mit echten Anwendungsbeispielen.\n"
         "Bsp: tldr tar  /  tldr git commit  /  tldr find  /  tldr ffmpeg  "
         "/  tldr --update  (Datenbank aktualisieren)",
         "tldr", "tldr", brew="tealdeer"),

    Tool("Produktiv", "navi",
         "Interaktive Spickzettel mit ausfüllbaren Platzhaltern",
         "Sucht in Community-Spickzetteln nach Befehlen und lässt Platzhalter "
         "interaktiv ausfüllen — nie wieder lange Optionen vergessen.\n"
         "Bsp: navi  (Suche öffnen)  /  navi --query docker  /  navi repo add <github-url>",
         "navi", "navi"),

    Tool("Produktiv", "thefuck",
         "Korrigiert den letzten fehlgeschlagenen Befehl",
         "Analysiert Fehlermeldungen und schlägt die korrekte Version des letzten "
         "Befehls vor — Tippfehler, fehlende sudo, falsche Flags werden erkannt.\n"
         "Nutzung: nach einem Fehler einfach 'fuck' (oder 'f') tippen.",
         "thefuck --help", "thefuck"),

    Tool("Produktiv", "just",
         "Moderner Make-Ersatz mit einfacherer Syntax",
         "Führt Aufgaben aus einer 'justfile' aus — einfacher und mächtiger als Makefile, "
         "unterstützt Variablen, Argumente, Abhängigkeiten und Shell-Auswahl.\n"
         "Bsp: just build  /  just test  /  just --list  /  just deploy staging",
         "just --help", "just"),

    Tool("Produktiv", "direnv",
         "Umgebungsvariablen automatisch pro Verzeichnis laden",
         "Lädt automatisch Umgebungsvariablen aus einer .envrc Datei wenn man "
         "in ein Verzeichnis wechselt — ideal für Projekt-spezifische API-Keys, "
         "PATH-Erweiterungen und Konfigurationen.\n"
         "Bsp: echo 'export API_KEY=xxx' > .envrc  /  direnv allow  /  direnv status",
         "direnv status", "direnv"),

    # ── Dokumente ────────────────────────────────────────────────────────────
    Tool("Dokumente", "pandoc",
         "Universeller Dokument-Konverter für über 40 Formate",
         "Konvertiert zwischen Markdown, PDF, DOCX, HTML, EPUB, LaTeX und vielen "
         "weiteren Formaten — unverzichtbar für technische Dokumentation.\n"
         "Bsp: pandoc README.md -o out.pdf  /  pandoc in.docx -t markdown  "
         "/  pandoc slides.md -t beamer -o slides.pdf  /  pandoc -f html -t markdown url",
         "pandoc --help", "pandoc"),

    Tool("Dokumente", "pdfinfo",
         "PDF-Metadaten auf einen Blick anzeigen",
         "Zeigt Metadaten einer PDF-Datei: Seitenanzahl, Seitenformat, "
         "Autor, Titel, Erstellungsdatum, PDF-Version und Verschlüsselungsstatus.\n"
         "Bsp: pdfinfo dokument.pdf  /  pdfinfo -f 1 -l 5 dokument.pdf",
         "pdfinfo", "pdfinfo", brew="poppler", apt="poppler-utils"),

    Tool("Dokumente", "pdftotext",
         "Text aus PDF-Dateien extrahieren",
         "Extrahiert den Textinhalt aus PDFs — behält das Seitenlayout bei, "
         "ideal für schnelle Textsuche oder Weiterverarbeitung.\n"
         "Bsp: pdftotext doc.pdf -  (→ stdout)  /  pdftotext -layout doc.pdf  "
         "/  pdftotext doc.pdf - | grep Suchwort  /  pdftotext -f 2 -l 5 doc.pdf",
         "pdftotext", "pdftotext", brew="poppler", apt="poppler-utils"),

    Tool("Dokumente", "qpdf",
         "PDF zusammenführen, aufteilen und verschlüsseln",
         "Manipuliert PDFs ohne Qualitätsverlust: Seiten zusammenführen, einzelne "
         "Seiten extrahieren, verschlüsseln, entschlüsseln, linearisieren.\n"
         "Bsp: qpdf --empty --pages a.pdf b.pdf -- out.pdf  (zusammenführen)  "
         "/  qpdf --pages doc.pdf 1-3 -- out.pdf  /  qpdf --decrypt --password=pw in.pdf out.pdf",
         "qpdf --help", "qpdf"),

    Tool("Dokumente", "gs",
         "Ghostscript: PDF komprimieren und reparieren",
         "Verarbeitet und konvertiert PDFs und PostScript: Dateien komprimieren, "
         "reparieren, Seiten extrahieren, in Bilder umwandeln.\n"
         "Komprimieren: gs -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 "
         "-dPDFSETTINGS=/ebook -sOutputFile=out.pdf in.pdf\n"
         "/ebook ≈ 150dpi  /  /printer ≈ 300dpi  /  /prepress = maximale Qualität",
         "gs --help", "gs", brew="ghostscript", apt="ghostscript"),

    # ── Bilder ───────────────────────────────────────────────────────────────
    Tool("Bilder", "heic2jpg",
         "Alle HEIC im aktuellen Verzeichnis nach JPG konvertieren",
         "Konvertiert alle *.heic/*.HEIC im aktuellen Verzeichnis nach JPG (Qualität 90%) "
         "via heif-convert. Optional mit --resize auf max. 1920px verkleinern.\n"
         "Varianten:\n"
         "  heic2jpg               — nur konvertieren\n"
         "  heic2jpg --resize      — konvertieren + auf 1920px verkleinern\n"
         "  heic2jpg_delete        — konvertieren + HEIC-Originale löschen\n"
         "  heic2jpg_resize_delete — konvertieren + verkleinern + löschen\n"
         "Tipp: h2j ist die modernere Version mit Flags (-r -d -rd)",
         "heic2jpg", "heic2jpg"),

    Tool("Bilder", "h2j",
         "HEIC → JPG mit Flags für Resize und Löschen",
         "Modernere All-in-One-Version der heic2jpg-Familie — konvertiert alle "
         "*.heic/*.HEIC im aktuellen Verzeichnis mit konfigurierbaren Optionen.\n"
         "Flags:\n"
         "  h2j       — nur konvertieren (Qualität 90%)\n"
         "  h2j -r    — + auf max. 1920px verkleinern\n"
         "  h2j -d    — + HEIC-Originale löschen\n"
         "  h2j -rd   — + verkleinern und löschen\n"
         "  h2j -h    — Hilfe anzeigen",
         "h2j -h", "h2j"),

    Tool("Bilder", "convert",
         "ImageMagick: Bilder konvertieren, skalieren und bearbeiten",
         "Mächtiges Bildverarbeitungswerkzeug: Formate konvertieren, skalieren, "
         "zuschneiden, drehen, Helligkeit/Kontrast anpassen, Wasserzeichen hinzufügen.\n"
         "Bsp: convert in.png -resize 50% out.jpg  /  convert -quality 85 in.png out.jpg  "
         "/  convert in.jpg -rotate 90 out.jpg  /  convert *.jpg -delay 50 animation.gif",
         "convert --help", "convert", brew="imagemagick", apt="imagemagick"),

    Tool("Bilder", "exiftool",
         "EXIF-Metadaten lesen, bearbeiten und entfernen",
         "Liest und bearbeitet Metadaten in Foto-, Video- und Audiodateien: "
         "GPS-Koordinaten, Kameramodell, Aufnahmedatum, Copyright, Belichtung.\n"
         "Bsp: exiftool foto.jpg  /  exiftool -GPS* foto.jpg  (nur GPS)  "
         "/  exiftool -all= foto.jpg  (alle Metadaten entfernen)  "
         "/  exiftool '-DateTimeOriginal+=1:0:0' *.jpg  (Datum korrigieren)",
         "exiftool", "exiftool", brew="exiftool", apt="libimage-exiftool-perl"),

    Tool("Bilder", "ffmpeg",
         "Video und Audio konvertieren, schneiden, komprimieren",
         "Das Schweizer Taschenmesser für Medien: praktisch jedes Video- und "
         "Audioformat konvertieren, schneiden, komprimieren, GIFs erstellen.\n"
         "Bsp: ffmpeg -i in.mp4 out.gif  /  ffmpeg -i in.mov -crf 28 out.mp4  "
         "/  ffmpeg -i in.mp4 -ss 00:01:00 -t 30 clip.mp4  (schneiden)  "
         "/  ffmpeg -i in.mp4 -vn -acodec mp3 audio.mp3  (nur Audio)",
         "ffmpeg -version", "ffmpeg"),

    Tool("Bilder", "img2pdf",
         "Bilder verlustfrei zu einer PDF-Datei zusammenführen",
         "Kombiniert mehrere Bilder zu einer PDF-Datei ohne Qualitätsverlust — "
         "im Gegensatz zu convert/ImageMagick wird das Bild nicht re-enkodiert.\n"
         "Bsp: img2pdf *.jpg -o out.pdf  /  img2pdf scan1.png scan2.png -o book.pdf  "
         "/  img2pdf --pagesize A4 *.jpg -o out.pdf",
         "img2pdf --help", "img2pdf"),

    # ── KI ───────────────────────────────────────────────────────────────────
    Tool("KI", "ki-chat",
         "Offline-KI: interaktiver Chat",
         "Startet einen interaktiven Chat mit dem lokalen KI-Modell — "
         "läuft vollständig offline ohne Cloud-Anbindung.",
         "ki interactive", "ki"),

    Tool("KI", "ki-agent",
         "Offline-KI Agent-Modus mit Datei- und Shell-Zugriff",
         "KI im Agent-Modus: kann Dateien lesen/schreiben und Shell-Befehle "
         "ausführen — für komplexere Aufgaben mit Systemzugriff.",
         "ki interactive --agent-mode", "ki"),

    Tool("KI", "ki-commit",
         "Commit-Message mit KI generieren",
         "Analysiert den aktuellen Git-Diff und schlägt eine passende "
         "Commit-Message vor — spart Zeit bei aussagekräftigen Commits.",
         "ki commit", "ki"),

    Tool("KI", "ki-diff",
         "Git-Diff mit KI erklären lassen",
         "Übergibt den aktuellen Git-Diff an die KI und erhält eine "
         "verständliche Erklärung der Änderungen.",
         "ki diff", "ki"),

    # ── Spass ────────────────────────────────────────────────────────────────
    Tool("Spass", "cmatrix",
         "Matrix-Digital-Regen im Terminal",
         "Klassischer grüner Matrix-Regen — Geschwindigkeit und Farbe einstellbar.\n"
         "Steuerung: q beenden  /  a/A Geschwindigkeit  /  b/B Helligkeit",
         "cmatrix -sab", "cmatrix"),

    Tool("Spass", "asciiquarium",
         "Animiertes Aquarium mit Fischen und Haien",
         "Ein buntes ASCII-Aquarium mit schwimmenden Fischen, Haien, Blasen "
         "und gelegentlichen Überraschungsgästen — entspannender Bildschirmschoner.\n"
         "Steuerung: q beenden",
         "asciiquarium", "asciiquarium"),

    Tool("Spass", "pipes.sh",
         "Animierte Rohre als Bildschirmschoner",
         "Zeichnet zufällig wachsende Rohre in verschiedenen Farben und Stilen — "
         "hypnotisierender Terminal-Bildschirmschoner.\n"
         "Steuerung: q beenden  /  Parameter: -t Stil (0-8)  /  -p Anzahl Rohre",
         "pipes.sh -t 0 -p 4", "pipes.sh"),

    Tool("Spass", "cbonsai",
         "Wachsender ASCII-Bonsai-Baum",
         "Lässt einen Bonsai-Baum mit zufälliger Struktur wachsen — jedes Mal "
         "ein anderes Ergebnis. Mit -l läuft die Animation bis q gedrückt wird.\n"
         "Bsp: cbonsai -l  (Live)  /  cbonsai -p  (sofort fertig)  /  cbonsai -S 42  (seed)",
         "cbonsai -l", "cbonsai"),

    Tool("Spass", "nms",
         "Sneakers-Entschlüsselungsanimation",
         "Simuliert den Entschlüsselungseffekt aus dem Film 'Sneakers' (1992): "
         "Text erscheint zuerst als zufällige Zeichen und enthüllt sich dann.\n"
         "Bsp: ls -la | nms  /  cat secret.txt | nms  /  echo 'Hello World' | nms -a",
         "ls -la | nms", "nms"),

    Tool("Spass", "lolcat",
         "Regenbogenfarben für beliebige Terminal-Ausgabe",
         "Gibt jede Ausgabe in animierten Regenbogenfarben aus — als Pipe verwendbar.\n"
         "Bsp: ls -la | lolcat  /  fortune | lolcat  /  echo 'Hello' | lolcat -a  "
         "/  cat /etc/passwd | lolcat",
         "ls -la | lolcat", "lolcat"),

    Tool("Spass", "fortune|cowsay",
         "Weiser Spruch vom Kuh-Orakel",
         "Zufälliger weiser (oder alberner) Spruch, vorgetragen von einer ASCII-Kuh — "
         "Terminal-Klassiker seit den 90ern. Häufig in .bashrc/.zshrc zu finden.\n"
         "Bsp: fortune | cowsay  /  fortune | cowsay -f tux  (Pinguin)  "
         "/  fortune | cowsay | lolcat  (bunt)",
         "fortune | cowsay", "fortune",
         brew="fortune cowsay", apt="fortune-mod cowsay"),
]


LINUX_ALIASES = {
    "fd":  "fdfind",
    "bat": "batcat",
}

_FZF_BASE = [
    "fzf", "--ansi",
    "--height=80%", "--layout=reverse", "--border=rounded",
    "--color=header:italic:cyan,prompt:green,pointer:green,border:blue",
]


def is_available(tool: Tool) -> bool:
    if IS_MAC and not tool.mac:
        return False
    if IS_LINUX and not tool.linux:
        return False
    binary = LINUX_ALIASES.get(tool.binary, tool.binary) if IS_LINUX else tool.binary
    return shutil.which(binary) is not None


def get_install_cmd(tool: Tool) -> str:
    if IS_MAC:
        pkg = tool.brew or tool.binary
        return f"brew install {pkg}"
    else:
        pkg = tool.apt or tool.binary
        return f"sudo apt install {pkg}"


def get_uninstall_cmd(tool: Tool) -> str:
    if IS_MAC:
        pkg = tool.brew or tool.binary
        return f"brew uninstall {pkg}"
    else:
        pkg = tool.apt or tool.binary
        return f"sudo apt remove {pkg}"


def resolve_command(cmd: str) -> str:
    if IS_LINUX:
        for mac_bin, linux_bin in LINUX_ALIASES.items():
            if cmd == mac_bin or cmd.startswith(mac_bin + " "):
                return linux_bin + cmd[len(mac_bin):]
    return cmd


def open_new_tab(command: str) -> None:
    """Öffnet den Befehl in einem neuen Tab im aktuellen Verzeichnis."""
    full_cmd = f"cd {shlex.quote(CURRENT_DIR)} && {command}"

    if IS_MAC:
        import tempfile
        escaped = full_cmd.replace("\\", "\\\\").replace('"', '\\"')
        script = (
            'tell application "iTerm2"\n'
            '    activate\n'
            '    tell front window\n'
            '        create tab with default profile\n'
            '    end tell\n'
            '    tell current session of front window\n'
            f'        write text "{escaped}"\n'
            '    end tell\n'
            'end tell\n'
        )
        with tempfile.NamedTemporaryFile(suffix=".scpt", mode="w", delete=False) as f:
            f.write(script)
            tmp = f.name
        subprocess.run(["osascript", tmp], capture_output=True)
        os.unlink(tmp)
        return

    # Linux: Terminal-Emulator nach Verfügbarkeit
    bash_cmd = f"{command}; exec bash"
    candidates = [
        ("xfce4-terminal", ["xfce4-terminal",
                             f"--working-directory={CURRENT_DIR}",
                             "--tab", "-e", f"bash -c {shlex.quote(bash_cmd)}"]),
        ("gnome-terminal", ["gnome-terminal",
                             f"--working-directory={CURRENT_DIR}",
                             "--tab", "--", "bash", "-c", bash_cmd]),
        ("kitty",          ["kitty", "--directory", CURRENT_DIR,
                             "--new-tab", "bash", "-c", bash_cmd]),
        ("alacritty",      ["alacritty", "--working-directory", CURRENT_DIR,
                             "-e", "bash", "-c", bash_cmd]),
        ("xterm",          ["xterm", "-e",
                             f"bash -c {shlex.quote(full_cmd + '; exec bash')}"]),
    ]
    for binary, args in candidates:
        if shutil.which(binary):
            subprocess.Popen(args)
            return

    os.system(full_cmd)  # Fallback: inline


def load_custom_tools() -> list[Tool]:
    if not os.path.exists(CUSTOM_TOOLS_FILE):
        return []
    try:
        with open(CUSTOM_TOOLS_FILE) as f:
            return [Tool(**entry) for entry in json.load(f)]
    except Exception:
        return []


def save_custom_tool(tool: Tool) -> None:
    os.makedirs(os.path.dirname(CUSTOM_TOOLS_FILE), exist_ok=True)
    existing: list = []
    if os.path.exists(CUSTOM_TOOLS_FILE):
        try:
            with open(CUSTOM_TOOLS_FILE) as f:
                existing = json.load(f)
        except Exception:
            pass
    existing.append({
        "category": tool.category, "name": tool.name,
        "description": tool.description, "details": tool.details,
        "command": tool.command, "binary": tool.binary,
        "mac": tool.mac, "linux": tool.linux,
        "brew": tool.brew, "apt": tool.apt,
    })
    with open(CUSTOM_TOOLS_FILE, "w") as f:
        json.dump(existing, f, indent=2, ensure_ascii=False)


def add_tool_interactive(existing_categories: list[str]) -> Tool | None:
    """Fragt interaktiv nach Tool-Daten und gibt ein neues Tool zurück."""
    print("\n  ── Neues Tool hinzufügen ──────────────────────────────────────\n")
    print("  Bestehende Kategorien: " + ", ".join(existing_categories) + "\n")

    def ask(prompt: str, default: str = "") -> str | None:
        hint = f" [{default}]" if default else ""
        try:
            val = input(f"  {prompt}{hint}: ").strip()
            return val if val else (default if default else None)
        except (EOFError, KeyboardInterrupt):
            return None

    category    = ask("Kategorie (bestehend oder neu)")
    if not category: return None
    name        = ask("Name")
    if not name: return None
    description = ask("Kurzbeschreibung (für Listenansicht)")
    if not description: return None
    details_raw = ask("Details + Beispiele (\\n für Zeilenumbruch)", "")
    if details_raw is None: return None
    details     = details_raw.replace("\\n", "\n")
    command     = ask("Befehl")
    if not command: return None
    binary      = ask("Binary (für Verfügbarkeitscheck)", command.split()[0])
    if not binary: return None
    brew        = ask("brew-Paketname (leer = wie binary)", "") or ""
    apt_pkg     = ask("apt-Paketname  (leer = wie binary)", "") or ""

    return Tool(
        category=category, name=name, description=description,
        details=details, command=command, binary=binary,
        brew=brew, apt=apt_pkg,
    )


def fzf_run(items: list[str], extra_args: list[str]) -> str | None:
    """Einfacher fzf-Aufruf, gibt die gewählte Zeile zurück."""
    result = subprocess.run(
        _FZF_BASE + extra_args,
        input="\n".join(items),
        text=True,
        stdout=subprocess.PIPE,
    )
    return result.stdout.strip() if result.returncode == 0 and result.stdout.strip() else None


def fzf_run_keyed(items: list[str], extra_args: list[str],
                  expect: str) -> tuple[str, str | None]:
    """fzf-Aufruf mit --expect. Gibt (taste, zeile) zurück; taste='' für Enter."""
    result = subprocess.run(
        _FZF_BASE + [f"--expect={expect}"] + extra_args,
        input="\n".join(items),
        text=True,
        stdout=subprocess.PIPE,
    )
    if result.returncode != 0 or not result.stdout.strip():
        return ("", None)
    lines = result.stdout.split("\n", 1)
    key  = lines[0].strip()
    item = lines[1].strip() if len(lines) > 1 and lines[1].strip() else None
    return (key, item)


def main() -> None:
    if not shutil.which("fzf"):
        print("fzf nicht gefunden – bitte installieren:")
        print("  macOS:  brew install fzf")
        print("  Linux:  sudo apt install fzf")
        sys.exit(1)

    # Benutzerdefinierte Tools aus JSON laden und anhängen
    TOOLS.extend(load_custom_tools())

    while True:
        # Bei jedem Schleifendurchlauf neu berechnen (damit neue Tools sofort erscheinen)
        platform_tools = [t for t in TOOLS if (IS_MAC and t.mac) or (IS_LINUX and t.linux)]
        categories = list(dict.fromkeys(t.category for t in platform_tools))
        ALL = "★  Alle Tools"

        total_installed = sum(1 for t in platform_tools if is_available(t))
        cat_total     = {c: sum(1 for t in platform_tools if t.category == c) for c in categories}
        cat_installed = {c: sum(1 for t in platform_tools if t.category == c and is_available(t))
                         for c in categories}

        cat_items = [f"{ALL}  ({total_installed}/{len(platform_tools)})"] + [
            f"{c}  ({cat_installed[c]}/{cat_total[c]})" for c in categories
        ]

        # ── Schritt 1: Kategorie wählen ──────────────────────────────────────
        cat_choice = fzf_run(
            cat_items,
            [
                "--prompt=  Kategorie > ",
                "--header= toolbox  ·  Kategorie wählen  (Esc = beenden)",
                "--no-preview",
            ],
        )
        if not cat_choice:
            sys.exit(0)

        if cat_choice.startswith(ALL[:3]):
            filtered, cat_label = platform_tools, "Alle Tools"
        else:
            cat_label = cat_choice.split("  (")[0]
            filtered  = [t for t in platform_tools if t.category == cat_label]

        # ── Schritt 2: Tool wählen ───────────────────────────────────────────
        DIM   = "\033[2m"
        YEL   = "\033[33m"
        RST   = "\033[0m"
        BADGE = f"  {YEL}[nicht installiert]{RST}"

        tool_lines = []
        for i, t in enumerate(filtered):
            avail = is_available(t)
            if avail:
                name_col    = t.name
                desc_col    = t.description
                raw_preview = f"$ {t.command}"
            else:
                name_col    = f"{DIM}{t.name}{RST}"
                desc_col    = f"{DIM}{t.description}{RST}{BADGE}"
                raw_preview = f"⚠  {get_install_cmd(t)}"

            # Felder: display_name | display_desc | name_b64 | preview_b64 | idx:status | details_b64
            # Alle Preview-Felder als Base64: kein Leerzeichen → kein Word-Splitting durch fzf
            name_b64    = base64.b64encode(t.name.encode()).decode()
            preview_b64 = base64.b64encode(raw_preview.encode()).decode()
            details_b64 = base64.b64encode(t.details.encode()).decode()
            line = (f"{name_col}\t{desc_col}\t{name_b64}\t{preview_b64}"
                    f"\t{i}:{'avail' if avail else 'missing'}\t{details_b64}")
            tool_lines.append(line)

        # Preview: alle Felder base64-kodiert → sicheres Dekodieren ohne Word-Splitting
        # Normaler Python-String (kein r""), damit \" → " wird (echter Shell-Quoted-Context)
        preview = (
            "printf '\\n  \\033[1m%s\\033[0m\\n\\n' \"$(printf '%s' {3} | base64 -d)\"; "
            "printf '%s' {6} | base64 -d; "
            "printf '\\n\\n  \\033[33m%s\\033[0m\\n' \"$(printf '%s' {4} | base64 -d)\""
        )

        key, chosen = fzf_run_keyed(
            tool_lines,
            [
                f"--prompt=  {cat_label} > ",
                (f"--header= ‹ {cat_label}"
                 "   ·   Enter=Starten   Ctrl-X=Deinstallieren   Ctrl-N=Neu   Esc=zurück"),
                "--delimiter=\t",
                "--with-nth=1,2",
                f"--preview={preview}",
                "--preview-window=right:45%:wrap",
            ],
            expect="ctrl-x,ctrl-n",
        )

        # ── Ctrl-N: Neues Tool hinzufügen ────────────────────────────────────
        if key == "ctrl-n":
            cats = list(dict.fromkeys(t.category for t in TOOLS))
            new_tool = add_tool_interactive(cats)
            if new_tool:
                save_custom_tool(new_tool)
                TOOLS.append(new_tool)
                print(f"\n  ✓  '{new_tool.name}' wurde gespeichert in {CUSTOM_TOOLS_FILE}\n")
                try:
                    input("  Enter drücken zum Fortfahren…")
                except (EOFError, KeyboardInterrupt):
                    pass
            continue

        if chosen is None:
            continue  # Esc → zurück zur Kategorie-Auswahl

        parts = chosen.split("\t")
        if len(parts) < 5:
            continue

        idx, status = parts[4].split(":", 1)
        t     = filtered[int(idx)]
        avail = (status == "avail")

        # ── Ctrl-X: Deinstallieren ────────────────────────────────────────────
        if key == "ctrl-x":
            if not avail:
                print(f"\n  {t.name} ist nicht installiert.\n")
            else:
                uninstall_cmd = get_uninstall_cmd(t)
                print(f"\n  {t.name} deinstallieren mit: {uninstall_cmd}\n")
                try:
                    ans = input("  Wirklich deinstallieren? [j/N] ").strip().lower()
                except (EOFError, KeyboardInterrupt):
                    print()
                    sys.exit(0)
                if ans == "j":
                    print()
                    os.system(uninstall_cmd)
            sys.exit(0)

        # ── Enter: Starten oder Installieren ────────────────────────────────
        if avail:
            command = resolve_command(t.command)
            print(f"\n▶  {t.name}  —  {command}\n")
            open_new_tab(command)
            sys.exit(0)
        else:
            install_cmd = get_install_cmd(t)
            print(f"\n  ⚠  {t.name} ist nicht installiert.")
            print(f"     Installieren mit: {install_cmd}\n")
            try:
                ans = input("  Jetzt installieren? [j/N] ").strip().lower()
            except (EOFError, KeyboardInterrupt):
                print()
                sys.exit(0)
            if ans == "j":
                print()
                os.system(install_cmd)
            sys.exit(0)


if __name__ == "__main__":
    main()
