"""kode - Vibe Coding im Terminal mit Modellen auf Mietkarten. kode interaktiv im aktuellen Ordner kode -p "Auftrag" ein Auftrag ohne Rueckfragen (Karte muss laufen) kode status | stopp | kosten | modelle | auswertung [--tage N] """ import argparse import os import sys from pathlib import Path # mosh: Die Cursorpositions-Abfrage (CPR) bleibt an. Ohne sie zeichnet # prompt_toolkit die Statuszeile nicht (renderer_height_is_known), und mosh # 1.4 beantwortet sie im Emulator auf dem Server - also sofort, nicht ueber # die Funkstrecke. Am 16.10.2026 erst abgeschaltet, dann deshalb zurueck. from . import agent, freigabe, karte, katalog, konfig, oberflaeche as o, protokoll def main() -> None: p = argparse.ArgumentParser(prog="kode", description="Vibe Coding mit Mietkarten-Modellen") p.add_argument("unterbefehl", nargs="?", choices=["status", "stopp", "kosten", "modelle", "auswertung"]) p.add_argument("--tage", type=int, default=30, help="Zeitraum für auswertung") p.add_argument("-p", "--prompt", help="ein Auftrag, nicht interaktiv") p.add_argument("-m", "--modell", help="Modell (Kennung, Nummer oder Namensteil)") p.add_argument("--modus", choices=freigabe.MODI, help="Freigabemodus (Standard: fragen)") p.add_argument("-w", "--weiter", action="store_true", help="letzte Sitzung in diesem Ordner fortsetzen") a = p.parse_args() konfig.verzeichnisse() if a.unterbefehl == "status": o.status_zeigen() return if a.unterbefehl == "stopp": protokoll.ereignis("befehl", name="kode stopp") o.stoppen() return if a.unterbefehl == "kosten": o.kosten_zeigen() return if a.unterbefehl == "auswertung": from . import auswertung auswertung.zeigen(a.tage) return if a.unterbefehl == "modelle": o.modelle_zeigen(o.einstellungen().get("modell")) return if not konfig.FESTE_BASIS and not (konfig.RUNPOD_API_KEY or konfig.VAST_API_KEY): sys.exit(f"Kein RUNPOD_API_KEY und kein VAST_API_KEY in {konfig.KONFIG_DATEI}.") mid = None if a.modell: mid = katalog.finden(a.modell) if not mid: sys.exit(f"Unbekanntes Modell: {a.modell} (kode modelle)") elif konfig.FESTE_BASIS and "lokal" in katalog.MODELLE: mid = "lokal" else: z = karte.lesen() # Laeuft schon eine Karte, wird ihr Modell genommen - sie kostet ja. if z["phase"] == "bereit" and z.get("modell") in katalog.MODELLE: mid = z["modell"] else: mid = o.einstellungen().get("modell") if mid not in katalog.MODELLE: mid = katalog.STANDARD_MODELL leer = o.einstellungen().get("leerlauf") if leer and not karte.lesen().get("kennung"): karte.aendern(leerlauf_min=int(leer)) s = agent.Sitzung(modell_id=mid, cwd=Path.cwd(), modus=a.modus or "fragen") if a.weiter: d = o.letzte_sitzung(s.projekt, s.kennung) if d: s.verlauf = d["verlauf"] if a.prompt: if not karte.bereit(): sys.exit("Keine Karte bereit - erst interaktiv `kode` und /start.") s.modus = a.modus or "dateien" ui = o.Stumm(s, None) protokoll.sitzung_beginnen(s.kennung, cwd=str(s.projekt), modell=s.modell_id, modus=s.modus, interaktiv=False) try: s.frage(a.prompt, ui) except Exception as f: protokoll.fehler("auftrag", f, eingabe=a.prompt) raise protokoll.ereignis("sitzung_ende", nachrichten=len(s.verlauf)) o.sitzung_sichern(s) return o.schleife(s) def _in_tmux_starten() -> None: """Unter mosh in eine eigene tmux-Sitzung wechseln (Scrollen per Mausrad). mosh uebertraegt nur den sichtbaren Bildschirm, Zurueckrollen geht nicht ("es ist nicht gut scrollbar", 16.10.2026). tmux haelt 100 000 Zeilen Verlauf, und das Mausrad kommt durch mosh durch. Nebenbei ueberlebt die Sitzung einen Verbindungsabbruch: `kode` im selben Ordner haengt sich wieder an (new-session -A). Abschalten: KODE_OHNE_TMUX=1, erzwingen (z. B. per ssh): KODE_MIT_TMUX=1. Nicht fuer -p und Unterbefehle. """ import hashlib import shutil if (os.getenv("TMUX") or os.getenv("KODE_OHNE_TMUX") or not sys.stdin.isatty() or not shutil.which("tmux") or not (protokoll._ist_mosh() or os.getenv("KODE_MIT_TMUX"))): return if len(sys.argv) > 1 and (sys.argv[1] in ("status", "stopp", "kosten", "modelle", "auswertung") or any(a in ("-p", "--prompt", "-h", "--help") for a in sys.argv[1:])): return cwd = os.getcwd() name = "kode-" + "".join(c if c.isalnum() else "-" for c in os.path.basename(cwd) or "root")[:20] \ + "-" + hashlib.sha1(cwd.encode()).hexdigest()[:4] umgebung = [x for k, v in os.environ.items() if k.startswith("KODE_") for x in ("-e", f"{k}={v}")] konf = str(Path(__file__).resolve().parent.parent / "tmux.conf") leiste = f"{konfig.DATEN}/leiste/#{{session_name}}" befehl = ["tmux", "-L", os.getenv("KODE_TMUX_SOCKET", "kode"), "-f", konf, "new-session", "-A", "-s", name, "-c", cwd, *umgebung, "-e", "KODE_IN_TMUX=1", "-e", f"KODE_UEBER_MOSH={int(protokoll._ist_mosh())}", "-e", f"KODE_TMUX_SITZUNG={name}", sys.executable, "-m", "kode", *sys.argv[1:], ";", "source-file", konf, ";", "set", "-g", "status-format[0]", f"#[align=left]#(cat {leiste}.1 2>/dev/null)", ";", "set", "-g", "status-format[1]", f"#[align=left]#(cat {leiste}.2 2>/dev/null)" "#[align=right]#{?#{&&:#{pane_in_mode},#{e|>=:#{client_width},80}},#[fg=colour244]Scrollen · q zurück ,}"] os.execvp("tmux", befehl) if __name__ == "__main__": _in_tmux_starten() main()