| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138 | """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() |