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.
@@ -0,0 +1,88 @@
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { WACHE_BEFEHL, mischeEinstellungen } from "./einstellungen-mischen.js";
5
+ export function einstellungenDatei() {
6
+ return join(homedir(), ".claude", "settings.json");
7
+ }
8
+ export function sicherungsDatei() {
9
+ return einstellungenDatei() + ".kimal3-sicherung";
10
+ }
11
+ export function trageWacheEin() {
12
+ const datei = einstellungenDatei();
13
+ const gabEsSchon = existsSync(datei);
14
+ // --- Schritt 1: lesen ---
15
+ let alt = null;
16
+ if (gabEsSchon) {
17
+ try {
18
+ alt = readFileSync(datei, "utf8");
19
+ }
20
+ catch (e) {
21
+ /*
22
+ 🔴 NICHT als "keine Datei" behandeln. Eine Datei, die es gibt und die
23
+ wir nicht lesen koennen, ist etwas anderes als eine, die es nicht gibt
24
+ -- wer beide gleich behandelt, legt eine neue an und der Mensch
25
+ verliert alles, was drinstand.
26
+ */
27
+ return { art: "gescheitert", grund: `nicht lesbar (${e.message})` };
28
+ }
29
+ }
30
+ const ergebnis = mischeEinstellungen(alt, WACHE_BEFEHL);
31
+ if (ergebnis.art === "abgelehnt")
32
+ return { art: "abgelehnt", grund: ergebnis.grund };
33
+ if (ergebnis.art === "unveraendert")
34
+ return { art: "stand-schon" };
35
+ // --- Schritt 2: Sicherungskopie ---
36
+ if (gabEsSchon) {
37
+ try {
38
+ copyFileSync(datei, sicherungsDatei());
39
+ }
40
+ catch (e) {
41
+ /*
42
+ 🔴 OHNE SICHERUNG WIRD NICHT GESCHRIEBEN. Der ganze Sinn von Schritt 4
43
+ ist, im Fehlerfall zurueckzukoennen -- ohne Kopie gibt es kein Zurueck,
44
+ und dann ist Schreiben die schlechtere Wahl als Nichtstun.
45
+ */
46
+ return { art: "gescheitert", grund: `keine Sicherungskopie moeglich (${e.message})` };
47
+ }
48
+ }
49
+ // --- Schritt 3: schreiben ---
50
+ try {
51
+ mkdirSync(join(homedir(), ".claude"), { recursive: true });
52
+ writeFileSync(datei, ergebnis.inhalt, "utf8");
53
+ }
54
+ catch (e) {
55
+ zurueck(gabEsSchon);
56
+ return { art: "gescheitert", grund: `nicht schreibbar (${e.message})` };
57
+ }
58
+ // --- Schritt 4: zuruecklesen und erneut pruefen ---
59
+ try {
60
+ const zurueckgelesen = readFileSync(datei, "utf8");
61
+ const geparst = JSON.parse(zurueckgelesen);
62
+ if (!zurueckgelesen.includes(WACHE_BEFEHL) || geparst === null) {
63
+ throw new Error("unser Eintrag steht nicht in der zurueckgelesenen Datei");
64
+ }
65
+ }
66
+ catch (e) {
67
+ zurueck(gabEsSchon);
68
+ return { art: "gescheitert", grund: `Kontrolle nach dem Schreiben fehlgeschlagen (${e.message})` };
69
+ }
70
+ return { art: "eingetragen" };
71
+ }
72
+ /**
73
+ * Die Sicherung zurueckspielen.
74
+ *
75
+ * ⚠️ Gab es vorher KEINE Datei, gibt es auch nichts zurueckzuspielen -- dann
76
+ * bleibt die halb geschriebene stehen. Das ist die harmlose Seite: Sie
77
+ * enthielt vorher nichts, was jemandem gehoerte.
78
+ */
79
+ function zurueck(gabEsSchon) {
80
+ if (!gabEsSchon)
81
+ return;
82
+ try {
83
+ copyFileSync(sicherungsDatei(), einstellungenDatei());
84
+ }
85
+ catch {
86
+ /* Mehr koennen wir nicht tun -- der Grund steht in der Meldung oben. */
87
+ }
88
+ }
package/dist/kimal3.js CHANGED
@@ -15,6 +15,8 @@ import { existsSync, readFileSync } from "node:fs";
15
15
  import { BASIS_URL, leseAnmeldung } from "./anmeldung.js";
