kimal3 0.2.1 → 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 +134 -102
- package/dist/anmeldung.js +1 -1
- package/dist/befehle/skill-install.js +50 -0
- package/dist/befehle/wache.js +185 -0
- package/dist/befehle-katalog.js +20 -0
- package/dist/einstellungen-mischen.js +124 -0
- package/dist/einstellungen-schreiben.js +88 -0
- package/dist/fehler.js +2 -0
- package/dist/kimal3.js +35 -1
- package/dist/wache-rechnung.js +139 -0
- package/dist/wache-schalter.js +149 -0
- package/package.json +27 -27
- package/skill/SKILL.md +381 -307
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://
|
|
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://
|
|
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
|
|
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
|
-
|
|
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://
|
|
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
|
+
}
|
package/dist/befehle-katalog.js
CHANGED
|
@@ -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
|
+
}
|