dotfiles-rene/bin/toolbox
rene 4c2d7db69b toolbox: Preview-Felder base64-kodieren um fzf Word-Splitting zu verhindern
Beschreibungstexte mit Leerzeichen und Zeilenumbrüche wurden durch fzf
word-gesplittet: Eintraege landeten in der Auswahlliste, printf-Cycling
zerlegte den Preview-Text Wort fuer Wort. Fix: name, raw_preview und
details als Base64 in den fzf-Feldern speichern (kein Leerzeichen →
kein Splitting), im Preview-Befehl per base64 -d dekodieren.
Shell-Quoted-Context per echten "-Zeichen (kein Raw-String) sichert
auch die Ausgabe von $(…) gegen nachtraegliches Splitting.
2026-04-02 16:27:22 +02:00

957 lines
46 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()