16
16
  import { gemerkterHinweis, lokalerKontextStandJetzt, NichtAngemeldet } from "./api.js";
17
17
  import { leseGesehen, merkeGesehen } from "./gesehen.js";
18
+ import { ausfallMeldung, hinweisAnhaengen } from "./wache-rechnung.js";
19
+ import { leseWacheDatei, sitzungJetzt, wacheAusgefallen } from "./wache-schalter.js";
18
20
  import { formatiere, heuteAus, skillMeldung, skillZustand, sollZeigen, } from "./nachricht.js";
19
21
  import { eigeneVersion } from "./version.js";
20
22
  import { hilfeText } from "./befehle-katalog.js";
@@ -29,6 +31,7 @@ import { logout } from "./befehle/logout.js";
29
31
  import { projektListe } from "./befehle/projekt-liste.js";
30
32
  import { projektNeu } from "./befehle/projekt-neu.js";
31
33
  import { skillDatei, skillInstall } from "./befehle/skill-install.js";
34
+ import { wache, wacheSchalten } from "./befehle/wache.js";
32
35
  // 📌 Kein handgeschriebener Block mehr (2026-08-18): Die Liste wohnt in
33
36
  // `befehle-katalog.ts` und wird von dort AUCH im Pro-Bereich der App angezeigt.
34
37
  // Zwei Listen ueber dieselben Befehle driften — die eine bekaeme den naechsten
@@ -146,6 +149,28 @@ function hinweisText() {
146
149
  return { text: skillMeldung(), kennung: "skill-veraltet", tag: heute };
147
150
  }
148
151
  }
152
+ /*
153
+ Weg 3: hat die Wache fuer diese Claude-Sitzung ueberhaupt nachgesehen?
154
+
155
+ 🔴 WARUM DAS DER LETZTE WEG IST, nicht der erste: Die beiden
156
+ oberen nennen etwas, das JETZT zu tun ist -- eine Nachricht von uns, ein
157
+ veralteter Befehlssatz. Dies hier ist eine Aussage ueber das Werkzeug
158
+ selbst. Es steht deshalb hinten und erscheint an den meisten Tagen als
159
+ Einziges, weil sonst nichts anliegt.
160
+
161
+ WARUM ES DAS UEBERHAUPT GIBT: Ein Startbefehl, dessen Programm fehlt
162
+ oder abstuerzt, laermt nicht -- gemessen mit Gegenprobe (M-0). Ohne
163
+ diese Zeilen sieht ein Tag mit toter Wache genauso aus wie einer, an dem
164
+ alles in Ordnung ist. *Eine Wache, die still stirbt, kauft Vertrauen und
165
+ deckt nichts mehr.*
166
+ */
167
+ if (wacheAusgefallen(leseWacheDatei(), sitzungJetzt())) {
168
+ // Auch dieser Hinweis hoechstens einmal am Tag, unter fester Kennung.
169
+ const gesehen = leseGesehen();
170
+ if (gesehen["wache-ausgefallen"] !== heute) {
171
+ return { text: ausfallMeldung(), kennung: "wache-ausgefallen", tag: heute };
172
+ }
173
+ }
149
174
  }
