From 831fe62de7a6a65e657728b9b16ba1d02594c4c5 Mon Sep 17 00:00:00 2001 From: rene Date: Sat, 28 Mar 2026 10:49:59 +0100 Subject: [PATCH] =?UTF-8?q?toolbox:=2065=20Tools,=20Install/Uninstall,=20C?= =?UTF-8?q?trl-N,=20ausf=C3=BChrliche=20Beschreibungen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tool-Dataclass um details, brew, apt Felder erweitert - ~65 Tools in 12 Kategorien (neu: Text/Daten, Dokumente, Bilder, Produktiv) - Nicht installierte Tools gedimmt mit Install-Angebot (brew/apt) - Ctrl-X: Deinstallation mit Bestätigung - Ctrl-N: Wizard zum Hinzufügen eigener Tools (custom_tools.json) - Kategorie-Header zeigt installiert/gesamt-Zähler - Preview-Pane rechts mit ausführlichen Beschreibungen und Beispielen - heic2jpg und h2j korrekt aus Scripts dokumentiert --- README.md | 71 +++-- bin/toolbox | 878 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 839 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index aee09ce..369c161 100644 --- a/README.md +++ b/README.md @@ -58,16 +58,33 @@ gitupdate # alle Repos pullen ### toolbox / tools - interaktiver Tool-Launcher -Zweistufiges fzf-Menü: erst Kategorie wählen, dann Tool — mit Preview-Pane für Beschreibung und Befehl. -Öffnet immer in einem neuen Fenster mit aktuellem Profil im aktuellen Verzeichnis. -Funktioniert auf macOS (iTerm2) und Linux (xfce4-terminal, gnome-terminal, kitty, alacritty). +Zweistufiges fzf-Menü: erst Kategorie wählen, dann Tool — mit Preview-Pane für ausführliche +Beschreibung und Beispiele. Öffnet immer in einem neuen Tab (macOS: iTerm2, Linux: xfce4-terminal, +gnome-terminal, kitty, alacritty) im aktuellen Verzeichnis. ```bash tools # interaktiver Launcher -tools-ref # statisches Cheatsheet (Kurzform: tr) ``` -Mac-only Tools (z.B. `temps`) werden auf Linux automatisch ausgeblendet. +Tastenbelegung im Tool-Menü: + +| Taste | Funktion | +|-------|----------| +| `Enter` | Tool in neuem Tab starten | +| `Ctrl-X` | Installiertes Tool deinstallieren | +| `Ctrl-N` | Neues Tool hinzufügen (Wizard) | +| `Esc` | Zurück zur Kategorieauswahl | + +Nicht installierte Tools werden **gedimmt** angezeigt — `Enter` bietet die Installation an +(`brew install …` / `sudo apt install …`). Der Kategorie-Header zeigt `(installiert/gesamt)`. + +Kategorien: Git, Dateien, Anzeige, System, Netzwerk, Text/Daten, Rechnen, Produktiv, +Dokumente, Bilder, KI, Spass — insgesamt ~65 vorkonfigurierte Tools. + +**Eigene Tools** werden in `~/.config/toolbox/custom_tools.json` gespeichert und +beim Start automatisch geladen. Die JSON-Datei ist auch manuell editierbar. + +Mac-only Tools (z.B. `temps`, `dog`) werden auf Linux automatisch ausgeblendet. Linux-Binaries (`batcat`, `fdfind`) werden automatisch aufgelöst. ### git-notify - Update-Benachrichtigung beim Shell-Start @@ -93,27 +110,31 @@ Verwendet `pbpaste`/`pbcopy` auf macOS und `xclip` auf Linux. ## Terminal-Tools (installiert & konfiguriert) -| Tool | Zweck | Alias | -|------|-------|-------| -| **lazygit** | Git TUI: stagen, committen, rebasen | `lg` | -| **delta** | Schöne Git-Diffs (side-by-side, automatisch aktiv) | — | -| **atuin** | Fuzzy History-Suche mit Zeitstempel | `Ctrl+R` | -| **mise** | Tool-Versionen pro Projekt verwalten | `mise use` | -| **btop** | Systemmonitor (CPU/RAM/Netz/Prozesse) | `btop` | -| **yazi** | Terminal-Dateimanager | `yazi` | -| **bat** | cat mit Syntax-Highlighting | `bat` | -| **eza** | Modernes ls | `eza -la`, `eza -T` | -| **fzf** | Fuzzy-Finder | `Ctrl+T`, `Ctrl+R` | -| **zoxide** | Smart cd (lernt häufige Pfade) | `z` | -| **nmap** | Netzwerk-Scanner | `nmap` | -| **units** | Einheitenumrechnung | `units` | -| **cmatrix** | Matrix-Screensaver | `cmatrix` | -| **asciiquarium** | Aquarium-Screensaver | `asciiquarium` | -| **pipes.sh** | Rohre-Screensaver | `pipes.sh` | -| **cbonsai** | Bonsai-Screensaver | `cbonsai` | -| **nms** | Sneakers-Entschlüsselungseffekt | `ls \| nms` | +Alle Tools sind über `tools` (toolbox) erreichbar. Hier eine Auswahl der wichtigsten: -Screensaver rotieren automatisch nach 5 Minuten Idle. +| Tool | Kategorie | Zweck | +|------|-----------|-------| +| **lazygit** | Git | Git TUI: stagen, committen, rebasen | +| **tig** | Git | Git-Log TUI: History, Blame, Branches | +| **gh** | Git | GitHub CLI: PRs, Issues, Repos | +| **delta** | Anzeige | Schöne Git-Diffs (side-by-side) | +| **yazi** | Dateien | TUI-Dateimanager mit Bildvorschau | +| **fzf** | Dateien | Universeller Fuzzy-Finder | +| **fd** | Dateien | Schnelles find | +| **rg** | Dateien | Blitzschnelles grep (ripgrep) | +| **bat** | Anzeige | cat mit Syntax-Highlighting | +| **eza** | Anzeige | Modernes ls mit Git-Status | +| **btop** | System | Systemmonitor (CPU/RAM/Netz/Prozesse) | +| **jq** | Text/Daten | JSON-Prozessor | +| **yq** | Text/Daten | YAML/JSON-Prozessor | +| **pandoc** | Dokumente | Dokument-Konverter (md↔pdf↔docx) | +| **heic2jpg / h2j** | Bilder | HEIC → JPG konvertieren | +| **ffmpeg** | Bilder | Video/Audio konvertieren | +| **tldr** | Produktiv | Vereinfachte Manpages mit Beispielen | +| **nmap** | Netzwerk | Netzwerk-Scanner | +| **zoxide** | Dateien | Smart cd (lernt häufige Pfade) | + +Screensaver (cmatrix, asciiquarium, pipes.sh, cbonsai) rotieren automatisch nach 5 Minuten Idle. ## Installation diff --git a/bin/toolbox b/bin/toolbox index 5b50cb8..f4e6487 100755 --- a/bin/toolbox +++ b/bin/toolbox @@ -10,72 +10,600 @@ import sys import os import platform import shlex +import json from dataclasses import dataclass IS_MAC = platform.system() == "Darwin" IS_LINUX = platform.system() == "Linux" -CURRENT_DIR = os.getcwd() +CURRENT_DIR = os.getcwd() +CUSTOM_TOOLS_FILE = os.path.expanduser("~/.config/toolbox/custom_tools.json") @dataclass class Tool: category: str name: str - description: 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", "lazygit", "lazygit"), - Tool("Git", "gitcheck", "Status aller Repos prüfen", "~/git-check-all.sh", "git"), - Tool("Git", "gitsync", "Alle Repos mit Gitea synchronisieren", "~/git-projekte/dotfiles-rene/bin/git-sync-all.sh", "git"), - Tool("Git", "git log", "Commit-History (mit delta, side-by-side)", "git log -p", "git"), - Tool("Git", "git diff", "Änderungen anzeigen (mit delta)", "git diff", "git"), + # ── 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"), - # --- Navigation & Dateien --- - Tool("Dateien", "yazi", "Terminal-Dateimanager (q = quit)", "yazi", "yazi"), - Tool("Dateien", "fzf", "Fuzzy-Finder: Dateien und History durchsuchen", "fzf", "fzf"), - Tool("Dateien", "ncdu", "Interaktive Festplattennutzung", "ncdu ~", "ncdu"), - Tool("Dateien", "duf", "Übersicht freier Speicherplatz", "duf", "duf"), - Tool("Dateien", "fd", "Schnelles find (Beispiel: fd .py)", "fd", "fd"), - Tool("Dateien", "rg", "Blitzschnelles grep (Beispiel: rg TODO)", "rg", "rg"), + 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"), - # --- Anzeige --- - Tool("Anzeige", "bat", "cat mit Syntax-Highlighting", "bat", "bat"), - Tool("Anzeige", "eza -la", "Modernes ls mit Details, Farben, Git-Status", "eza -la", "eza"), - Tool("Anzeige", "eza -T", "Verzeichnisbaum", "eza -T", "eza"), - Tool("Anzeige", "delta", "Schöne Git-Diffs (wird automatisch verwendet)", "git diff HEAD~1 | delta", "delta"), + 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"), - # --- System & Monitoring --- - Tool("System", "btop", "Systemmonitor: CPU, RAM, Netz, Prozesse", "btop", "btop"), - Tool("System", "fastfetch", "Systeminfo-Übersicht", "fastfetch", "fastfetch"), - Tool("System", "temps", "CPU/GPU-Temperatur + Akku (Mac)", "sudo powermetrics -s cpu_power,gpu_power,thermal,battery -i 1000 -n 1", - "sudo", linux=False), + 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"), - # --- Netzwerk --- - Tool("Netzwerk", "nmap", "Netzwerk-Scanner (Beispiel: nmap 10.47.11.0/24)", "nmap -sn 10.47.11.0/24", "nmap"), + 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"), - # --- Berechnung --- - Tool("Rechnen", "units", "Einheitenumrechnung (Beispiel: units 1kWh MJ)", "units", "units"), - Tool("Rechnen", "python3", "Python REPL für schnelle Berechnungen", "python3", "python3"), + 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"), - # --- KI --- - Tool("KI", "ki-chat", "Offline-KI interaktiver Chat", "ki interactive", "ki"), - Tool("KI", "ki-agent", "Offline-KI Agent-Modus: Datei- und Shell-Zugriff", "ki interactive --agent-mode", "ki"), - Tool("KI", "ki-commit", "Commit-Message mit KI generieren", "ki commit", "ki"), - Tool("KI", "ki-diff", "Git-Diff mit KI erklären lassen", "ki diff", "ki"), + 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"), - # --- Screensaver / Spass --- - Tool("Spass", "cmatrix", "Matrix-Regen (q = quit)", "cmatrix -sab", "cmatrix"), - Tool("Spass", "asciiquarium", "Aquarium im Terminal (q = quit)", "asciiquarium", "asciiquarium"), - Tool("Spass", "pipes.sh", "Animierte Rohre (q = quit)", "pipes.sh -t 0 -p 4", "pipes.sh"), - Tool("Spass", "cbonsai", "Wachsender Bonsai-Baum (q = quit)", "cbonsai -l", "cbonsai"), - Tool("Spass", "nms", "Sneakers-Entschlüsselungseffekt", "ls -la | nms", "nms"), + 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 ' " + "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 ", + "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"), ] @@ -100,6 +628,24 @@ def is_available(tool: Tool) -> bool: 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(): @@ -108,22 +654,29 @@ def resolve_command(cmd: str) -> str: return cmd -def open_new_window(command: str) -> None: - """Öffnet den Befehl in einem neuen Fenster im aktuellen Verzeichnis.""" +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' - ' set w to (create window with current profile)\n' - ' tell current session of w\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' ) - subprocess.run(["osascript", "-e", script], capture_output=True) + 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 @@ -131,12 +684,12 @@ def open_new_window(command: str) -> None: candidates = [ ("xfce4-terminal", ["xfce4-terminal", f"--working-directory={CURRENT_DIR}", - "-e", f"bash -c {shlex.quote(bash_cmd)}"]), + "--tab", "-e", f"bash -c {shlex.quote(bash_cmd)}"]), ("gnome-terminal", ["gnome-terminal", f"--working-directory={CURRENT_DIR}", - "--", "bash", "-c", bash_cmd]), + "--tab", "--", "bash", "-c", bash_cmd]), ("kitty", ["kitty", "--directory", CURRENT_DIR, - "bash", "-c", bash_cmd]), + "--new-tab", "bash", "-c", bash_cmd]), ("alacritty", ["alacritty", "--working-directory", CURRENT_DIR, "-e", "bash", "-c", bash_cmd]), ("xterm", ["xterm", "-e", @@ -150,7 +703,74 @@ def open_new_window(command: str) -> None: 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), @@ -160,6 +780,23 @@ def fzf_run(items: list[str], extra_args: list[str]) -> str | None: 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, + capture_output=True, + ) + 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:") @@ -167,24 +804,24 @@ def main() -> None: print(" Linux: sudo apt install fzf") sys.exit(1) - available = [t for t in TOOLS if is_available(t)] - categories = list(dict.fromkeys(t.category for t in available)) - ALL = "★ Alle Tools" - - # Kategorie-Vorschau: Anzahl verfügbarer Tools pro Kategorie - cat_counts = {c: sum(1 for t in available if t.category == c) for c in categories} - cat_items = [f"{ALL} ({len(available)})"] + [ - f"{c} ({cat_counts[c]})" for c in categories - ] - - # Preview für Kategorien: Tools der markierten Kategorie anzeigen - # {1} = Kategoriename (erstes Wort), fzf-Feldreferenz - cat_preview = ( - "cat << 'CATEOF'\n" # Dummy — wir nutzen awk auf die Eingabeliste - ) - # Einfacherer Ansatz: kein Preview auf Kategorie-Ebene, nur auf Tool-Ebene + # 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, @@ -197,45 +834,116 @@ def main() -> None: if not cat_choice: sys.exit(0) - if cat_choice.startswith(ALL[:3]): # "★ Alle …" - filtered, cat_label = available, "Alle Tools" + if cat_choice.startswith(ALL[:3]): + filtered, cat_label = platform_tools, "Alle Tools" else: - cat_label = cat_choice.split(" (")[0] # Name ohne "(N)" - filtered = [t for t in available if t.category == cat_label] + cat_label = cat_choice.split(" (")[0] + filtered = [t for t in platform_tools if t.category == cat_label] # ── Schritt 2: Tool wählen ─────────────────────────────────────────── - # Felder TAB-getrennt: name \t description \t command - tool_lines = [f"{t.name}\t{t.description}\t{t.command}" for t in filtered] + DIM = "\033[2m" + YEL = "\033[33m" + RST = "\033[0m" + BADGE = f" {YEL}[nicht installiert]{RST}" - # Preview zeigt Beschreibung + Befehl für das markierte Tool. - # {2} und {3} sind fzf-Feldreferenzen (TAB-Delimiter), werden von fzf - # korrekt gequotet, bevor sie in den Shell-Befehl eingefügt werden. - preview = r"printf '\n \033[1m%s\033[0m\n\n \033[33m$\033[0m %s\n' {2} {3}" + 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)}" - chosen = fzf_run( + # Felder: display_name | display_desc | raw_name | raw_preview | idx:status | details + line = (f"{name_col}\t{desc_col}\t{t.name}\t{raw_preview}" + f"\t{i}:{'avail' if avail else 'missing'}\t{t.details}") + tool_lines.append(line) + + # Preview: Name (bold) + Details + Befehl/Install (gelb) + preview = ( + r"printf '\n \033[1m%s\033[0m\n\n%s\n\n \033[33m%s\033[0m\n' " + r"'{3}' '{6}' '{4}'" + ) + + key, chosen = fzf_run_keyed( tool_lines, [ f"--prompt= {cat_label} > ", - f"--header= ‹ {cat_label} (Esc = zurück zur Kategorie)", + (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=bottom:5:wrap", + "--preview-window=right:45%:wrap", ], + expect="ctrl-x,ctrl-n", ) - if not chosen: + # ── 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) < 3: + if len(parts) < 5: continue - tool_name, _, tool_cmd = parts[0], parts[1], parts[2] - command = resolve_command(tool_cmd) - print(f"\n▶ {tool_name} — {command}\n") - open_new_window(command) - sys.exit(0) + 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__":