kimal3 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,102 +1,134 @@
1
- # kimal3
2
-
3
- **Trag deine KI-Projekte dort ein, wo sie entstehen: im Terminal.**
4
-
5
- Wer mit Claude Code arbeitet, baut echte Dinge — und dokumentiert sie hinterher nicht,
6
- weil das ein zweiter Arbeitsschritt in einem anderen Fenster wäre. `kimal3` schließt
7
- diese Lücke: Am Ende einer Sitzung trägt Claude das fertige Projekt selbst in dein
8
- KImal3-Erfolgsprofil ein. Du gibst es dort nur noch frei.
9
-
10
- > **Voraussetzung: ein KImal3-Konto mit Pro.**
11
- > Ohne Pro meldet sich dieses Werkzeug ab, ohne etwas zu tun.
12
- > Was Pro enthält und wie du es bekommst: **https://app.kimal3.de/pro**
13
-
14
- ## Loslegen
15
-
16
- Einmal pro Rechner:
17
-
18
- ```bash
19
- npm install -g kimal3 # das Werkzeug installieren (setzt Node.js voraus)
20
- kimal3 login # diesen Rechner mit deinem Konto verbinden
21
- kimal3 skill install # Claude beibringen, das Werkzeug zu benutzen
22
- ```
23
-
24
- > **In Claude Code?** Dann lass es einrichten, statt es zu tippen — der Satz zum Kopieren steht
25
- > auf **https://app.kimal3.de/pro**. Nur `kimal3 login` tippst du selbst (dort mit `!`
26
- > davor), weil die Bestätigung im Browser einen Menschen braucht.
27
-
28
- `login` zeigt dir eine Adresse und einen achtstelligen Code. Die Adresse rufst du selbst
29
- auf und bestätigst den Code dort in deinem eingeloggten Konto — so wie bei `gh auth login`.
30
- **Es wird kein Passwort ins Terminal getippt.**
31
-
32
- ## Befehle
33
-
34
- ```
35
- kimal3 Zeigt, ob dieser Rechner angemeldet ist
36
- kimal3 login Diesen Rechner mit deinem Konto verbinden
37
- kimal3 skill install Claude beibringen, dieses Werkzeug zu benutzen — und dir die /k3-Befehle geben
38
- kimal3 arbeitsplatz neu Den ganzen KI-Arbeitsplatz anlegen — Firmenebene und Werkbank
39
- kimal3 werkbank neu Nur eine eigene Werkbank in einer bestehenden Firma anlegen
40
- kimal3 kontext runter Den Firmenkontext holen — CLAUDE.md und die fünf Dateien in kontext/
41
- kimal3 kontext fragen Zeigen, welche Fragen über deine Firma noch unbeantwortet sind
42
- kimal3 kontext hoch Deine lokalen Änderungen an den fünf Kontext-Dateien hochschicken
43
- kimal3 projekt neu … Ein Projekt ins Erfolgsprofil eintragen
44
- kimal3 projekt liste Deine eingetragenen Projekte anzeigen
45
- kimal3 firma daten Die Daten deiner Firma holen, damit Claude sie auswerten kann
46
- kimal3 logout Verbindung wieder aufheben
47
- ```
48
-
49
- Mehr zu einem Befehl: `kimal3 projekt neu --help`
50
-
51
- ## Was eingetragen wird
52
-
53
- Nur, was du eingibst — Titel, Beschreibung, optional ein Link. **Dein Code wird nicht
54
- gelesen, nicht hochgeladen und nicht analysiert.** Das Werkzeug schreibt ausschließlich
55
- über die KImal3-App und hat keinen eigenen Datenbankzugang.
56
-
57
- Eingetragene Projekte sind zunächst **nicht öffentlich**. Sie erscheinen in deinem
58
- Erfolgsprofil, und du entscheidest dort, was sichtbar wird.
59
-
60
- ## Rückgabewerte
61
-
62
- Für Skripte und für Claude selbst:
63
-
64
- | Wert | Bedeutung | Hilft |
65
- |---|---|---|
66
- | `0` | hat geklappt | — |
67
- | `1` | Eingabe unvollständig oder nichts gefunden | Eingabe prüfen |
68
- | `2` | nicht angemeldet | `kimal3 login` |
69
- | `3` | gerade nicht erreichbar | später noch einmal |
70
- | `4` | kein Pro-Konto | erneutes Versuchen hilft **nicht** |
71
-
72
- Der Unterschied zwischen `3` und `4` ist Absicht: Bei `3` lohnt ein zweiter Versuch,
73
- bei `4` nie.
74
-
75
- ## Update-Hinweise
76
-
77
- Steht eine neuere Fassung bereit, schreibt `kimal3` nach dem Befehl eine eingerückte
78
- Zeile — höchstens **einmal am Tag**:
79
-
80
- ```
81
- → kimal3 0.2.0 ist da. npm i -g kimal3@latest && kimal3 skill install
82
- ```
83
-
84
- Sie geht auf **stderr**, nicht auf stdout: `kimal3 firma daten > firma.json` bleibt
85
- damit sauberes JSON. Sie **ändert den Rückgabewert nicht** — eine `0` bleibt eine `0`.
86
- Und `kimal3` **installiert nichts von selbst**; der Befehl steht in der Zeile, ausführen
87
- tust du ihn.
88
-
89
- Dasselbe gilt für die Skill-Dateien: Sind die in `~/.claude/skills/kimal3/` älter als
90
- das installierte Werkzeug, sagt `kimal3` es — dafür braucht es kein Netz.
91
-
92
- ## Voraussetzungen
93
-
94
- Node.js 20 oder neuer. Sonst nichts — das Werkzeug bringt **keine fremden Bibliotheken**
95
- mit.
96
-
97
- ## Lizenz
98
-
99
- Proprietär. Die Nutzung ist Inhabern eines gültigen KImal3-Pro-Kontos gestattet;
100
- Weitergabe, Veränderung und Weiterverwendung des Codes sind nicht erlaubt.
101
-
102
- © KImal3 · https://kimal3.de
1
+ # kimal3
2
+
3
+ **Trag deine KI-Projekte dort ein, wo sie entstehen: im Terminal.**
4
+
5
+ Wer mit Claude Code arbeitet, baut echte Dinge — und dokumentiert sie hinterher nicht,
6
+ weil das ein zweiter Arbeitsschritt in einem anderen Fenster wäre. `kimal3` schließt
7
+ diese Lücke: Am Ende einer Sitzung trägt Claude das fertige Projekt selbst in dein
8
+ KImal3-Erfolgsprofil ein. Du gibst es dort nur noch frei.
9
+
10
+ > **Voraussetzung: ein KImal3-Konto mit Pro.**
11
+ > Ohne Pro meldet sich dieses Werkzeug ab, ohne etwas zu tun.
12
+ > Was Pro enthält und wie du es bekommst: **https://app.kimal3.de/pro**
13
+
14
+ ## Loslegen
15
+
16
+ Einmal pro Rechner:
17
+
18
+ ```bash
19
+ npm install -g kimal3 # das Werkzeug installieren (setzt Node.js voraus)
20
+ kimal3 login # diesen Rechner mit deinem Konto verbinden
21
+ kimal3 skill install # Claude beibringen, das Werkzeug zu benutzen
22
+ ```
23
+
24
+ > **In Claude Code?** Dann lass es einrichten, statt es zu tippen — der Satz zum Kopieren steht
25
+ > auf **https://app.kimal3.de/pro**. Nur `kimal3 login` tippst du selbst (dort mit `!`
26
+ > davor), weil die Bestätigung im Browser einen Menschen braucht.
27
+
28
+ `login` zeigt dir eine Adresse und einen achtstelligen Code. Die Adresse rufst du selbst
29
+ auf und bestätigst den Code dort in deinem eingeloggten Konto — so wie bei `gh auth login`.
30
+ **Es wird kein Passwort ins Terminal getippt.**
31
+
32
+ ## Befehle
33
+
34
+ ```
35
+ kimal3 Zeigt, ob dieser Rechner angemeldet ist
36
+ kimal3 login Diesen Rechner mit deinem Konto verbinden
37
+ kimal3 skill install Claude beibringen, dieses Werkzeug zu benutzen — und dir die /k3-Befehle geben
38
+ kimal3 arbeitsplatz neu Den ganzen KI-Arbeitsplatz anlegen — Firmenebene und Werkbank
39
+ kimal3 werkbank neu Nur eine eigene Werkbank in einer bestehenden Firma anlegen
40
+ kimal3 kontext runter Den Firmenkontext holen — CLAUDE.md und die fünf Dateien in kontext/
41
+ kimal3 kontext fragen Zeigen, welche Fragen über deine Firma noch unbeantwortet sind
42
+ kimal3 kontext hoch Deine lokalen Änderungen an den fünf Kontext-Dateien hochschicken
43
+ kimal3 projekt neu … Ein Projekt ins Erfolgsprofil eintragen
44
+ kimal3 projekt liste Deine eingetragenen Projekte anzeigen
45
+ kimal3 firma daten Die Daten deiner Firma holen, damit Claude sie auswerten kann
46
+ kimal3 wache Läuft von selbst, einmal am Tag: prüft Zugang, Nachricht, Version, Skill
47
+ kimal3 wache aus Die tägliche Prüfung abschalten — sie sagt dann nichts mehr
48
+ kimal3 wache an Sie wieder einschalten
49
+ kimal3 logout Verbindung wieder aufheben
50
+ ```
51
+
52
+ Mehr zu einem Befehl: `kimal3 projekt neu --help`
53
+
54
+ ## Was eingetragen wird
55
+
56
+ Nur, was du eingibst — Titel, Beschreibung, optional ein Link. **Dein Code wird nicht
57
+ gelesen, nicht hochgeladen und nicht analysiert.** Das Werkzeug schreibt ausschließlich
58
+ über die KImal3-App und hat keinen eigenen Datenbankzugang.
59
+
60
+ Eingetragene Projekte sind zunächst **nicht öffentlich**. Sie erscheinen in deinem
61
+ Erfolgsprofil, und du entscheidest dort, was sichtbar wird.
62
+
63
+ ## Rückgabewerte
64
+
65
+ Für Skripte und für Claude selbst:
66
+
67
+ | Wert | Bedeutung | Hilft |
68
+ |---|---|---|
69
+ | `0` | hat geklappt | — |
70
+ | `1` | Eingabe unvollständig, nichts gefunden — oder deine Claude-Einstellungen sind nicht lesbar | die Meldung lesen, sie nennt den Fall |
71
+ | `2` | nicht angemeldet | `kimal3 login` |
72
+ | `3` | gerade nicht erreichbar | später noch einmal |
73
+ | `4` | kein Pro-Konto | erneutes Versuchen hilft **nicht** |
74
+
75
+ Der Unterschied zwischen `3` und `4` ist Absicht: Bei `3` lohnt ein zweiter Versuch,
76
+ bei `4` nie.
77
+
78
+ ## Update-Hinweise
79
+
80
+ Steht eine neuere Fassung bereit, schreibt `kimal3` nach dem Befehl eine eingerückte
81
+ Zeile — höchstens **einmal am Tag**:
82
+
83
+ ```
84
+ → kimal3 0.2.0 ist da. npm i -g kimal3@latest && kimal3 skill install
85
+ ```
86
+
87
+ Sie geht auf **stderr**, nicht auf stdout: `kimal3 firma daten > firma.json` bleibt
88
+ damit sauberes JSON. Sie **ändert den Rückgabewert nicht** — eine `0` bleibt eine `0`.
89
+ Und `kimal3` **installiert nichts von selbst**; der Befehl steht in der Zeile, ausführen
90
+ tust du ihn.
91
+
92
+ Dasselbe gilt für die Skill-Dateien: Sind die in `~/.claude/skills/kimal3/` älter als
93
+ das installierte Werkzeug, sagt `kimal3` es — dafür braucht es kein Netz.
94
+
95
+ ## Die tägliche Prüfung — und wie du sie abstellst
96
+
97
+ `kimal3 skill install` trägt eine Zeile in deine Claude-Einstellungen ein
98
+ (`~/.claude/settings.json`). Sie sorgt dafür, dass beim Start von Claude einmal am Tag
99
+ `kimal3 wache` läuft. Die Prüfung sieht nach, ob dein Zugang noch gilt, ob eine neuere
100
+ Fassung bereitsteht, ob deine `/k3`-Befehle veraltet sind und ob eine Nachricht von uns
101
+ anliegt.
102
+
103
+ **Sie meldet sich nur, wenn wirklich etwas ansteht.** Ist alles in Ordnung, sagt sie nichts.
104
+
105
+ Willst du sie nicht:
106
+
107
+ ```
108
+ kimal3 wache aus # ab jetzt still
109
+ kimal3 wache an # doch wieder
110
+ ```
111
+
112
+ Nach `kimal3 wache aus` wird **nichts mehr an unseren Server geschickt** — die Prüfung
113
+ steigt aus, bevor sie irgendetwas fragt. Gemessen: mit Prüfung rund 0,9 Sekunden, ohne
114
+ rund 0,19 Sekunden, und in den 0,19 Sekunden verlässt kein Byte deinen Rechner.
115
+
116
+ Die Zeile in den Einstellungen bleibt dabei stehen, damit `kimal3 wache an` sofort wirkt
117
+ und wir deine Einstellungsdatei nicht noch einmal anfassen müssen. Es startet also weiter
118
+ ein kleines Programm von uns, das nach rund 0,19 Sekunden wieder aufhört.
119
+
120
+ **Soll auch das nicht mehr passieren**, nimm die Zeile selbst heraus: In
121
+ `~/.claude/settings.json` steht unter `hooks` → `SessionStart` ein Eintrag mit dem Befehl
122
+ `kimal3 wache`. Lösch diesen einen Eintrag — den Rest der Datei lass, wie er ist.
123
+
124
+ ## Voraussetzungen
125
+
126
+ Node.js 20 oder neuer. Sonst nichts — das Werkzeug bringt **keine fremden Bibliotheken**
127
+ mit.
128
+
129
+ ## Lizenz
130
+
131
+ Proprietär. Die Nutzung ist Inhabern eines gültigen KImal3-Pro-Kontos gestattet;
132
+ Weitergabe, Veränderung und Weiterverwendung des Codes sind nicht erlaubt.
133
+
134
+ © KImal3 · https://kimal3.de
package/dist/anmeldung.js CHANGED
@@ -5,7 +5,7 @@ import { join } from "node:path";
5
5
  * Wo die App wohnt. Ueberschreibbar fuers lokale Entwickeln:
