CCW — Claude Code Web: das Handbuch zum mobilen Terminal
Claude Code läuft bei mir nicht auf dem Laptop, sondern auf einer VM im Homelab — in einer
tmux-Session, die nie endet. Am Schreibtisch hänge ich mit ttyd daran. Aber die
interessanten Momente passieren selten am Schreibtisch: Claude arbeitet zehn Minuten an einem
Refactoring und fragt dann „darf ich diese Datei ändern?” — während ich in der Küche stehe.
CCW (Claude Code Web) ist die Antwort darauf: dieselbe Sitzung im Browser des Handys, mit Bedienelementen, die eine reine Kommandozeile nicht bieten kann. Dieser Post ist das Handbuch dazu — und zugleich eine Blaupause, falls du dir etwas Ähnliches baust.
Die Grundidee
Kein zweiter Prozess, keine parallele Sitzung: CCW klemmt sich per WebSocket an ein
Pseudo-Terminal, in dem tmux attach läuft. Wer wo tippt, ist egal — es ist immer dieselbe
Shell, dasselbe Claude, derselbe Verlauf.
flowchart LR
A["Desktop<br/>ttyd :7681"] --> T
B["Handy<br/>CCW :7682"] --> W["FastAPI /ws"]
W --> P["PTY"]
P --> T["tmux-Session claude<br/>~/.ccw/ccw.sock"]
T --> C["Claude Code<br/>+ Shells + SSH"]
tmux new-session -A -s claude heißt attach-if-exists-else-create. Genau daraus entsteht
das Gefühl, die Arbeit einfach mitzunehmen: Du klappst den Laptop zu, ziehst am Handy die
PWA hoch und schaust demselben Prozess weiter zu.
Ein Web-Terminal ist eine vollwertige Shell im Browser. Alles hier steht hinter mehrstufiger Anmeldung, und von außen zusätzlich hinter einem SSO-Proxy. Ohne diese Schichten gehört so etwas nicht ins Internet.
Anmelden
Die Kette ist bewusst mehrstufig, weil dahinter eine Vollshell wartet:
- E-Mail + Passwort
- PIN (4–8 Ziffern, beim ersten Login selbst gesetzt)
- E-Mail-Code — nur auf einem unbekannten Gerät. Danach gilt es 30 Tage als bekannt.
Wer es schneller mag: Passkey einrichten (⚙ → Biometrie einrichten), dann entsperrt der Fingerabdruck. Der Passkey ist an die Adresse gebunden — intern und extern also getrennt registrieren.
Als PWA installiert („zum Startbildschirm hinzufügen”) läuft CCW im Vollbild und darf Push-Benachrichtigungen anzeigen — der Punkt, an dem aus einem Web-Terminal ein Werkzeug wird, das sich meldet.
Der Bildschirm in drei Zonen
| Zone | Inhalt |
|---|---|
| Oben | Verbindungspunkt, Tabs (= tmux-Fenster), +, Warte-Badge, Claude-Status, 🪄 Rituale, ? Handbuch, ⚙, ⋮ |
| Mitte | Das Terminal (xterm.js) |
| Unten | Keybar mit Sondertasten und Werkzeugen, darunter die Eingabezeile |
Tabs, die etwas erzählen
Jedes tmux-Fenster ist eine Karte. Sie trägt mehr als nur einen Namen:
✳= hier läuft Claude,❯= normale Shell. Erkannt wird das über den TTY-Prozessbaum, nicht über den Vordergrundbefehl — sonst flackert die Anzeige auf „Shell”, sobald Claude ein Werkzeug startet.- Grüner Punkt = neue Ausgabe. Pinker Punkt = das Fenster wartet auf dich.
12mdaneben: so lange wartet es schon. Bei zehn offenen Sitzungen ist das der Unterschied zwischen „hängt seit einer Stunde” und „hat gerade gefragt”.
Für Vollbild-Anwendungen (Claude selbst, vim, htop) wird der grüne Punkt unterdrückt —
die zeichnen sich ständig neu, „neue Ausgabe” wäre dort ein Dauersignal ohne Aussage.
Der Claude-Status
Claudes eigene Statuszeile ist Text im Terminal: Blättert man zurück, ist sie weg. CCW liest sie serverseitig aus und zeigt sie als festes Element oben — Kontext-Füllstand als Balken (ab 70 % gelb, ab 90 % rot), Modellname, und per Tipp die Rate-Limits mit Reset-Zeiten. Weil die Information damit doppelt vorliegt, lassen sich sowohl die tmux-Leiste als auch Claudes eigene Statuszeile abschalten — zusammen drei bis vier Zeilen mehr Terminal.
Mit Claude arbeiten
Eingeben
Neben dem direkten Tippen ins Terminal gibt es die Eingabezeile: mehrzeilig, mit Verlauf, und mit einem Entwurf pro Fenster, der Fensterwechsel und Neuladen überlebt.
Ein Detail, das mich Nerven gekostet hat: Text und Enter dürfen nicht im selben Rutsch kommen. Claude Code wertet einen schnellen Burst als Einfügen — das Enter landet dann als Zeilenumbruch im Eingabefeld statt abzuschicken. CCW sendet das Enter deshalb bewusst verzögert.
Freigaben beantworten
Fragt Claude „Do you want to proceed?”, erscheint ein Overlay mit ✓ Ja · ⏩ Immer · ✗ Nein — statt am Handy die richtige Ziffer treffen zu müssen.
Darüber steht, worum es geht: der auslösende Werkzeugaufruf und der Diff, hinzugefügte Zeilen grün, entfernte rot. Die Frage allein („Do you want to proceed?”) ist auf einem kleinen Display wertlos; entscheidend ist, was man freigibt.
Rituale statt Tippen
Ein kleines Menü (🪄) sendet vereinbarte Code-Wörter: „Bearbeitungsstand?” zum Tagesstart, „Aufräumen” zum Feierabend (mit Sicherheitsabfrage), „merk: …” für Notizen. Das sind bei mir etablierte Trigger, hinter denen jeweils ein ganzer Ablauf steht — am Handy will man sie nicht buchstabieren.
Suchen und springen
Ein Terminal ist linear. Genau da helfen grafische Elemente am meisten:
| Werkzeug | Was es tut |
|---|---|
| Strg/⌘+F | Suche im sichtbaren Puffer, mit Hervorhebung |
| ⧉ Verlauf | durchsucht den vollen tmux-Verlauf (50.000 Zeilen) — der Browser kennt nur einen Ausschnitt |
| ⊞ | erweitert die Suche auf alle Fenster; Treffer zeigen den Tab und springen dorthin |
| ⚑ Sprungmarken | zerlegt den Verlauf in Werkzeugaufrufe, Antworten und Befehle — ein Tipp blättert dorthin (Strg/⌘+J) |
| 🔗 Links | vierte Marken-Sicht: alle URLs und Dateipfade des Verlaufs, dedupliziert — Klick öffnet, ⤴ blättert zur Fundstelle |
| Klickbare Pfade | backend/files.py:412 in der Ausgabe öffnet direkt die Vorschau bzw. den Editor |
Die Link-Sicht ist der Nachzügler, der sich als der nützlichste Teil entpuppt hat: Anklickbare Pfade im Terminal helfen nur, solange die Zeile im Bild ist. Die Liste beantwortet dagegen „welche Adresse hat Claude vorhin genannt?” — ohne zu wissen, wo.
Zwei Details, die dabei zählen: Lange URLs bricht das Terminal um, zeilenweises Extrahieren
liefert dann https://gitlab statt der vollen Adresse — umbrochene Zeilen müssen über
#{pane_width} wieder zusammengesetzt werden. Und für eine Liste braucht es strengere
Muster als für anklickbare Links: Beim Klick klärt sich, ob es die Datei gibt, in der Liste
stünden Fehltreffer dauerhaft herum. Sonst landen deutsche Wortpaare wie „Vorschau/Editor”
zwischen den Pfaden.
Die Sprungmarken haben zudem einen hübschen Fallstrick: tmux’ goto-line im Copy-Mode zählt
von unten, die Marken aus einem capture-pane aber von oben. Ohne Umrechnung über
history_size springt man spiegelverkehrt in den Verlauf.
1
2
3
# so lässt sich das nachmessen — der Wert landet 1:1 in scroll_position
tmux copy-mode -t <ziel> \; send-keys -X -t <ziel> goto-line 20
tmux display-message -p -t <ziel> '#{scroll_position}'
Dateien
Der Explorer bewegt sich unterhalb einer festen Wurzel (bei mir ~/ai-projects) — bewusst
nicht im ganzen Home, damit ~/.ssh, ~/.secrets und Konsorten gar nicht erst im Browser
auftauchen können.
Darin: navigieren, sortieren, filtern, umbenennen, löschen (nicht-leere Ordner nach zweiter Rückfrage), Mehrfachauswahl mit Kopieren/Verschieben/ZIP — und ein Viewer für Bilder, PDF, Video/Audio, Code mit Syntaxfarben und Markdown. Textdateien lassen sich direkt inline bearbeiten und speichern; genau das Richtige für den Fall „Claude verweigert den Edit, ich mache es eben selbst”.
In die andere Richtung: 📎 lädt Datei, Kamerabild oder Zwischenablage-Screenshot hoch und schickt Claude „Notiz + Pfad” — Claude liest die Datei dann selbst. Und die Ablage ist der gemeinsame Ordner, in den Claude Ergebnisse legt („leg das in die Ablage”).
Alle Bibliotheken — Terminal, Editor, PDF-Renderer, Syntax-Highlighting — liegen lokal im Projekt. Ein CDN, das mal hängt, hat mir genau einmal den Ladebalken blockiert; das reicht als Lehre.
Benachrichtigungen: der eigentliche Grund
Der Kern der Sache ist nicht das Terminal im Browser — es ist die Frage: woher weiß ich, dass Claude wartet?
CCW beobachtet dafür jedes Fenster einzeln und meldet sich, wenn dessen Ausgabe zur Ruhe gekommen ist und niemand an der Sitzung hängt. Sitzt du am Desktop, bleibt es still.
Wichtig war die Erkenntnis, dass eine globale Beobachtung nicht reicht: Fragt Claude in einem
inaktiven Tab, ändert sich am sichtbaren Fenster nichts — die Meldung bliebe aus. Umgekehrt
darf ein Dauerläufer wie htop in Fenster 4 nicht alle anderen Meldungen blockieren. Also:
Ruhe-Zustand pro Fenster.
Die Meldung trägt den Fensternamen, ein Tipp öffnet die App genau dort, und bei einer Freigabe stehen Ja / Immer / Nein direkt in der Benachrichtigung — die Antwort geht an das Fenster, das gefragt hat, nicht an das gerade aktive.
Dazu die Feinsteuerung, die im Alltag den Unterschied macht: Ruhezeiten (z. B. 23–7 Uhr) und nur bei Freigabe-Frage — kein Ping für „ist fertig”, nur für „braucht dich”.
Was dabei überhaupt als „Ausgabe” zählt, ist die eigentlich knifflige Frage. Am unteren Rand steht eine Statuszeile, die sich von selbst fortschreibt — ein Countdown, der jede Minute weiterspringt. Für eine Beobachtung, die auf „hat sich etwas geändert?” hört, ist das jede Minute eine Änderung, gefolgt von Ruhe: also genau das Muster, das eine Meldung auslöst. Weil die Sperrfrist zwischen zwei Meldungen ebenfalls eine Minute betrug, trafen sich beide Takte in Resonanz — eine Benachrichtigung pro Minute, ohne dass irgendetwas geschehen wäre. Aufgefallen ist es daran, dass sich in aufeinanderfolgenden Meldungen nur der Countdown unterschied.
Die Beobachtung lässt den unteren Rand deshalb aus. Der naheliegende Weg — „die letzten paar Zeilen einfach ignorieren” — wäre allerdings der falsche gewesen: Die Freigabe-Frage steht ebenfalls unten, und ihr Erscheinen ist die wichtigste Änderung überhaupt. Man hätte ausgerechnet die Meldung wegoptimiert, um die es geht. Geschnitten wird stattdessen an der Rahmenlinie der Eingabebox: Darunter steht nie Programmausgabe, die scrollt oberhalb weg. Ein Terminal ohne solche Oberfläche hat dort keine Linie und wird unverändert beobachtet.
Homelab-Cockpit
Weil das Handy ohnehin in der Hand ist, hängt an CCW ein kleines Statusboard: alle Reverse-Proxy-Hosts mit Erreichbarkeitsprüfung, die Proxmox-VMs mit Start/Neustart, und für den Notfall die freigeschaltete Steckdose in Home Assistant — für den Node, der sich aufgehängt hat und nur per Stromstoß zurückkommt. Jede Aktion mit Rückfrage und Audit-Log.
Wohin die Zeit geht
Claude Code schreibt jede Anfrage und Antwort als JSON-Zeile mit — pro Projekt eine Datei. Das ist eine vollständige Buchhaltung, die nur niemand liest. CCW addiert sie: Tokens und aktive Zeit je Tag und je Projekt, wahlweise über sieben, dreißig, neunzig Tage oder alles.
Zwei Details entscheiden darüber, ob so eine Übersicht ehrlich ist.
Das erste ist die Pausengrenze. Wer schlicht den ersten vom letzten Zeitstempel abzieht, bekommt für eine über Nacht offene Sitzung acht Stunden Arbeit gutgeschrieben. Gezählt wird deshalb der Abstand zwischen aufeinanderfolgenden Einträgen — und nur, solange er unter fünf Minuten liegt.
Das zweite ist der Cache. Bei mir stehen 17,2 Milliarden Tokens über 90 Tage zu Buche — davon sind 16,8 Milliarden Cache-Treffer: Jede Anfrage legt den bisherigen Gesprächsverlauf erneut vor. Die Zahl ist echt, aber sie misst die Länge der Gespräche, nicht die Arbeit. Ein Umschalter blendet die Cache-Treffer aus; übrig bleiben gut 400 Millionen wirklich neu verarbeitete Tokens. Erst diese Kurve folgt dem, was ich tatsächlich getan habe.
Der Rest ist Handwerk: Ein Gigabyte bei jedem Öffnen zu parsen wäre unbrauchbar, also merkt sich CCW je Datei Größe und Leseposition. Weil solche Protokolle nur angehängt werden, liest der nächste Aufruf ausschließlich die neuen Bytes — aus 3,3 Sekunden werden zehn Millisekunden.
Was ich unterwegs gelernt habe
Ein Web-Terminal ist mehr als xterm.js. Die Bibliothek rendert; alles, was das Arbeiten angenehm macht, liegt drumherum: Reconnect nach Standby, Tastatur-Eigenheiten mobiler Browser, Auswahl vs. Scrollen, Zeichenbreiten von Emoji.
Mobile Tastaturen sind ein eigenes Kapitel. Androids Eingabemethode liefert Text als laufende Komposition — pro Tastendruck das kumulative Wort. Wer das naiv weiterreicht, bekommt jedes Zeichen doppelt. CCW schickt deshalb den Diff zum letzten Stand.
Messen statt raten. Die Fensterliste kostete 39 ms, weil jedes Fenster einen eigenen
capture-pane-Prozess bekam. Gebündelt in einen tmux-Aufruf sind es 3 ms:
1
2
3
# statt N Prozesse: eine Kommandokette, Blöcke per Marker getrennt
tmux capture-pane -p -t @1 -S -12 \; display-message -p CCWMARK \; \
capture-pane -p -t @2 -S -12 \; display-message -p CCWMARK
Der Marker darf übrigens nicht mit - beginnen — sonst hält tmux ihn für eine Option.
„Erfolgreich gesendet” ist keine Zustellbestätigung. Der ärgerlichste Fehler der ganzen Entwicklung: Text abschicken, Bestätigung sehen, und beim Empfänger kommt nichts an. Die Pane stand im tmux-Copy-Mode (nach einem Marken-Sprung oder Scrollen), und der fängt jede Eingabe ab. Die App meldete trotzdem Vollzug, weil sie nur schreibt und nichts prüft. Heute warnt ein Band über dem Terminal, und das Absenden verlässt den Modus vorher von selbst. Die Lehre gilt über CCW hinaus: Wenn eine Meldung nur bestätigt, dass gesendet wurde, verspricht sie mehr, als sie weiß.
Wer die Maus hat, ist nicht die Frage, die man denkt. Im Terminal markiert man mit der Maus — nur reicht der Terminal-Emulator die Maus an das laufende Programm weiter, wenn dieses sie beansprucht. Genau das tun moderne Kommandozeilen-Oberflächen, sobald sie eine Auswahlliste zeigen. Ein Umschalter, der nur die eine Ebene abschaltet (den Multiplexer), greift dann ins Leere: Es entsteht eine Auswahl im Browser, sichtbar blau markiert, während der Emulator selbst gar keine kennt — und alles, was an dieser Auswahl hängen soll, bleibt aus. Noch unangenehmer ist die zweite Hälfte: Erzwingt man die Auswahl über die Modifier, auf die der Emulator hört, dann markiert er zwar — meldet die Maus aber weiter an das Programm. Aus „ich darf markieren” folgt nicht „es wird nichts gemeldet”. Die Bewegung zum Kontextmenü hin reichte, damit das Programm neu zeichnete und die Markierung verschwand. Die Lehre: Bei drei beteiligten Ebenen genügt es nicht zu wissen, wer die Eingabe verarbeitet — man muss auch wissen, wer sie weitergibt.
Kopieren aus einem Terminal ist nicht, was es zu sein scheint. Ein langes Kommando sieht wie eine Zeile aus, ist nach dem Umbruch aber physisch mehrere — beim Kopieren wandern diese Umbrüche als echte Zeilenschaltungen mit, und im Zielterminal führt jede davon ein halbes Kommando aus. CCW fügt sie beim Kopieren wieder zusammen und erkennt sie daran, dass die Zeile die Terminalbreite exakt ausfüllt; echte Codezeilen treffen die Breite so gut wie nie punktgenau, ein mehrzeiliges Skript bleibt also unangetastet.
Der Unterschied zwischen „läuft” und „ist benutzbar” steckt in Kleinigkeiten. Eine leere Zeile über der Statusleiste, damit der Inhalt nicht klebt. Eine Wartedauer am Tab. Ein Kontext-Balken, der auch beim Zurückblättern sichtbar bleibt. Nichts davon ist Architektur — aber es entscheidet, ob man das Werkzeug gern benutzt.
Eine Sperre, die nur den Browser aufhält, hält das Terminal nicht auf. Beim Einfügen kam der Text lange Zeit doppelt an. Die Ursache ist eine Unterscheidung, die man leicht überliest: Der übliche Handgriff, mit dem man die Standardreaktion des Browsers unterbindet, beendet nicht den Weg des Ereignisses. Es lief anschließend weiter zu der versteckten Eingabefläche, die die Terminal-Bibliothek selbst betreibt — und die hat einen eigenen Einfüge-Handler, der nicht nachsieht, ob schon jemand übernommen hat. Zwei Handschläge, einer davon unsichtbar. Der Beweis kam erst aus der Messung: zweimal Text an der Gegenstelle bei nur einem Aufruf der öffentlichen Einfüge-Funktion — die zweite Einfügung geschah im Inneren der Bibliothek und war im eigenen Quelltext gar nicht zu finden. Die Lehre: Wer eine fremde Komponente umschließt, muss wissen, wo deren eigene Verarbeitung ansetzt — sonst ergänzt man sie, statt sie zu ersetzen.
Ein grüner Test kann die falsche Frage stellen. Beide Fehler oben liefen monatelang unter laufenden Tests. Der eine prüfte, dass eingefügter Text ankommt — er kam die ganze Zeit doppelt an. Der andere prüfte, dass eine störende Zeile aus einer Meldung verschwunden ist — dass danach nichts Brauchbares übrig blieb, sah niemand. Beide Behauptungen waren wahr und trotzdem wertlos: Sie beschrieben je eine Hälfte des erwarteten Verhaltens. Eine Prüfung auf Anwesenheit lässt zwei ganze Fehlerklassen durch, „zu oft” und „inhaltsleer”. Seither gehört zu jedem solchen Test die Anzahl, der Inhalt — und die Gegenprobe: Korrektur ausbauen, der Test muss rot werden. Sonst prüft er nichts.
Und: eine Funktion, die niemand findet, gibt es nicht. Die Sprungmarken lagen zwei Ebenen tief in einem Menü, das man beim Suchen gar nicht aufklappt. Die Rückmeldung lautete schlicht „kann ich nicht finden” — ein Befund über die Navigation, nicht über das Feature. Jetzt führen fünf Wege dorthin.
Kurzreferenz
| Kürzel / Element | Wirkung |
|---|---|
Strg/⌘ + F |
Suche im Verlauf (⊞ = alle Fenster) |
Strg/⌘ + K |
Befehls-Palette |
Strg/⌘ + J |
Sprungmarken (inkl. 🔗 Link-Sicht) |
F1 |
Handbuch in der App |
Alt + 1…9 |
direkt ins Fenster mit dieser Nummer |
Enter / Shift+Enter |
senden / neue Zeile in der Eingabezeile |
q |
Blätter-Modus verlassen |
| Wischen ← → | Fenster wechseln (Touch) |
| Long-Press auf Tab | umbenennen |
| Markieren | Aktionsleiste an der Auswahl: kopieren, an Claude, in die Ablage, suchen |
Fazit
Der Nutzen liegt nicht darin, „auch am Handy ein Terminal zu haben” — sondern darin, dass die Sitzung weiterläuft, während man weggeht, und sich meldet, wenn sie etwas braucht. Alles andere in CCW ist Beiwerk um diesen einen Gedanken herum.
Wer selbst so etwas baut: Fang bei der geteilten tmux-Session an, nicht beim Frontend. Der
Rest ergibt sich aus der Frage, was du unterwegs wirklich tun willst — und das ist meist
weniger, als man zuerst denkt: lesen, freigeben, kurz nachsteuern.