150
175
  catch {
151
176
  /* Ein Nebenschauplatz darf den Befehl nicht umwerfen. */
@@ -182,7 +207,10 @@ function endeMitHinweis(code) {
182
207
  verbrauchte den EINEN Platz des Tages. Wer sich vertippt und danach
183
208
  richtig tippt, saehe die Nachricht dann heute gar nicht mehr.
184
209
  */
185
- const raus = code === 0 ? hinweisText() : null;
210
+ // 🔴 `hinweisAnhaengen` entscheidet, ob dieser Befehl den allgemeinen
211
+ // Kanal ueberhaupt bekommt -- `kimal3 wache` bekommt ihn nicht, weil
212
+ // sie ihre Zeilen selbst und in der richtigen Schreibweise sagt.
213
+ const raus = code === 0 && hinweisAnhaengen(process.argv.slice(2)) ? hinweisText() : null;
186
214
  /*
187
215
  🔴 BEIDE KANAELE LEEREN, NICHT NUR STDERR. Die erste Fassung dieser
188
216
  Funktion sicherte stderr -- und liess den haeufigeren Weg (kein Hinweis)
@@ -306,6 +334,12 @@ async function haupt() {
306
334
  return kontextHoch(argv.slice(2));
307
335
  if (argv[0] === "kontext" && argv[1] === "fragen")
308
336
  return kontextFragen(argv.slice(2));
337
+ if (wort === "wache")
338
+ return wache();
339
+ if (wort === "wache aus")
340
+ return wacheSchalten(false);
341
+ if (wort === "wache an")
342
+ return wacheSchalten(true);
309
343
  console.error(`Den Befehl „${wort}" kenne ich nicht.\n${HILFE}`);
310
344
  return 1;
311
345
  }
@@ -0,0 +1,139 @@
1
+ import { istAelter, istVersionsstand } from "./nachricht.js";
2
+ import { BEFEHLE } from "./befehle-katalog.js";
3
+ /**
4
+ * Den Befehl in der Schreibweise holen, in der man ihn dort eintippt, wo man
5
+ * ihn liest -- die Wache spricht immer durch Claude Code, also mit `!`.
6
+ *
7
+ * 🔴 KEIN ERSATZWERT AUS DER LUFT. Steht der Befehl nicht im Katalog, ist das
8
+ * ein Fehler beim Bauen und keiner, den der Kunde ausbaden soll; check:wache
9
+ * faengt ihn, weil die Zeile dann den erwarteten Text nicht enthaelt.
10
+ */
11
+ function tippen(befehl) {
12
+ const eintrag = BEFEHLE.find((b) => b.befehl === befehl);
13
+ return eintrag ? eintrag.tippen : befehl;
14
+ }
15
+ export function wacheZeilen(lage) {
16
+ // 🔴 Ausgeloggt: NUR diese eine Zeile. Die anderen beiden waeren durchaus
17
+ // beantwortbar (npm fragt ohne Anmeldung, der Skill-Stempel liegt auf der
18
+ // Platte) -- sie werden trotzdem unterdrueckt. Wer ausgeloggt ist, hat genau
19
+ // eine Aufgabe; zwei weitere Ratschlaege daneben begraben sie.
20
+ if (!lage.zugangGilt) {
21
+ return [
22
+ "Dein Zugang ist abgelaufen — das passiert nach längerer Pause. " +
23
+ `Neu anmelden: ${tippen("kimal3 login")}`,
24
+ ];
25
+ }
26
+ const zeilen = [];
27
+ if (lage.nachricht !== null) {
28
+ zeilen.push(`Nachricht von kimal3: „${lage.nachricht}“`);
29
+ }
30
+ // Beide Nummern muessen da UND gueltig sein. Fehlt eine, wird geschwiegen --
31
+ // nicht geraten.
32
+ if (lage.eigeneVersion !== null && lage.neuesteVersion !== null &&
33
+ istVersionsstand(lage.eigeneVersion) && istVersionsstand(lage.neuesteVersion) &&
34
+ istAelter(lage.eigeneVersion, lage.neuesteVersion)) {
35
+ zeilen.push(`Es gibt eine neuere Version: ${lage.neuesteVersion} (du hast ${lage.eigeneVersion}). ` +
36
+ `Holen mit: ! npm install -g kimal3@${lage.neuesteVersion}`);
37
+ }
38
+ if (lage.skillVeraltet) {
39
+ zeilen.push("Deine /k3-Befehle in Claude sind aus einer älteren Version. " +
40
+ `Auffrischen: ${tippen("kimal3 skill install")}`);
41
+ }
42
+ return zeilen;
43
+ }
44
+ /**
45
+ * Zieht den Hinweis aus der Antwort von `/api/cli/wache` -- unter dem Feld
46
+ * `nachricht`, nicht `hinweis` wie beim allgemeinen Update-Kanal in `api.ts`.
47
+ *
48
+ * 🔑 WARUM EIN EIGENES FELD UND KEINE ZWEITVERWENDUNG VON `hinweisAusAntwort`:
49
+ * Die Wache ist ein EIGENER, zweiter Kanal (US-1 der Wache-Spec) -- ihre
50
+ * Antwort traegt die Nachricht deshalb absichtlich unter einem anderen
51
+ * Namen als der Kanal, den `ruf()` bei jedem anderen Befehl mitliest. Zwei
52
+ * Kanaele, die zufaellig denselben Feldnamen teilten, koennten sich sonst
53
+ * gegenseitig fuellen.
54
+ */
55
+ export function nachrichtAusAntwort(koerper) {
56
+ if (!koerper || typeof koerper !== "object")
57
+ return null;
58
+ const roh = koerper.nachricht;
59
+ if (!roh || typeof roh !== "object")
60
+ return null;
61
+ const h = roh;
62
+ if (typeof h.id !== "string" || typeof h.text !== "string")
63
+ return null;
64
+ return {
65
+ id: h.id,
66
+ text: h.text,
67
+ gilt_unter_version: typeof h.gilt_unter_version === "string" ? h.gilt_unter_version : null,
68
+ };
69
+ }
70
+ /**
71
+ * Was der HTTP-Status von `/api/cli/wache` bedeutet -- getrennt von allem,
72
+ * was Netz oder Zeitgrenze braucht, damit sich die drei Faelle ohne Warten
73
+ * oder Verbiegen pruefen lassen: gilt, widerrufen, Serverfehler.
74
+ *
75
+ * 🔴 NUR `401` HEISST "WIDERRUFEN". Jeder andere Nicht-Erfolg (`500`, ein
76
+ * verstuemmeltes JSON, ein Feld, das fehlt) heisst "wir wissen es nicht" und
77
+ * bekommt deshalb `zugangGilt: true` -- dieselbe Regel wie bei `istAelter`:
78
+ * Im Zweifel schweigen ist bei einem Nebenschauplatz die richtige Vorgabe.
79
+ * Wuerde stattdessen `false` zurueckkommen, zeigte ein simpler Aussetzer des
80
+ * EIGENEN Servers dieselbe Zeile wie ein wirklich widerrufener Ausweis --
81
+ * und `wacheZeilen` wuerde dabei zusaetzlich die Fassungs- und Skill-Zeile
82
+ * mit unterdruecken, die von diesem Ausfall gar nicht betroffen sind.
83
+ */
84
+ export function wacheAntwortAusHttp(status, koerper) {
85
+ if (status === 401)
86
+ return { zugangGilt: false, hinweis: null };
87
+ if (status !== 200)
88
+ return { zugangGilt: true, hinweis: null };
89
+ return { zugangGilt: true, hinweis: nachrichtAusAntwort(koerper) };
90
+ }
91
+ /**
92
+ * Die Zeile, die erscheint, wenn die Wache fuer diese Claude-Sitzung nicht
93
+ * nachgesehen hat.
94
+ *
95
+ * 🔴 SIE NENNT BEIDE WEGE -- den zurueck UND den hinaus. Eine Meldung, die
96
+ * man nicht abstellen kann, wird nach der dritten Wiederholung zur Wand, und
97
+ * dann liest niemand mehr die, auf die es ankommt.
98
+ *
99
+ * 🔑 MIT AUSRUFEZEICHEN: Diese Zeile erscheint ausschliesslich in Claude --
100
+ * sie wird ja nur gezeigt, wenn eine Sitzungsnummer da ist. Wer sie liest,
101
+ * sitzt in dem Fenster, in dem `!` der richtige Anfang ist.
102
+ */
103
+ export function ausfallMeldung() {
104
+ return ("Die tägliche kimal3-Prüfung hat für diese Claude-Sitzung nicht nachgesehen. " +
105
+ `Neu eintragen: ${tippen("kimal3 skill install")}` +
106
+ /*
107
+ 🔴 `kimal3 wache aus` steht NICHT im Katalog -- dort steht nur
108
+ `kimal3 wache`. `tippen()` gibt bei einem unbekannten Befehl den Text
109
+ unveraendert zurueck, also OHNE das Ausrufezeichen: Der Mensch tippt in
110
+ Claude etwas ab, das dort nicht laeuft. Gefunden am 2026-09-08 in der
111
+ Live-Messung, NICHT von der Pruefung -- die fragte nur, ob irgendwo ein
112
+ `! kimal3` vorkommt, und das stimmte wegen der Zeile darueber.
113
+ Deshalb: die Schreibweise vom Katalog holen, das Wort `aus` anhaengen.
114
+ */
115
+ ` · Dauerhaft abstellen: ${tippen("kimal3 wache")} aus`);
116
+ }
117
+ /**
118
+ * Soll der allgemeine Update-Hinweis an diesen Befehl angehaengt werden?
119
+ *
120
+ * 🔴 BEI `kimal3 wache` NICHT -- gefunden in der Live-Messung am 2026-09-08,
121
+ * nachdem die Route live ging. Die Wache sagt bei abgelaufenem Zugang
122
+ * absichtlich GENAU EINE Zeile ("wer ausgeloggt ist, hat genau eine Aufgabe;
123
+ * zwei weitere Ratschlaege daneben begraben sie"). Der allgemeine Kanal haengt
124
+ * trotzdem seine eigene Zeile darunter -- und macht damit genau das kaputt,
125
+ * wofuer die Beschraenkung gebaut wurde.
126
+ *
127
+ * ⚠️ UND ER SCHREIBT SIE IN DER FALSCHEN FORM. Der allgemeine Hinweis ist fuer
128
+ * ein Terminalfenster gedacht und nennt `kimal3 skill install` ohne
129
+ * Ausrufezeichen. Die Wache laeuft aber ausschliesslich in Claude, wo dieselbe
130
+ * Zeile `! kimal3 skill install` heissen muss. *Der Mensch haette etwas
131
+ * abgetippt, das dort nicht laeuft.*
132
+ *
133
+ * 🔑 Und es geht nichts verloren: Die beiden Faelle des allgemeinen Kanals --
134
+ * neue Fassung, veralteter Skill -- sagt die Wache ohnehin selbst, in der
135
+ * richtigen Schreibweise und mit besserem Wortlaut.
136
+ */
137
+ export function hinweisAnhaengen(argv) {
138
+ return argv[0] !== "wache";
139
+ }
@@ -0,0 +1,149 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ /**
5
+ * Der Ausschalter und der Tages-Merkzettel der Wache: "aus" (fuer immer
6
+ * schweigen) und "zuletzt" (heute schon gelaufen) wohnen in DERSELBEN Datei.
7
+ *
8
+ * 🔑 WARUM BEIDES ZUSAMMEN: Beides sind reine Zustandsfragen ueber denselben
9
+ * Merkzettel -- eine zweite Datei wuerde nur eine zweite Stelle schaffen, an
10
+ * der beide synchron bleiben muessten.
11
+ *
12
+ * 🔴 ALLE VIER FUNKTIONEN MIT DATEIZUGRIFF WERFEN NIE (Vorbild: gesehen.ts).
13
+ * Ein kaputter, fehlender oder schreibgeschuetzter Merkzettel darf keinen
14
+ * Claude-Start umwerfen.
15
+ */
16
+ const ORDNER = join(homedir(), ".kimal3");
17
+ const DATEI = join(ORDNER, "wache.json");
18
+ /** Der rohe Inhalt des Merkzettels, oder null. Wirft nie. */
19
+ export function leseWacheDatei() {
20
+ try {
21
+ const roh = JSON.parse(readFileSync(DATEI, "utf8"));
22
+ if (!roh || typeof roh !== "object" || Array.isArray(roh))
23
+ return null;
24
+ const inhalt = roh;
25
+ const raus = {};
26
+ if (typeof inhalt.aus === "boolean")
27
+ raus.aus = inhalt.aus;
28
+ if (typeof inhalt.zuletzt === "string")
29
+ raus.zuletzt = inhalt.zuletzt;
30
+ if (typeof inhalt.sitzung === "string")
31
+ raus.sitzung = inhalt.sitzung;
32
+ return raus;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ function schreibeWacheDatei(inhalt) {
39
+ try {
40
+ // ⚠️ `mode: 0o700` wie in gesehen.ts -- `mkdirSync` mit `recursive: true`
41
+ // aendert die Rechte eines schon bestehenden Ordners NICHT. Wer diesen
42
+ // Ordner zuerst anlegt, legt seine Rechte fuer immer fest.
43
+ mkdirSync(ORDNER, { recursive: true, mode: 0o700 });
44
+ writeFileSync(DATEI, JSON.stringify(inhalt, null, 2), "utf8");
45
+ }
46
+ catch {
47
+ /* Merkzettel nicht schreibbar -- der Befehl laeuft trotzdem durch. */
48
+ }
49
+ }
50
+ /** Ist die Wache dauerhaft abgeschaltet? Wirft nie. */
51
+ export function wacheIstAus() {
52
+ return leseWacheDatei()?.aus === true;
53
+ }
54
+ /** Wache an- oder abschalten. Der Tages-Merkzettel ("zuletzt") bleibt erhalten. */
55
+ export function setzeWache(an) {
56
+ const bisher = leseWacheDatei() ?? {};
57
+ schreibeWacheDatei({ ...bisher, aus: !an });
58
+ }
59
+ /** Ist die Wache heute schon gelaufen? Wirft nie. */
60
+ export function schonGelaufenAm(heute) {
61
+ return leseWacheDatei()?.zuletzt === heute;
62
+ }
63
+ /** Merken, dass die Wache heute gelaufen ist. Der Ausschalter bleibt erhalten. */
64
+ export function merkeLauf(heute) {
65
+ const bisher = leseWacheDatei() ?? {};
66
+ schreibeWacheDatei({ ...bisher, zuletzt: heute });
67
+ }
68
+ /**
69
+ * Laeuft die Wache heute?
70
+ *
71
+ * 🔴 REIN und ohne Dateisystem, damit sie geprueft werden kann -- "heute schon
72
+ * gelaufen" und "abgeschaltet" gibt es in der echten Welt nur durch Warten
73
+ * oder Verbiegen.
74
+ *
75
+ * 🔴 Bei Muell im Merkzettel lautet die Antwort JA. Der umgekehrte Fehler
76
+ * waere teurer: Eine Wache, die wegen einer kaputten Datei fuer immer
77
+ * schweigt, sieht genauso aus wie eine, bei der alles in Ordnung ist.
78
+ */
79
+ export function sollHeuteLaufen(inhalt, heute) {
80
+ if (inhalt !== null && inhalt.aus === true)
81
+ return false;
82
+ if (inhalt !== null && typeof inhalt.zuletzt === "string" && inhalt.zuletzt === heute) {
83
+ return false;
84
+ }
85
+ return true;
86
+ }
87
+ /**
88
+ * Die Nummer der Claude-Sitzung, in der dieser Aufruf gerade laeuft -- oder
89
+ * `null`, wenn kein Claude im Spiel ist.
90
+ *
91
+ * 🔑 WOFUER: Claude gibt jeder Sitzung eine eigene Nummer und reicht sie an
92
+ * jedes Programm weiter, das es startet. Damit laesst sich exakt beantworten,
93
+ * ob die Wache FUER DIESE SITZUNG nachgesehen hat. Ohne sie muesste man den
94
+ * TAG vergleichen -- und "zuletzt vor drei Tagen" hat zwei Ursachen: Die
95
+ * Wache ist kaputt, oder der Mensch hat einfach nichts gemacht. *Ein Wert,
96
+ * der zwei Wirklichkeiten traegt, ist genau die Krankheit, gegen die die
97
+ * Wache gebaut ist.*
98
+ *
99
+ * 📌 Gemessen am 2026-09-08 (docs/qa/2026-09-08-wache-messungen.md, M-0b):
100
+ * Der Startbefehl sieht dieselbe Nummer wie die laufende Sitzung, eine zweite
101
+ * Sitzung bekommt eine andere, und der getippte Weg mit `!` sieht sie auch.
102
+ * ⚠️ Gemessen auf einem Rechner, in einer Claude-Fassung. Fehlt sie beim
103
+ * Kunden, ist die Antwort `null` -- und dann schweigt die Erkennung, statt zu
104
+ * raten. Die harmlose Seite.
105
+ */
106
+ export function sitzungJetzt() {
107
+ const roh = process.env.CLAUDE_CODE_SESSION_ID;
108
+ return typeof roh === "string" && roh.trim() !== "" ? roh : null;
109
+ }
110
+ /** Merken, fuer welche Claude-Sitzung zuletzt nachgesehen wurde. Wirft nie. */
111
+ export function merkeSitzung(sitzung) {
112
+ if (sitzung === null)
113
+ return;
114
+ const bisher = leseWacheDatei() ?? {};
115
+ schreibeWacheDatei({ ...bisher, sitzung });
116
+ }
117
+ /**
118
+ * Ist die Wache fuer diese Claude-Sitzung ausgefallen?
119
+ *
120
+ * 🔴 REIN und ohne Umgebung, damit sie geprueft werden kann -- "eine andere
121
+ * Sitzung hat zuletzt nachgesehen" gibt es in der Wirklichkeit nur durch
122
+ * Claude-Neustarts.
123
+ *
124
+ * WARUM ES DIESE FRAGE UEBERHAUPT GIBT: Ein Startbefehl, dessen Programm
125
+ * fehlt oder abstuerzt, laermt nicht (gemessen, M-0). Der Bildschirm sieht
126
+ * dann aus wie an einem guten Tag. *Eine Wache, die still stirbt, ist
127
+ * schlimmer als keine: Sie kauft Vertrauen und deckt nichts mehr.*
128
+ *
129
+ * 🔴 DREI MAL SCHWEIGEN, EINMAL SPRECHEN:
130
+ * - Keine Sitzungsnummer -> der Mensch sitzt im Terminalfenster, die Wache
131
+ * hatte nie einen Anlass zu laufen. "Nicht gelaufen" heisst hier nichts.
132
+ * - Abgeschaltet -> wer `kimal3 wache aus` getippt hat, will nicht taeglich
133
+ * daran erinnert werden. Sonst waere der Ausschalter keiner.
134
+ * - Dieselbe Nummer -> sie hat fuer diese Sitzung nachgesehen.
135
+ * Nur wenn eine Sitzung laeuft, die Wache angeschaltet ist und trotzdem
136
+ * niemand fuer sie nachgesehen hat, ist wirklich etwas kaputt.
137
+ */
138
+ export function wacheAusgefallen(
139
+ // Der GANZE Merkzettel, nicht nur die zwei Felder, die diese Rechnung liest.
140
+ // `zuletzt` interessiert sie nicht -- aber ein Merkzettel aus einer aelteren
141
+ // Fassung traegt genau das und sonst nichts, und dieser Fall gehoert
142
+ // geprueft. Ein Typ, der ihn verbietet, verbietet auch seine Pruefung.
143
+ inhalt, jetzt) {
144
+ if (jetzt === null)
145
+ return false;
146
+ if (inhalt !== null && inhalt.aus === true)
147
+ return false;
148
+ return inhalt === null || inhalt.sitzung !== jetzt;
149
+ }
package/package.json CHANGED
@@ -1,27 +1,27 @@
1
- {
2
- "name": "kimal3",
3
- "version": "0.2.2",
4
- "description": "Trag deine KI-Projekte dort ein, wo sie entstehen: im Terminal.",
5
- "type": "module",
6
- "bin": {
7
- "kimal3": "dist/kimal3.js"
8
- },
9
- "files": [
10
- "dist",
11
- "skill",
12
- "README.md"
13
- ],
14
- "engines": {
15
- "node": ">=20"
16
- },
17
- "scripts": {
18
- "build": "tsc -p tsconfig.json",
19
- "prepublishOnly": "npm run build"
20
- },
21
- "keywords": [
22
- "kimal3",
23
- "cli"
24
- ],
25
- "homepage": "https://app.kimal3.de/pro",
26
- "license": "SEE LICENSE IN README.md"
27
- }
1
+ {
2
+ "name": "kimal3",
3
+ "version": "0.2.3",
4
+ "description": "Trag deine KI-Projekte dort ein, wo sie entstehen: im Terminal.",
5
+ "type": "module",
6
+ "bin": {
7
+ "kimal3": "dist/kimal3.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "skill",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=20"
16
+ },
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.json",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "kimal3",
23
+ "cli"
24
+ ],
25
+ "homepage": "https://app.kimal3.de/pro",
26
+ "license": "SEE LICENSE IN README.md"
27
+ }