6
6
  * KIMAL3_URL=http://localhost:3000 kimal3 projekt liste
7
7
  */
8
- export const BASIS_URL = process.env.KIMAL3_URL ?? "https://kimal3-app.vercel.app";
8
+ export const BASIS_URL = process.env.KIMAL3_URL ?? "https://app.kimal3.de";
9
9
  const ORDNER = join(homedir(), ".kimal3");
10
10
  const DATEI = join(ORDNER, "anmeldung.json");
11
11
  export function leseAnmeldung() {
@@ -2,6 +2,8 @@ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync,
2
2
  import { homedir } from "node:os";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { join } from "node:path";
5
+ import { einstellungenDatei, trageWacheEin } from "../einstellungen-schreiben.js";
6
+ import { merkeSitzung, sitzungJetzt } from "../wache-schalter.js";
5
7
  import { STEMPEL_MARKE } from "../nachricht.js";
6
8
  import { eigeneVersion } from "../version.js";
7
9
  export const VERALTETE_BEFEHLE = [
@@ -255,6 +257,54 @@ export async function skillInstall() {
255
257
  *Ein Hinweis auf etwas, das es nicht gibt, sieht aus wie ein Fehler des
256
258
  Menschen.*
257
259
  */
260
+ /*
261
+ Die Wache in die Claude-Einstellungen eintragen.
262
+
263
+ 🔑 WARUM HIER UND NICHT IN EINEM EIGENEN BEFEHL: Der Mensch tippt
264
+ `kimal3 skill install` ohnehin -- nach dem Kauf, nach jedem Update. Ein
265
+ zweiter Befehl waere ein zweiter Schritt, den man vergisst, und die Wache
266
+ ist genau fuer die Menschen gebaut, die nicht daran denken.
267
+
268
+ 🔴 EIN FEHLSCHLAG HIER DARF DEN LAUF NICHT ALS ERFOLG ENDEN LASSEN.
269
+ Das Skill steht dann zwar -- aber die taegliche Pruefung laeuft nicht, und
270
+ ein stiller Ausfall genau dort ist der teuerste Fall dieser Scheibe.
271
+ */
272
+ const wache = trageWacheEin();
273
+ if (wache.art === "eingetragen" || wache.art === "stand-schon") {
274
+ /*
275
+ 🔴 WARUM DAS HIER STEHEN MUSS: Der Eintrag greift erst beim
276
+ NAECHSTEN Start von Claude -- in der laufenden Sitzung hat die Wache
277
+ also noch nie nachgesehen, voellig zu Recht. Ohne diesen Stempel
278
+ bekaeme der Mensch unmittelbar nach dem Einrichten die Meldung "deine
279
+ taegliche Pruefung laeuft nicht" -- ausgerechnet in dem Augenblick, in
280
+ dem er sie gerade eingerichtet hat. *Der erste Eindruck waere ein
281
+ Fehlalarm.*
282
+ */
283
+ merkeSitzung(sitzungJetzt());
284
+ }
285
+ if (wache.art === "eingetragen") {
286
+ console.log(" Die taegliche kimal3-Pruefung ist eingetragen. Sie meldet sich nur, wenn etwas ansteht.");
287
+ }
288
+ else if (wache.art === "stand-schon") {
289
+ console.log(" Die taegliche kimal3-Pruefung steht schon in deinen Einstellungen.");
290
+ }
291
+ else if (wache.art === "abgelehnt") {
292
+ console.error("");
293
+ console.error(" Die taegliche kimal3-Pruefung konnte NICHT eingetragen werden.");
294
+ console.error(` Deine Claude-Einstellungen sind nicht lesbar: ${einstellungenDatei()}`);
295
+ console.error(wache.grund === "kein-gueltiges-json"
296
+ ? " Die Datei ist kein gueltiges JSON. Sie wurde NICHT angefasst."
297
+ : " Die Datei hat eine Form, die wir nicht verstehen. Sie wurde NICHT angefasst.");
298
+ console.error(" Wiederholen hilft nicht -- erst die Datei in Ordnung bringen.");
299
+ return 1;
300
+ }
301
+ else if (wache.art === "gescheitert") {
302
+ console.error("");
303
+ console.error(" Die taegliche kimal3-Pruefung konnte NICHT eingetragen werden.");
304
+ console.error(` ${einstellungenDatei()}: ${wache.grund}`);
305
+ console.error(" Deine bisherigen Einstellungen sind unveraendert.");
306
+ return 1;
307
+ }
258
308
  if (geschrieben.includes("k3")) {
259
309
  console.log(" Tipp /k3 in Claude Code. (Taucht nichts auf, einmal neu starten.)");
260
310
  }
@@ -0,0 +1,185 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { BASIS_URL, leseAnmeldung } from "../anmeldung.js";
3
+ import { lokalerKontextStandJetzt } from "../api.js";
4
+ import { leseGesehen, merkeGesehen } from "../gesehen.js";
5
+ import { heuteAus, sollZeigen, skillZustand } from "../nachricht.js";
6
+ import { eigeneVersion } from "../version.js";
7
+ import { leseWacheDatei, merkeLauf, merkeSitzung, setzeWache, sitzungJetzt, sollHeuteLaufen, } from "../wache-schalter.js";
8
+ import { wacheAntwortAusHttp, wacheZeilen } from "../wache-rechnung.js";
9
+ import { skillDatei } from "./skill-install.js";
10
+ /**
11
+ * 🔴 BEWUSSTE VORGABE, KEINE MESSUNG (Aufgabe 0b ist vertagt). Der bestehende
12
+ * Update-Kanal (`api.ts`) nimmt zwei Sekunden -- die Wache laeuft aber bei
13
+ * JEDEM Claude-Start mit, nicht bei einem bewusst getippten Befehl, und
14
+ * zweimal 2000 ms parallel waeren dort schon eher spuerbar. 1500 ms ist ein
15
+ * Bauchwert; die echte Zahl gehoert in eine spaetere Messung.
16
+ *
17
+ * Dieselbe Zahl gilt fuer BEIDE Anrufe (npm und unser Server) -- die Wache
18
+ * darf beim Start von Claude in Summe nicht laenger haengen als ein Anruf.
19
+ */
20
+ const ZEITGRENZE_MS = 1500;
21
+ /**
22
+ * Die neueste Fassung -- von der oeffentlichen npm-Registry, nicht von uns.
23
+ *
24
+ * 🔑 ZWEI GRUENDE, WARUM SIE NICHT UEBER UNSEREN SERVER GEHT: Sie stimmt
25
+ * immer, ohne dass wir sie pflegen -- und sie funktioniert auch dann, wenn
26
+ * unser eigener Server gerade nicht erreichbar ist.
27
+ *
28
+ * 🔴 `null` heisst "npm hat nicht geantwortet", NICHT "es gibt nichts Neues".
29
+ * Der Aufrufer schweigt dann.
30
+ */
31
+ async function neuesteVonNpm(zeitgrenzeMs) {
32
+ try {
33
+ const antwort = await fetch("https://registry.npmjs.org/kimal3/latest", {
34
+ signal: AbortSignal.timeout(zeitgrenzeMs),
35
+ });
36
+ if (!antwort.ok)
37
+ return null;
38
+ const koerper = await antwort.json();
39
+ if (koerper && typeof koerper === "object" && "version" in koerper) {
40
+ const v = koerper.version;
41
+ if (typeof v === "string")
42
+ return v;
43
+ }
44
+ }
45
+ catch {
46
+ // Kein Netz, Zeitgrenze, Muell -- alles dasselbe: wir wissen es nicht.
47
+ }
48
+ return null;
49
+ }
50
+ /**
51
+ * Sind die Skill-Dateien aelter als die installierte CLI? Braucht kein Netz --
52
+ * `skillZustand` ist bereits gebaut (Aufgabe 3) und von `check:nachrichten`
53
+ * gedeckt, hier wird nur der Stempel von der Platte gelesen und hindurch-
54
+ * gereicht.
55
+ *
56
+ * ⚠️ Ohne eigene Versionsnummer laesst sich nichts vergleichen -- dann lieber
57
+ * schweigen (`false`) als eine Vermutung anzuzeigen.
58
+ */
59
+ function skillIstVeraltet() {
60
+ const eigene = eigeneVersion();
61
+ if (eigene === null)
62
+ return false;
63
+ const datei = skillDatei();
64
+ const inhalt = existsSync(datei) ? readFileSync(datei, "utf8") : null;
65
+ return skillZustand(inhalt, eigene) === "veraltet";
66
+ }
67
+ /**
68
+ * Der eine Anruf beim eigenen Server -- Zugang pruefen, Nachricht abholen.
69
+ *
70
+ * 🔑 KEINE gemeinsame Anrufhilfe aus `api.ts`: Die Wache ist ein EIGENER,
71
+ * zweiter Kanal (Spec US-1) und soll nicht an den Nebeneffekten haengen, die
72
+ * jene Hilfsfunktion fuer die interaktiven Befehle mitbringt (den
73
+ * Kontext-Stand-Kopf lesen, den modul-globalen Update-Hinweis setzen). Ein
74
+ * eigener, kleiner Aufruf mit eigener Zeitgrenze ist hier klarer als ein
75
+ * Werkzeug fuer einen anderen Weg zweitzuverwenden.
76
+ *
77
+ * 🔴 DIE FILTERUNG GEHOERT HIERHIN, NICHT IN `wacheZeilen`: Die Route gibt die
78
+ * Nachricht immer VOLLSTAENDIG zurueck (`id`, `text`, `gilt_unter_version`).
79
+ * Ob sie ueberhaupt gezeigt wird, entscheidet `sollZeigen` -- ohne sie saehe
80
+ * jeder Kunde jede Nachricht, auch der auf der neuesten Fassung.
81
+ *
82
+ * `merkeGesehen` wird bewusst NICHT hier aufgerufen, sondern erst in `wache()`,
83
+ * nachdem die Zeile wirklich gedruckt wurde -- dieselbe Reihenfolge wie beim
84
+ * bestehenden Update-Kanal in `kimal3.ts` ("erst schreiben, dann vermerken").
85
+ */
86
+ async function frageServer(schluessel, heute) {
87
+ try {
88
+ const antwort = await fetch(`${BASIS_URL}/api/cli/wache`, {
89
+ method: "POST",
90
+ headers: { Authorization: `Bearer ${schluessel}` },
91
+ signal: AbortSignal.timeout(ZEITGRENZE_MS),
92
+ });
93
+ const koerper = await antwort.json().catch(() => null);
94
+ const { zugangGilt, hinweis } = wacheAntwortAusHttp(antwort.status, koerper);
95
+ if (!hinweis)
96
+ return { zugangGilt, hinweis: null };
97
+ const eigene = eigeneVersion();
98
+ if (eigene === null)
99
+ return { zugangGilt, hinweis: null };
100
+ const standJetzt = lokalerKontextStandJetzt();
101
+ if (!sollZeigen(hinweis, eigene, leseGesehen(), heute, standJetzt)) {
102
+ return { zugangGilt, hinweis: null };
103
+ }
104
+ return { zugangGilt, hinweis };
105
+ }
106
+ catch {
107
+ // Kein Netz, Zeitgrenze, Muell -- wie beim Server-Aussetzer: im Zweifel
108
+ // gilt der Zugang, und es liegt keine Nachricht an.
109
+ return { zugangGilt: true, hinweis: null };
110
+ }
111
+ }
112
+ /**
113
+ * 🔴 GIBT IMMER 0 ZURUECK, auch im Fehlerfall. Ein Nebenschauplatz darf den
114
+ * Start von Claude nicht umwerfen -- dieselbe Regel wie bei der
115
+ * Abhaengigkeits-Wache.
116
+ *
117
+ * 🔴 REIHENFOLGE DER AUSSTIEGE, jede spart die naechste ein:
118
+ * 1. abgeschaltet -> still, kein Netz
119
+ * 2. keine Anmeldedatei -> still, KEIN NETZAUFRUF (US-5)
120
+ * 3. heute schon gelaufen -> still, kein Netz
121
+ * Erst danach wird ueberhaupt telefoniert.
122
+ */
123
+ export async function wache() {
124
+ try {
125
+ const heute = heuteAus(new Date());
126
+ /*
127
+ 🔴 GANZ VORNE, VOR JEDEM AUSSTIEG. Dieser Stempel beantwortet
128
+ nicht "war heute etwas zu melden", sondern "hat ueberhaupt jemand fuer
129
+ diese Claude-Sitzung nachgesehen" -- und genau das ist auch dann wahr,
130
+ wenn die Wache gleich still aussteigt, weil sie heute schon lief.
131
+
132
+ Ohne diese Zeile bekaeme die ZWEITE Claude-Sitzung eines Tages die
133
+ Meldung "deine Pruefung laeuft nicht", obwohl sie tadellos laeuft: Die
134
+ Wache steigt dort bei "heute schon gelaufen" aus, und der Merkzettel
135
+ truege noch die Nummer der ersten Sitzung. *Ein Fehlalarm an genau der
136
+ Stelle, an der die Meldung Vertrauen braucht.*
137
+ */
138
+ merkeSitzung(sitzungJetzt());
139
+ if (!sollHeuteLaufen(leseWacheDatei(), heute))
140
+ return 0;
141
+ const anmeldung = leseAnmeldung();
142
+ if (anmeldung === null)
143
+ return 0;
144
+ const [vomServer, vonNpm] = await Promise.all([
145
+ frageServer(anmeldung.schluessel, heute),
146
+ neuesteVonNpm(ZEITGRENZE_MS),
147
+ ]);
148
+ const zeilen = wacheZeilen({
149
+ zugangGilt: vomServer.zugangGilt,
150
+ nachricht: vomServer.hinweis?.text ?? null,
151
+ eigeneVersion: eigeneVersion(),
152
+ neuesteVersion: vonNpm,
153
+ skillVeraltet: skillIstVeraltet(),
154
+ });
155
+ // 🔴 VOR der Ausgabe -- sonst spricht die Wache bei einem Fehler in der
156
+ // Ausgabe morgen wieder und uebermorgen wieder.
157
+ merkeLauf(heute);
158
+ if (zeilen.length === 0)
159
+ return 0; // 🔴 Stille heisst WIRKLICH nichts --
160
+ // auch keine leere Kopfzeile.
161
+ console.log("🔑 kimal3");
162
+ for (const z of zeilen)
163
+ console.log(`• ${z}`);
164
+ // Erst NACH dem Drucken vermerken, dass die Nachricht heute schon lief --
165
+ // dieselbe Reihenfolge wie beim Update-Kanal in kimal3.ts.
166
+ if (vomServer.hinweis)
167
+ merkeGesehen(vomServer.hinweis.id, heute);
168
+ return 0;
169
+ }
170
+ catch {
171
+ return 0;
172
+ }
173
+ }
174
+ /**
175
+ * Der Schalter: `kimal3 wache aus` / `kimal3 wache an`.
176
+ *
177
+ * 🔑 EIGENE, DRUCKENDE HUELLE UM `setzeWache`: Die reine Datei-Rechnung in
178
+ * `wache-schalter.ts` gibt weder eine Zahl zurueck noch sagt sie dem
179
+ * Menschen etwas -- der Verteiler in `kimal3.ts` braucht beides.
180
+ */
181
+ export function wacheSchalten(an) {
182
+ setzeWache(an);
183
+ console.log(an ? "✓ Wache eingeschaltet." : "✓ Wache ausgeschaltet.");
184
+ return 0;
185
+ }
@@ -177,6 +177,26 @@ export const BEFEHLE = [
177
177
  "⚠️ Es sind personenbezogene Daten von Kolleginnen und Kollegen. Für den Einsatz in einer echten Firma braucht es ein kommerzielles Claude-Konto mit Auftragsverarbeitungsvertrag; werden Leistungen von Beschäftigten ausgewertet, ist das in Deutschland mitbestimmungspflichtig.",
178
178
  ],
179
179
  },
180
+ /*
181
+ 🔴 Dieser Eintrag steht bewusst NICHT im "Weg, den ein Mensch geht" --
182
+ niemand muss ihn selbst tippen. Er wird beim Start von Claude Code einmal
183
+ am Tag von selbst aufgerufen (Aufgabe 8) und steht hier trotzdem, weil
184
+ `tippen()` in `wache-rechnung.ts` seine `!`-Schreibweise braucht, wenn die
185
+ Wache selbst einmal auf ihren eigenen Befehl verweisen muss.
186
+ */
187
+ {
188
+ befehl: "kimal3 wache",
189
+ beschreibung: "Nachsehen, ob dein Zugang gilt und ob etwas anliegt",
190
+ wer: "du",
191
+ rhythmus: "einmal am Tag, von selbst",
192
+ tippen: "! kimal3 wache",
193
+ terminal: "kimal3 wache",
194
+ details: [
195
+ "Läuft von selbst, einmal am Tag beim Start von Claude Code — du musst ihn nicht selbst aufrufen.",
196
+ "Bleibt sie still, ist alles in Ordnung: keine Zeile heißt kein Problem. Sie meldet sich nur, wenn dein Zugang abgelaufen ist, eine Nachricht anliegt, eine neue Version bereitsteht oder deine Skill-Dateien veraltet sind.",
197
+ "Erreicht sie unseren Server nicht, schweigt sie zu Zugang und Nachricht — meldet aber trotzdem eine neue Version, falls npm eine kennt. Die beiden Fragen hängen nicht aneinander.",
198
+ ],
199
+ },
180
200
  {
181
201
  befehl: "kimal3 logout",
182
202
  beschreibung: "Verbindung wieder aufheben",
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Traegt die Wache in die Claude-Einstellungen eines Kunden ein -- ohne
3
+ * kaputtzumachen, was dort sonst steht.
4
+ *
5
+ * 🔴 DIESE DATEI FASST KEINE DATEI AN. Sie nimmt Text und gibt Text zurueck.
6
+ * Nur so laesst sie sich pruefen: In `~/.claude/settings.json` stehen die
7
+ * Startbefehle des Menschen, sein Statusbalken, seine Berechtigungen und seine
8
+ * Modellwahl -- ein Pruefgeraet, das man kaputt machen kann, ist der
9
+ * Arbeitsplatz des Menschen. Das Lesen und Schreiben wohnt in
10
+ * `befehle/skill-install.ts`, samt Sicherungskopie und Zurueckkontrolle.
11
+ */
12
+ /**
13
+ * Der Befehl, der in die Einstellungen eingetragen wird.
14
+ *
15
+ * 🔴 NUR EIN BEFEHL, keine Kommandozeilen-Zeichen -- kein `||`, kein `2>/dev/null`.
16
+ * Das sind Bash-Zeichen, und ob auf dem Kundenrechner eine Bash laeuft, wissen
17
+ * wir nicht. Die Fehlerbehandlung gehoert ins Programm, nicht in die Zeile.
18
+ * *Ein Befehl, der beim Erbauer laeuft, ist keine Aussage ueber den Rechner
19
+ * des Kunden.*
20
+ */
21
+ export const WACHE_BEFEHL = "kimal3 wache";
22
+ /**
23
+ * Wie lange Claude auf die Wache wartet, in Sekunden.
24
+ *
25
+ * 🔑 Die Wache fragt mit 1,5 Sekunden Zeitgrenze bei uns und bei npm nach --
26
+ * zehn Sekunden sind also weit jenseits des Normalfalls und trotzdem eine
27
+ * harte Obergrenze. *Ein Startbefehl, der haengt, haelt den Menschen von
28
+ * seiner Arbeit ab -- und ausgerechnet unserer soll ihm helfen.*
29
+ */
30
+ export const WACHE_ZEITGRENZE = 10;
31
+ function istObjekt(wert) {
32
+ return typeof wert === "object" && wert !== null && !Array.isArray(wert);
33
+ }
34
+ /**
35
+ * Ist das ein Eintrag, den WIR frueher einmal geschrieben haben?
36
+ *
37
+ * 🔴 WARUM DAS NOETIG IST: Aendert sich der Befehlstext spaeter (ein Argument
38
+ * kommt dazu, der Name wird laenger), wuerde ein blosses Anhaengen den alten
39
+ * Eintrag stehen lassen. Der Kunde riefe dann bei jedem Claude-Start zwei
40
+ * Wachen auf, und die alte zeigte auf etwas, das es nicht mehr gibt. Weder
41
+ * Compiler noch Waechter faenden das -- es sieht ja aus wie ein fremder
42
+ * Eintrag, den man hoeflich in Ruhe laesst.
43
+ */
44
+ function istUnserer(eintrag) {
45
+ if (!istObjekt(eintrag))
46
+ return false;
47
+ return typeof eintrag.command === "string" && eintrag.command.includes(WACHE_BEFEHL);
48
+ }
49
+ export function mischeEinstellungen(alt, befehl) {
50
+ /*
51
+ Eine fehlende und eine leere Datei sind dasselbe: Es gibt nichts zu
52
+ bewahren, also legen wir an. `null` kommt vom Aufrufer, wenn die Datei gar
53
+ nicht existiert; ein leerer Text entsteht, wenn ein frueherer Lauf sie
54
+ angelegt, aber nicht mehr befuellt hat.
55
+ */
56
+ const leer = alt === null || alt.trim() === "";
57
+ let gelesen = {};
58
+ if (!leer) {
59
+ try {
60
+ gelesen = JSON.parse(alt);
61
+ }
62
+ catch {
63
+ /*
64
+ 🔴 HIER WIRD NICHTS GESCHRIEBEN. Wer eine unlesbare Einstellungsdatei
65
+ ueberschreibt, wirft weg, was der Mensch dort stehen hat -- und
66
+ ausgerechnet der Fall, in dem sie schon beschaedigt ist, ist der, in
67
+ dem er sie am dringendsten braucht. Die Absage geht nach oben, der
68
+ Mensch erfaehrt sie.
69
+ */
70
+ return { art: "abgelehnt", grund: "kein-gueltiges-json" };
71
+ }
72
+ }
73
+ // Eine Liste oder eine blosse Zahl ist gueltiges JSON, aber keine
74
+ // Einstellungsdatei. Das ist ein DRITTER Fall, nicht der zweite.
75
+ if (!istObjekt(gelesen))
76
+ return { art: "abgelehnt", grund: "fremde-form" };
77
+ const vorher = JSON.stringify(gelesen);
78
+ const hooks = gelesen.hooks === undefined ? {} : gelesen.hooks;
79
+ if (!istObjekt(hooks))
80
+ return { art: "abgelehnt", grund: "fremde-form" };
81
+ const bisher = hooks.SessionStart === undefined ? [] : hooks.SessionStart;
82
+ if (!Array.isArray(bisher))
83
+ return { art: "abgelehnt", grund: "fremde-form" };
84
+ /*
85
+ 🔴 GEFILTERT WIRD AUF DER INNEREN EBENE, nicht auf der aeusseren.
86
+ Ein Eintrag unter `SessionStart` ist eine Gruppe, die MEHRERE Befehle
87
+ tragen kann. Wer die ganze Gruppe wegwirft, weil ein Befehl darin unserer
88
+ ist, loescht die fremden Befehle daneben gleich mit -- und nichts sagt es
89
+ dem Menschen. Uebrig bleibt die Gruppe nur, wenn nach dem Filtern noch
90
+ etwas darin steht.
91
+ */
92
+ const fremde = [];
93
+ for (const gruppe of bisher) {
94
+ if (!istObjekt(gruppe) || !Array.isArray(gruppe.hooks)) {
95
+ // Etwas, das wir nicht verstehen, bleibt unveraendert stehen.
96
+ fremde.push(gruppe);
97
+ continue;
98
+ }
99
+ const behalten = gruppe.hooks.filter((h) => !istUnserer(h));
100
+ if (behalten.length === 0)
101
+ continue;
102
+ fremde.push({ ...gruppe, hooks: behalten });
103
+ }
104
+ const neu = {
105
+ ...gelesen,
106
+ hooks: {
107
+ ...hooks,
108
+ SessionStart: [
109
+ ...fremde,
110
+ { hooks: [{ type: "command", command: befehl, timeout: WACHE_ZEITGRENZE }] },
111
+ ],
112
+ },
113
+ };
114
+ /*
115
+ Steht danach genau dasselbe da wie vorher, gibt es nichts zu schreiben.
116
+ 🔑 Warum das mehr ist als Sparsamkeit: Jedes Schreiben ist eine Gelegenheit,
117
+ die Datei zu beschaedigen. Ein Lauf, der nichts zu tun hat, soll sie auch
118
+ nicht anfassen.
119
+ */
120
+ const nachher = JSON.stringify(neu);
121
+ if (!leer && nachher === vorher)
122
+ return { art: "unveraendert" };
123
+ return { art: "geschrieben", inhalt: JSON.stringify(neu, null, 2) + "\n" };
124
+ }