kimal3 0.2.10 → 0.3.1
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 +137 -2
- package/dist/api.js +16 -7
- package/dist/arbeitsplatz-dateien.js +23 -19
- package/dist/argumente.js +25 -97
- package/dist/befehle/arbeitsplatz.js +50 -68
- package/dist/befehle/firma-daten.js +24 -26
- package/dist/befehle/kontext-fragen.js +57 -47
- package/dist/befehle/kontext-gemeinsam.js +84 -40
- package/dist/befehle/kontext-hoch.js +40 -46
- package/dist/befehle/kontext-runter.js +70 -51
- package/dist/befehle/login.js +23 -9
- package/dist/befehle/logout.js +9 -4
- package/dist/befehle/projekt-liste.js +24 -29
- package/dist/befehle/projekt-neu.js +25 -31
- package/dist/befehle/skill-install.js +78 -49
- package/dist/befehle/skill-tausch.js +28 -0
- package/dist/befehle/wache.js +12 -6
- package/dist/befehle/werkbank.js +143 -104
- package/dist/befehle-katalog-en.js +185 -0
- package/dist/befehle-katalog.js +11 -50
- package/dist/befehlswoerter.js +69 -0
- package/dist/einstellungen-mischen.js +2 -2
- package/dist/einstellungen-schreiben.js +8 -5
- package/dist/fehler.js +5 -5
- package/dist/firmen-liste.js +8 -5
- package/dist/format.js +41 -0
- package/dist/fragen-lesen.js +3 -3
- package/dist/hilfe.js +24 -0
- package/dist/kimal3.js +77 -71
- package/dist/kontext-dateien.js +30 -23
- package/dist/kontext-hoch-zeile.js +5 -3
- package/dist/kontext-rat.js +151 -0
- package/dist/kontext-stand.js +79 -210
- package/dist/nachricht.js +39 -15
- package/dist/projekt-neu-firma.js +9 -9
- package/dist/projekt-zeilen.js +40 -54
- package/dist/slash-befehle.js +36 -0
- package/dist/sprache.js +114 -0
- package/dist/texte/allgemein.js +99 -0
- package/dist/texte/arbeitsplatz.js +135 -0
- package/dist/texte/kontext-hoch.js +128 -0
- package/dist/texte/kontext.js +171 -0
- package/dist/texte/projekte.js +126 -0
- package/dist/texte/skill.js +87 -0
- package/dist/texte/wache.js +36 -0
- package/dist/texte/werkbank.js +161 -0
- package/dist/wache-rechnung.js +27 -40
- package/dist/werkbank-name.js +28 -10
- package/dist/wort.js +98 -0
- package/package.json +3 -3
- package/skill/SKILL.md +9 -9
- package/skill/commands/k3.md +7 -4
- package/skill/commands/k3feierabend.md +99 -97
- package/skill/commands/k3kontext.md +1 -1
- package/skill/commands/k3kurs.md +6 -5
- package/skill/commands/k3start.md +2 -1
- package/skill/en/SKILL.md +451 -0
- package/skill/en/commands/k3.md +106 -0
- package/skill/en/commands/k3analyse.md +59 -0
- package/skill/en/commands/k3context.md +92 -0
- package/skill/en/commands/k3course.md +54 -0
- package/skill/en/commands/k3list.md +26 -0
- package/skill/en/commands/k3project.md +50 -0
- package/skill/en/commands/k3start.md +47 -0
- package/skill/en/commands/k3wrapup.md +99 -0
- package/dist/zz-kaputt.js +0 -1
package/dist/befehle/login.js
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import { hostname } from "node:os";
|
|
2
2
|
import { feld, ruf } from "../api.js";
|
|
3
3
|
import { BASIS_URL, schreibeAnmeldung } from "../anmeldung.js";
|
|
4
|
+
import { sprache } from "../sprache.js";
|
|
5
|
+
import { ALLGEMEIN_TEXTE } from "../texte/allgemein.js";
|
|
6
|
+
import { stelleSkillUm } from "./skill-tausch.js";
|
|
4
7
|
const warte = (ms) => new Promise((f) => setTimeout(f, ms));
|
|
5
8
|
export async function login() {
|
|
9
|
+
// 🌐 Die Sprache wird bei JEDER Ausgabe neu gelesen: Die Abhol-Antwort nennt die Kontosprache
|
|
10
|
+
// (Task 5) und kann sie mitten im Lauf aendern -- die Adresse vorher kam noch in der alten.
|
|
11
|
+
const t = () => ALLGEMEIN_TEXTE[sprache()];
|
|
6
12
|
const start = await ruf("/api/cli/login/start", { koerper: { bezeichnung: hostname() } });
|
|
7
13
|
const nutzerCode = feld(start.koerper, "nutzer_code");
|
|
8
14
|
const geraeteCode = feld(start.koerper, "geraete_code");
|
|
9
15
|
if (start.status !== 200 || !nutzerCode || !geraeteCode) {
|
|
10
|
-
console.error(
|
|
16
|
+
console.error(t().unreachable);
|
|
11
17
|
return 3;
|
|
12
18
|
}
|
|
13
|
-
console.log(
|
|
14
|
-
console.log(
|
|
15
|
-
console.log(
|
|
19
|
+
console.log(t().loginOpen(BASIS_URL));
|
|
20
|
+
console.log(t().loginEnterCode(nutzerCode));
|
|
21
|
+
console.log(t().loginWaiting);
|
|
16
22
|
const endeUm = Date.now() + 10 * 60 * 1000;
|
|
17
23
|
while (Date.now() < endeUm) {
|
|
18
24
|
await warte(2000);
|
|
@@ -34,7 +40,7 @@ export async function login() {
|
|
|
34
40
|
if (a.status >= 500)
|
|
35
41
|
continue;
|
|
36
42
|
if (a.status === 410) {
|
|
37
|
-
console.error(
|
|
43
|
+
console.error(t().loginExpired);
|
|
38
44
|
return 2;
|
|
39
45
|
}
|
|
40
46
|
// 🔴 Dieser Zweig MUSS hier oben stehen. Ohne ihn faellt eine 402 durch bis
|
|
@@ -48,19 +54,27 @@ export async function login() {
|
|
|
48
54
|
// Schleife fragt also einfach weiter, statt aufzugeben. Genau dafuer ist
|
|
49
55
|
// sie da. (Review-Befund 2026-08-14, s. `src/app/cli/actions.ts`.)
|
|
50
56
|
if (a.status === 402) {
|
|
51
|
-
console.error(`\n${feld(a.koerper, "meldung") ??
|
|
57
|
+
console.error(`\n${feld(a.koerper, "meldung") ?? t().needsPro}`);
|
|
52
58
|
return 4;
|
|
53
59
|
}
|
|
54
60
|
const schluessel = feld(a.koerper, "schluessel");
|
|
55
61
|
const email = feld(a.koerper, "email");
|
|
56
62
|
if (a.status === 200 && schluessel && email) {
|
|
57
63
|
schreibeAnmeldung({ schluessel, email });
|
|
58
|
-
console.log(
|
|
64
|
+
console.log(t().signedInAs(email));
|
|
65
|
+
// 🌐 Ab hier ist die Kontosprache bekannt (die Abhol-Antwort nennt sie). Liegt Claudes Anleitung in der
|
|
66
|
+
// anderen Sprache -- `skill install` lief auf /pro VOR dem Login --, stellt der Login sie um. Der Login
|
|
67
|
+
// bleibt gelungen, was immer dabei passiert (Lagen-Tabelle in docs/qa/2026-10-02-mehrsprachig-teil-4-messungen.md).
|
|
68
|
+
const umstellung = stelleSkillUm(sprache());
|
|
69
|
+
if (umstellung === "umgestellt")
|
|
70
|
+
console.log(t().skillSwitched);
|
|
71
|
+
if (umstellung === "unvollstaendig")
|
|
72
|
+
console.error(t().skillSwitchIncomplete);
|
|
59
73
|
return 0;
|
|
60
74
|
}
|
|
61
|
-
console.error(
|
|
75
|
+
console.error(t().loginFailed);
|
|
62
76
|
return 3;
|
|
63
77
|
}
|
|
64
|
-
console.error(
|
|
78
|
+
console.error(t().loginExpired);
|
|
65
79
|
return 2;
|
|
66
80
|
}
|
package/dist/befehle/logout.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { leseAnmeldung, loescheAnmeldung } from "../anmeldung.js";
|
|
2
2
|
import { ruf } from "../api.js";
|
|
3
|
+
import { sprache } from "../sprache.js";
|
|
4
|
+
import { ALLGEMEIN_TEXTE } from "../texte/allgemein.js";
|
|
3
5
|
/**
|
|
4
6
|
* Abmelden — erst beim Server widerrufen, DANN die Datei loeschen.
|
|
5
7
|
*
|
|
@@ -8,26 +10,29 @@ import { ruf } from "../api.js";
|
|
|
8
10
|
* Server gueltig, und nichts wuerde davon berichten.
|
|
9
11
|
*/
|
|
10
12
|
export async function logout() {
|
|
13
|
+
let t = ALLGEMEIN_TEXTE[sprache()];
|
|
11
14
|
const a = leseAnmeldung();
|
|
12
15
|
if (!a) {
|
|
13
16
|
// Kein Fehler: Der Zielzustand ist bereits erreicht. Deshalb 0.
|
|
14
|
-
console.log(
|
|
17
|
+
console.log(t.noSignIn);
|
|
15
18
|
return 0;
|
|
16
19
|
}
|
|
17
20
|
try {
|
|
18
21
|
const antwort = await ruf("/api/cli/logout", { mitAusweis: true });
|
|
22
|
+
// 🌐 Die Antwort kann die Kontosprache umgestellt haben (Spec 3.2, Schritt 3): Was ab hier gedruckt wird, in der neuen.
|
|
23
|
+
t = ALLGEMEIN_TEXTE[sprache()];
|
|
19
24
|
if (antwort.status !== 200 && antwort.status !== 401) {
|
|
20
|
-
console.error(
|
|
25
|
+
console.error(t.unreachableNothingChanged);
|
|
21
26
|
return 3;
|
|
22
27
|
}
|
|
23
28
|
// 401 heisst: Der Schluessel gilt ohnehin nicht mehr (schon widerrufen
|
|
24
29
|
// oder abgelaufen). Der Zielzustand stimmt also — die Datei darf weg.
|
|
25
30
|
}
|
|
26
31
|
catch {
|
|
27
|
-
console.error(
|
|
32
|
+
console.error(t.unreachableNothingChanged);
|
|
28
33
|
return 3;
|
|
29
34
|
}
|
|
30
35
|
loescheAnmeldung();
|
|
31
|
-
console.log(
|
|
36
|
+
console.log(t.signedOut(a.email));
|
|
32
37
|
return 0;
|
|
33
38
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { feld, ruf } from "../api.js";
|
|
2
|
+
import { sprache } from "../sprache.js";
|
|
3
|
+
import { befehlIn, schalterIn } from "../befehlswoerter.js";
|
|
4
|
+
import { ALLGEMEIN_TEXTE } from "../texte/allgemein.js";
|
|
5
|
+
import { PROJEKTE_TEXTE } from "../texte/projekte.js";
|
|
2
6
|
import { schlussFehler } from "../fehler.js";
|
|
3
|
-
import { MAX_PROJEKTE_ANZEIGE, antwortHatProjekteFeld, titelFeldLesen, projekteAusAntwort, projektZeilen, titelAusAlterAntwort, } from "../projekt-zeilen.js";
|
|
7
|
+
import { MAX_PROJEKTE_ANZEIGE, antwortHatProjekteFeld, titelFeldLesen, projekteAusAntwort, projektZeilen, titelAusAlterAntwort, titelListeZeilen, werkzeugZuAltZeilen, } from "../projekt-zeilen.js";
|
|
4
8
|
import { BASIS_URL } from "../anmeldung.js";
|
|
5
9
|
/**
|
|
6
10
|
* Die eigenen Projekte anzeigen.
|
|
@@ -15,7 +19,10 @@ import { BASIS_URL } from "../anmeldung.js";
|
|
|
15
19
|
* Werkzeug hergibt, laesst Claude entweder raten oder sich entschuldigen.*
|
|
16
20
|
*/
|
|
17
21
|
export async function projektListe() {
|
|
18
|
-
const antwort = await ruf("/api/cli/projekt", { methode: "GET", mitAusweis: true });
|
|
22
|
+
const antwort = await ruf("/api/cli/projekt", { methode: "GET", mitAusweis: true }); // kein-anzeigetext: HTTP-Methode
|
|
23
|
+
// 🌐 Die Antwort kann die Kontosprache umgestellt haben (Spec 3.2, Schritt 3): Was ab hier gedruckt wird, in der neuen.
|
|
24
|
+
const s = sprache();
|
|
25
|
+
const t = PROJEKTE_TEXTE[s];
|
|
19
26
|
if (antwort.status === 200) {
|
|
20
27
|
// 🔴 Ein `200` mit unlesbarem Rumpf ist KEIN Erfolg -- dieselbe Wache wie
|
|
21
28
|
// in `firma-daten.ts`: `ruf` verschluckt Parse-Fehler zu `null`, und ein
|
|
@@ -43,16 +50,11 @@ export async function projektListe() {
|
|
|
43
50
|
const alt = titelAusAlterAntwort(antwort.koerper);
|
|
44
51
|
if (alt) {
|
|
45
52
|
if (alt.length === 0) {
|
|
46
|
-
console.log(
|
|
47
|
-
console.log(
|
|
53
|
+
console.log(t.noProjects);
|
|
54
|
+
console.log(t.addHint(befehlIn("projekt neu", s), schalterIn("titel", s))); // kein-anzeigetext: Befehlsschluessel
|
|
48
55
|
return 0;
|
|
49
56
|
}
|
|
50
|
-
|
|
51
|
-
alt.forEach((t, i) => console.log(` ${i + 1}. ${t}`));
|
|
52
|
-
console.log("\n (Diese Version der App liefert noch keine Zahlen zu den");
|
|
53
|
-
console.log(" Projekten. Du hast nichts falsch gemacht — sie kommen mit");
|
|
54
|
-
console.log(" dem nächsten Update der App von selbst dazu.)");
|
|
55
|
-
console.log(`\n Ansehen: ${BASIS_URL}/profil\n`);
|
|
57
|
+
titelListeZeilen(alt, "alt", s, BASIS_URL).forEach((z) => console.log(z));
|
|
56
58
|
return 0;
|
|
57
59
|
}
|
|
58
60
|
/*
|
|
@@ -75,35 +77,28 @@ export async function projektListe() {
|
|
|
75
77
|
*/
|
|
76
78
|
const titel = titelFeldLesen(antwort.koerper);
|
|
77
79
|
if (titel && titel.length > 0) {
|
|
78
|
-
|
|
79
|
-
titel.forEach((t, i) => console.log(` ${i + 1}. ${t}`));
|
|
80
|
-
console.log("\n (Die Zahlen dazu kann diese Fassung von kimal3 nicht lesen —");
|
|
81
|
-
console.log(" die App ist neuer als das Werkzeug. Du hast nichts falsch");
|
|
82
|
-
console.log(" gemacht; `npm install -g kimal3` holt sie.)");
|
|
83
|
-
console.log(`\n Ansehen: ${BASIS_URL}/profil\n`);
|
|
80
|
+
titelListeZeilen(titel, "neu", s, BASIS_URL).forEach((z) => console.log(z));
|
|
84
81
|
return 0;
|
|
85
82
|
}
|
|
86
|
-
|
|
87
|
-
console.error("Du hast nichts falsch gemacht — das Werkzeug ist älter als die App.");
|
|
88
|
-
console.error(" npm install -g kimal3");
|
|
83
|
+
werkzeugZuAltZeilen(s).forEach((z) => console.error(z));
|
|
89
84
|
return 1;
|
|
90
85
|
}
|
|
91
|
-
console.error(
|
|
86
|
+
console.error(ALLGEMEIN_TEXTE[s].unreadableAnswer);
|
|
92
87
|
return 3;
|
|
93
88
|
}
|
|
94
89
|
// Eine leere Liste ist KEIN Fehler — sie ist eine gueltige Antwort auf
|
|
95
90
|
// eine gueltige Frage. Deshalb 0 und ein Satz, kein Fehlertext.
|
|
96
91
|
if (projekte.length === 0) {
|
|
97
|
-
console.log(
|
|
98
|
-
console.log(
|
|
92
|
+
console.log(t.noProjects);
|
|
93
|
+
console.log(t.addHint(befehlIn("projekt neu", s), schalterIn("titel", s))); // kein-anzeigetext: Befehlsschluessel
|
|
99
94
|
return 0;
|
|
100
95
|
}
|
|
101
|
-
console.log(
|
|
102
|
-
projektZeilen(projekte).forEach((z) => console.log(z));
|
|
103
|
-
console.log(
|
|
96
|
+
console.log(t.projectsHeaderBlank(projekte.length, MAX_PROJEKTE_ANZEIGE));
|
|
97
|
+
projektZeilen(projekte, s).forEach((z) => console.log(z));
|
|
98
|
+
console.log(t.viewOnline(BASIS_URL));
|
|
104
99
|
return 0;
|
|
105
100
|
}
|
|
106
|
-
const meldung = feld(antwort.koerper, "meldung") ??
|
|
101
|
+
const meldung = feld(antwort.koerper, "meldung") ?? ALLGEMEIN_TEXTE[s].genericFailure;
|
|
107
102
|
// 402 vor 401: Beide heissen "kommst hier nicht rein", verlangen aber
|
|
108
103
|
// Gegenteiliges. Wer sie zusammenwirft, raet dem Menschen `kimal3 login` —
|
|
109
104
|
// und der meldet sich dann erfolgreich an, ohne dass es etwas aendert.
|
|
@@ -112,11 +107,11 @@ export async function projektListe() {
|
|
|
112
107
|
return 4;
|
|
113
108
|
}
|
|
114
109
|
if (antwort.status === 401) {
|
|
115
|
-
console.error(`${meldung}
|
|
110
|
+
console.error(`${meldung}${ALLGEMEIN_TEXTE[s].pleaseSignIn}`);
|
|
116
111
|
return 2;
|
|
117
112
|
}
|
|
118
113
|
if (antwort.status === 409 && feld(antwort.koerper, "grund") === "kein-profil") {
|
|
119
|
-
console.error(`${meldung}
|
|
114
|
+
console.error(`${meldung}${t.createProfile(BASIS_URL)}`);
|
|
120
115
|
return 1;
|
|
121
116
|
}
|
|
122
117
|
// ⚠️ Hier landet auch die `503` "Konto gerade nicht prüfbar" — und sie MUSS
|
|
@@ -128,7 +123,7 @@ export async function projektListe() {
|
|
|
128
123
|
// 🔑 Den Rest entscheidet `schlussFehler` fuer alle Befehle gemeinsam --
|
|
129
124
|
// insbesondere die `404` (B-7), die hier frueher auf die `1` fiel und damit
|
|
130
125
|
// "Eingabe passt nicht" meldete, obwohl die Eingabe stimmte.
|
|
131
|
-
const schluss = schlussFehler(antwort.status, meldung);
|
|
126
|
+
const schluss = schlussFehler(antwort.status, meldung, s);
|
|
132
127
|
console.error(schluss.text);
|
|
133
128
|
return schluss.wert;
|
|
134
129
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { feld, ruf } from "../api.js";
|
|
2
|
+
import { sprache } from "../sprache.js";
|
|
3
|
+
import { ALLGEMEIN_TEXTE } from "../texte/allgemein.js";
|
|
4
|
+
import { PROJEKTE_TEXTE } from "../texte/projekte.js";
|
|
2
5
|
import { schlussFehler } from "../fehler.js";
|
|
3
6
|
import { BASIS_URL } from "../anmeldung.js";
|
|
4
7
|
import { zerlege } from "../argumente.js";
|
|
@@ -8,34 +11,22 @@ const OPTIONEN = [
|
|
|
8
11
|
"titel", "beschreibung", "symbol", "stunden", "ergebnis", "umsatz",
|
|
9
12
|
"wirkung", "wirkung-label", "link", "live-seit", "firma", "firma-id",
|
|
10
13
|
];
|
|
11
|
-
// exportiert, damit `check:cli` die
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
--titel Pflicht. 2–80 Zeichen.
|
|
18
|
-
--beschreibung Bis 300 Zeichen.
|
|
19
|
-
--symbol Eines der bekannten Symbole, z. B. bot, mail, workflow.
|
|
20
|
-
--stunden Zeitersparnis pro Woche, 0–168.
|
|
21
|
-
--ergebnis Ergebnis pro Monat in Euro, z. B. 1200 oder 1200,50.
|
|
22
|
-
--umsatz Umsatz pro Monat in Euro.
|
|
23
|
-
--wirkung Kurz, bis 20 Zeichen, z. B. "-70%".
|
|
24
|
-
--wirkung-label Wozu, bis 40 Zeichen, z. B. "Angebotszeit".
|
|
25
|
-
--link Vollstaendige Adresse mit http:// oder https://.
|
|
26
|
-
--live-seit Datum als JJJJ-MM-TT.
|
|
27
|
-
--firma "Name" Wenn du zu mehreren Firmen gehoerst: welche das Projekt bekommt.
|
|
28
|
-
--firma-id "…" Falls zwei deiner Firmen gleich heissen.
|
|
29
|
-
--ohne-firma Das Projekt gehoert keiner Firma (steht nur auf deinem Profil).
|
|
30
|
-
`;
|
|
14
|
+
// exportiert, damit `check:cli` die Hilfe als AUSGABE-Text prueft (§24 erlaubt das: nicht die
|
|
15
|
+
// Anleitung, die den Text beschreibt, sondern der Text, der wirklich auf dem Bildschirm des Kunden
|
|
16
|
+
// landet).
|
|
17
|
+
export function hilfe(s) {
|
|
18
|
+
return PROJEKTE_TEXTE[s].newHelp;
|
|
19
|
+
}
|
|
31
20
|
export async function projektNeu(argv) {
|
|
21
|
+
let s = sprache();
|
|
22
|
+
let t = PROJEKTE_TEXTE[s];
|
|
32
23
|
// ⚠️ VOR dem Zerlegen abfragen, nicht danach.
|
|
33
24
|
// Der Bauplan hatte "help" in der Optionsliste und fragte hinterher. Das
|
|
34
25
|
// konnte nie greifen: `zerlege` verlangt zu jeder Option einen Wert, also
|
|
35
26
|
// scheitert `--help` schon dort mit "fehlt ihr Wert". Ein Schalter ist keine
|
|
36
27
|
// Option mit Wert — er gehoert vor den Zerleger.
|
|
37
28
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
38
|
-
console.log(
|
|
29
|
+
console.log(hilfe(s));
|
|
39
30
|
return 0;
|
|
40
31
|
}
|
|
41
32
|
// ⚠️ `zerlege` kennt keine Optionen ohne Wert -- `--ohne-firma` wird
|
|
@@ -43,21 +34,21 @@ export async function projektNeu(argv) {
|
|
|
43
34
|
// in kontext-hoch.ts. Die Rechnung dahinter steht in projekt-neu-firma.ts,
|
|
44
35
|
// damit `check:cli` sie pruefen kann (Final-Review F-1).
|
|
45
36
|
const { rest, ohneFirma } = ohneFirmaAusArgv(argv);
|
|
46
|
-
const z = zerlege(rest, OPTIONEN);
|
|
37
|
+
const z = zerlege(rest, OPTIONEN, s);
|
|
47
38
|
if (!z.ok) {
|
|
48
39
|
console.error(z.meldung);
|
|
49
40
|
return 1;
|
|
50
41
|
}
|
|
51
42
|
const o = z.werte.optionen;
|
|
52
43
|
if (!o.titel) {
|
|
53
|
-
console.error(
|
|
44
|
+
console.error(t.missingTitle + hilfe(s));
|
|
54
45
|
return 1;
|
|
55
46
|
}
|
|
56
47
|
// 🔴 Final-Review F-3: `--ohne-firma` zusammen mit `--firma`/`--firma-id`
|
|
57
48
|
// liesse die Route den Schalter still gewinnen lassen (`firmenWunschAus` in
|
|
58
49
|
// route.ts prueft `ohne_firma` zuerst). Hier abweisen, bevor der Wunsch
|
|
59
50
|
// ueberhaupt losgeschickt wird.
|
|
60
|
-
const wunsch = firmenKoerperAus(o.firma, o["firma-id"], ohneFirma);
|
|
51
|
+
const wunsch = firmenKoerperAus(o.firma, o["firma-id"], ohneFirma, s);
|
|
61
52
|
if (!wunsch.ok) {
|
|
62
53
|
console.error(wunsch.meldung);
|
|
63
54
|
return 1;
|
|
@@ -73,14 +64,17 @@ export async function projektNeu(argv) {
|
|
|
73
64
|
...wunsch.koerper,
|
|
74
65
|
},
|
|
75
66
|
});
|
|
67
|
+
// 🌐 Die Antwort kann die Kontosprache umgestellt haben (Spec 3.2, Schritt 3): Was ab hier gedruckt wird, in der neuen.
|
|
68
|
+
s = sprache();
|
|
69
|
+
t = PROJEKTE_TEXTE[s];
|
|
76
70
|
if (antwort.status === 201) {
|
|
77
71
|
const titel = feld(antwort.koerper, "titel") ?? o.titel;
|
|
78
72
|
const anzahl = antwort.koerper?.anzahl;
|
|
79
|
-
console.log(
|
|
80
|
-
console.log(
|
|
73
|
+
console.log(t.projectAdded(titel, anzahl));
|
|
74
|
+
console.log(t.viewOnlineShort(BASIS_URL));
|
|
81
75
|
return 0;
|
|
82
76
|
}
|
|
83
|
-
const meldung = feld(antwort.koerper, "meldung") ??
|
|
77
|
+
const meldung = feld(antwort.koerper, "meldung") ?? ALLGEMEIN_TEXTE[s].genericFailure;
|
|
84
78
|
// 402 vor 401: Beide heissen "kommst hier nicht rein", verlangen aber
|
|
85
79
|
// Gegenteiliges. Wer sie zusammenwirft, raet dem Menschen `kimal3 login` —
|
|
86
80
|
// und der meldet sich dann erfolgreich an, ohne dass es etwas aendert.
|
|
@@ -89,11 +83,11 @@ export async function projektNeu(argv) {
|
|
|
89
83
|
return 4;
|
|
90
84
|
}
|
|
91
85
|
if (antwort.status === 401) {
|
|
92
|
-
console.error(`${meldung}
|
|
86
|
+
console.error(`${meldung}${ALLGEMEIN_TEXTE[s].pleaseSignIn}`);
|
|
93
87
|
return 2;
|
|
94
88
|
}
|
|
95
89
|
if (antwort.status === 409 && feld(antwort.koerper, "grund") === "kein-profil") {
|
|
96
|
-
console.error(`${meldung}
|
|
90
|
+
console.error(`${meldung}${t.createProfile(BASIS_URL)}`);
|
|
97
91
|
return 1;
|
|
98
92
|
}
|
|
99
93
|
if (antwort.status === 409) {
|
|
@@ -101,7 +95,7 @@ export async function projektNeu(argv) {
|
|
|
101
95
|
// Meldung und die Firmen als fertige Befehle. Der Befehl in der Zeile
|
|
102
96
|
// traegt ALLE getippten Optionen mit, nicht nur --titel (Final-Review
|
|
103
97
|
// F-2) -- sonst verliert der Kunde beim Kopieren --stunden, --ergebnis &co.
|
|
104
|
-
return absage(antwort,
|
|
98
|
+
return absage(antwort, "projekt neu", ` ${befehlszeileOhneFirma(o, s)}`); // kein-anzeigetext: Befehlsschluessel
|
|
105
99
|
}
|
|
106
100
|
// ⚠️ Hier landet auch die `503` "Konto gerade nicht prüfbar" — und sie MUSS
|
|
107
101
|
// hier landen, nicht oben bei der 402. Rueckgabewert 3 heisst "nochmal
|
|
@@ -112,7 +106,7 @@ export async function projektNeu(argv) {
|
|
|
112
106
|
// 🔑 Den Rest entscheidet `schlussFehler` fuer alle Befehle gemeinsam --
|
|
113
107
|
// insbesondere die `404` (B-7), die hier frueher auf die `1` fiel und damit
|
|
114
108
|
// "Eingabe passt nicht" meldete, obwohl die Eingabe stimmte.
|
|
115
|
-
const schluss = schlussFehler(antwort.status, meldung);
|
|
109
|
+
const schluss = schlussFehler(antwort.status, meldung, s);
|
|
116
110
|
console.error(schluss.text);
|
|
117
111
|
return schluss.wert;
|
|
118
112
|
}
|
|
@@ -4,8 +4,11 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { einstellungenDatei, trageWacheEin } from "../einstellungen-schreiben.js";
|
|
6
6
|
import { merkeSitzung, sitzungJetzt } from "../wache-schalter.js";
|
|
7
|
-
import {
|
|
7
|
+
import { stempelZeile } from "../nachricht.js";
|
|
8
8
|
import { eigeneVersion } from "../version.js";
|
|
9
|
+
import { sprache } from "../sprache.js";
|
|
10
|
+
import { andereSpracheZuRaeumen, skillQuelle } from "../slash-befehle.js";
|
|
11
|
+
import { SKILL_TEXTE } from "../texte/skill.js";
|
|
9
12
|
export const VERALTETE_BEFEHLE = [
|
|
10
13
|
/*
|
|
11
14
|
🔴 **Der ERSTE Eintrag ueberhaupt** (2026-08-28). `/k3firma` heisst jetzt
|
|
@@ -45,7 +48,7 @@ export function skillOrdner() {
|
|
|
45
48
|
return join(homedir(), ".claude", "skills", "kimal3");
|
|
46
49
|
}
|
|
47
50
|
export function skillDatei() {
|
|
48
|
-
return join(skillOrdner(), "SKILL.md");
|
|
51
|
+
return join(skillOrdner(), "SKILL.md"); // kein-anzeigetext: Dateiname
|
|
49
52
|
}
|
|
50
53
|
/**
|
|
51
54
|
* Haengt die eigene Versionsnummer ans ENDE des Skill-Textes.
|
|
@@ -77,12 +80,14 @@ export function skillDatei() {
|
|
|
77
80
|
* ⚠️ Ohne eigene Nummer GAR KEIN Stempel. Ein erfundener Ersatzwert waere
|
|
78
81
|
* schlimmer als keiner: Die CLI vergliche spaeter gegen ihre eigene Erfindung.
|
|
79
82
|
* Ohne Stempel antwortet `skillZustand` "veraltet" -- die harmlose Seite.
|
|
83
|
+
*
|
|
84
|
+
* 🌐 Seit Teil 4 traegt der englische Stempel ` sprache=en`; der deutsche ist Zeichen fuer Zeichen der alte.
|
|
80
85
|
*/
|
|
81
|
-
function mitStempel(inhalt) {
|
|
86
|
+
export function mitStempel(inhalt, s) {
|
|
82
87
|
const eigene = eigeneVersion();
|
|
83
88
|
if (eigene === null)
|
|
84
89
|
return inhalt;
|
|
85
|
-
return `${inhalt.trimEnd()}\n\n
|
|
90
|
+
return `${inhalt.trimEnd()}\n\n${stempelZeile(eigene, s)}\n`;
|
|
86
91
|
}
|
|
87
92
|
/**
|
|
88
93
|
* Legt das Skill UND die Slash-Befehle GLOBAL ab, damit Claude dieses Werkzeug
|
|
@@ -103,7 +108,9 @@ function mitStempel(inhalt) {
|
|
|
103
108
|
* `dist/befehle/skill-install.js` -> zwei Ebenen hoch -> `skill/…`.
|
|
104
109
|
*/
|
|
105
110
|
export async function skillInstall() {
|
|
106
|
-
const
|
|
111
|
+
const s = sprache();
|
|
112
|
+
const t = SKILL_TEXTE[s];
|
|
113
|
+
const quelle = fileURLToPath(new URL(`../../${skillQuelle(s).skill}`, import.meta.url)); // kein-anzeigetext: Paketpfad
|
|
107
114
|
if (!existsSync(quelle)) {
|
|
108
115
|
/*
|
|
109
116
|
🔴 **`1`, nicht `3`** (Review-Befund, 2026-08-18). Ein unvollstaendiges
|
|
@@ -112,8 +119,8 @@ export async function skillInstall() {
|
|
|
112
119
|
Text, und wuerde einen Rat geben, der garantiert nicht hilft. Genau der
|
|
113
120
|
Fehlertyp, den dieser Zweig bei B-5, B-7 und L-7 behoben hat.
|
|
114
121
|
*/
|
|
115
|
-
console.error(
|
|
116
|
-
console.error(" npm install -g kimal3");
|
|
122
|
+
console.error(t.skillMissing);
|
|
123
|
+
console.error(" npm install -g kimal3"); // kein-anzeigetext: Befehlszeile zum Kopieren
|
|
117
124
|
return 1;
|
|
118
125
|
}
|
|
119
126
|
// 🔑 GLOBAL, nicht projektlokal (Fund von Dennis, 2026-08-13).
|
|
@@ -161,15 +168,15 @@ export async function skillInstall() {
|
|
|
161
168
|
Der Stempel spricht ueber die SKILL.md, nicht ueber den ganzen Lauf.
|
|
162
169
|
*/
|
|
163
170
|
const inhalt = readFileSync(quelle, "utf8");
|
|
164
|
-
writeFileSync(ziel, mitStempel(inhalt), "utf8");
|
|
171
|
+
writeFileSync(ziel, mitStempel(inhalt, s), "utf8");
|
|
165
172
|
}
|
|
166
173
|
catch (fehler) {
|
|
167
|
-
console.error(
|
|
174
|
+
console.error(t.skillWriteFailed(String(fehler)));
|
|
168
175
|
return 3;
|
|
169
176
|
}
|
|
170
|
-
console.log(gabEsSchon ?
|
|
177
|
+
console.log(gabEsSchon ? t.skillUpdated : t.skillCreated);
|
|
171
178
|
console.log(` ${ziel}`);
|
|
172
|
-
const namen = slashBefehleInstallieren();
|
|
179
|
+
const namen = slashBefehleInstallieren(s, false);
|
|
173
180
|
if (namen === "kaputt") {
|
|
174
181
|
/*
|
|
175
182
|
⚠️ **Was hier zaehlt, ist die TEILWIRKUNG** (Review-Runde 2, 2026-08-18):
|
|
@@ -178,31 +185,31 @@ export async function skillInstall() {
|
|
|
178
185
|
nun gilt. *Ein halber Erfolg ist weder ein Erfolg noch ein Totalausfall
|
|
179
186
|
-- er ist ein eigener Zustand und braucht einen eigenen Satz.*
|
|
180
187
|
*/
|
|
181
|
-
console.error(
|
|
188
|
+
console.error(t.skillOnlyInstalled);
|
|
182
189
|
return 1;
|
|
183
190
|
}
|
|
184
191
|
const { geschrieben, uebersprungen, unlesbar, gestoert } = namen;
|
|
185
192
|
if (geschrieben.length > 0) {
|
|
186
|
-
console.log(
|
|
193
|
+
console.log(t.commandsInstalled(geschrieben.length, geschrieben.map((n) => `/${n}`).join(" · ")));
|
|
187
194
|
console.log(` ${join(homedir(), ".claude", "commands")}`);
|
|
188
195
|
}
|
|
189
196
|
// ⚠️ Uebersprungene Dateien gehoeren in die AUSGABE, nicht nur in die Logik.
|
|
190
197
|
// Ein Mensch, dem stillschweigend ein Befehl fehlt, sucht den Fehler bei
|
|
191
198
|
// sich. (Review-Runde 2, 2026-08-18.)
|
|
192
199
|
if (uebersprungen.length > 0) {
|
|
193
|
-
console.log(
|
|
194
|
-
console.log(
|
|
195
|
-
console.log(
|
|
196
|
-
console.log(
|
|
200
|
+
console.log(t.skipped(uebersprungen.length, uebersprungen.map((n) => `/${n}`).join(" · ")));
|
|
201
|
+
console.log(t.skipped1);
|
|
202
|
+
console.log(t.skipped2);
|
|
203
|
+
console.log(t.skipped3);
|
|
197
204
|
}
|
|
198
205
|
// 🔴 Ein LESEFEHLER bekommt einen eigenen Satz (Review-Runde 6): Er sieht
|
|
199
206
|
// aus wie Fremdbesitz, ist aber keiner -- und der Rat "benenn deine Datei
|
|
200
207
|
// um" waere hier falsch.
|
|
201
208
|
if (unlesbar.length > 0) {
|
|
202
|
-
console.log(
|
|
203
|
-
console.log(
|
|
204
|
-
console.log(
|
|
205
|
-
console.log(
|
|
209
|
+
console.log(t.unreadable(unlesbar.length, unlesbar.map((n) => `/${n}`).join(" · ")));
|
|
210
|
+
console.log(t.unreadable1);
|
|
211
|
+
console.log(t.unreadable2);
|
|
212
|
+
console.log(t.unreadable3);
|
|
206
213
|
}
|
|
207
214
|
/*
|
|
208
215
|
🔴 **Kein Slash-Befehl installiert ist kein Erfolg** (Review-Runde 5,
|
|
@@ -218,8 +225,8 @@ export async function skillInstall() {
|
|
|
218
225
|
*/
|
|
219
226
|
if (geschrieben.length === 0) {
|
|
220
227
|
const wiederholbar = gestoert || unlesbar.length > 0;
|
|
221
|
-
console.error(
|
|
222
|
-
console.error(
|
|
228
|
+
console.error(t.noCommandInstalled1);
|
|
229
|
+
console.error(t.noCommandInstalled2);
|
|
223
230
|
return wiederholbar ? 3 : 1;
|
|
224
231
|
}
|
|
225
232
|
/*
|
|
@@ -229,8 +236,8 @@ export async function skillInstall() {
|
|
|
229
236
|
Teilwirkung weg und meldete alles als misslungen.
|
|
230
237
|
*/
|
|
231
238
|
if (gestoert) {
|
|
232
|
-
console.error(
|
|
233
|
-
console.error(
|
|
239
|
+
console.error(t.notAllWritten1);
|
|
240
|
+
console.error(t.notAllWritten2);
|
|
234
241
|
return 3;
|
|
235
242
|
}
|
|
236
243
|
/*
|
|
@@ -246,7 +253,7 @@ export async function skillInstall() {
|
|
|
246
253
|
Hinweis, den kein Befolgen aufloesen konnte.
|
|
247
254
|
*/
|
|
248
255
|
if (unlesbar.length > 0) {
|
|
249
|
-
console.error(
|
|
256
|
+
console.error(t.runIncomplete);
|
|
250
257
|
return 3;
|
|
251
258
|
}
|
|
252
259
|
/*
|
|
@@ -283,33 +290,31 @@ export async function skillInstall() {
|
|
|
283
290
|
merkeSitzung(sitzungJetzt());
|
|
284
291
|
}
|
|
285
292
|
if (wache.art === "eingetragen") {
|
|
286
|
-
console.log(
|
|
293
|
+
console.log(t.watchSetUp);
|
|
287
294
|
}
|
|
288
295
|
else if (wache.art === "stand-schon") {
|
|
289
|
-
console.log(
|
|
296
|
+
console.log(t.watchAlready);
|
|
290
297
|
}
|
|
291
298
|
else if (wache.art === "abgelehnt") {
|
|
292
299
|
console.error("");
|
|
293
|
-
console.error(
|
|
294
|
-
console.error(
|
|
295
|
-
console.error(wache.grund === "kein-gueltiges-json"
|
|
296
|
-
|
|
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.");
|
|
300
|
+
console.error(t.watchFailed);
|
|
301
|
+
console.error(t.settingsUnreadable(einstellungenDatei()));
|
|
302
|
+
console.error(wache.grund === "kein-gueltiges-json" ? t.settingsNotJson : t.settingsStrangeShape);
|
|
303
|
+
console.error(t.settingsRetryNoHelp);
|
|
299
304
|
return 1;
|
|
300
305
|
}
|
|
301
306
|
else if (wache.art === "gescheitert") {
|
|
302
307
|
console.error("");
|
|
303
|
-
console.error(
|
|
304
|
-
console.error(
|
|
305
|
-
console.error(
|
|
308
|
+
console.error(t.watchFailed);
|
|
309
|
+
console.error(t.settingsFailedLine(einstellungenDatei(), ursacheDes(wache, t)));
|
|
310
|
+
console.error(t.settingsUnchanged);
|
|
306
311
|
return 1;
|
|
307
312
|
}
|
|
308
313
|
if (geschrieben.includes("k3")) {
|
|
309
|
-
console.log(
|
|
314
|
+
console.log(t.tipK3);
|
|
310
315
|
}
|
|
311
316
|
else {
|
|
312
|
-
console.log(
|
|
317
|
+
console.log(t.tipOther(geschrieben[0]));
|
|
313
318
|
}
|
|
314
319
|
return 0;
|
|
315
320
|
}
|
|
@@ -326,16 +331,22 @@ export async function skillInstall() {
|
|
|
326
331
|
* Wiederholen hilft NIE) bzw. `"stoerung"` (Schreiben ging schief, Wiederholen
|
|
327
332
|
* kann helfen). *Zwei Fehlerfaelle, die Gegenteiliges verlangen, duerfen nicht
|
|
328
333
|
* denselben Namen tragen.*
|
|
334
|
+
*
|
|
335
|
+
* 🌐 `leise`: `kimal3 login` stellt den Satz nach der Anmeldung auf die Kontosprache um (`skill-tausch.ts`)
|
|
336
|
+
* und sagt dazu EINE Zeile -- die Einzelmeldungen dieses Laufs gehoeren dann nicht auf den Bildschirm.
|
|
329
337
|
*/
|
|
330
|
-
function slashBefehleInstallieren() {
|
|
331
|
-
const
|
|
338
|
+
export function slashBefehleInstallieren(s, leise) {
|
|
339
|
+
const t = SKILL_TEXTE[s];
|
|
340
|
+
const quellOrdner = fileURLToPath(new URL(`../../${skillQuelle(s).befehle}`, import.meta.url)); // kein-anzeigetext: Paketpfad
|
|
332
341
|
if (!existsSync(quellOrdner)) {
|
|
333
|
-
|
|
342
|
+
if (!leise)
|
|
343
|
+
console.error(t.commandsMissing);
|
|
334
344
|
return "kaputt";
|
|
335
345
|
}
|
|
336
346
|
const dateien = readdirSync(quellOrdner).filter((n) => n.endsWith(".md")).sort();
|
|
337
347
|
if (dateien.length === 0) {
|
|
338
|
-
|
|
348
|
+
if (!leise)
|
|
349
|
+
console.error(t.commandsNone);
|
|
339
350
|
return "kaputt";
|
|
340
351
|
}
|
|
341
352
|
const zielOrdner = join(homedir(), ".claude", "commands");
|
|
@@ -390,7 +401,8 @@ function slashBefehleInstallieren() {
|
|
|
390
401
|
`geschrieben` und `uebersprungen` sind ausserhalb des `try` deklariert,
|
|
391
402
|
genau dafuer.
|
|
392
403
|
*/
|
|
393
|
-
|
|
404
|
+
if (!leise)
|
|
405
|
+
console.error(t.commandsWriteFailed(String(fehler)));
|
|
394
406
|
gestoert = true;
|
|
395
407
|
}
|
|
396
408
|
/*
|
|
@@ -426,9 +438,11 @@ function slashBefehleInstallieren() {
|
|
|
426
438
|
immer den Dateinamen. Ohne die Angleichung schluege der Vergleich fehl, und
|
|
427
439
|
ein Eintrag ohne `.md` bliebe fuer immer liegen.
|
|
428
440
|
*/
|
|
429
|
-
const alsDatei = (n) => (n.endsWith(".md") ? n : `${n}.md`);
|
|
441
|
+
const alsDatei = (n) => (n.endsWith(".md") ? n : `${n}.md`); // kein-anzeigetext: Dateiname
|
|
430
442
|
const ersatzLiegt = VERALTETE_BEFEHLE.filter((e) => e.ersetztDurch === null || geschrieben.includes(alsDatei(e.ersetztDurch))).map((e) => e.alt);
|
|
431
|
-
|
|
443
|
+
// 🌐 Teil 4 (T4-3): Die eigenen Dateien der anderen Sprache gehen denselben Weg wie umbenannte Befehle --
|
|
444
|
+
// nur, wenn ihr Ersatz in diesem Lauf wirklich geschrieben wurde, und nur mit unserer Marke (`raeumeVeralteteAuf`).
|
|
445
|
+
raeumeVeralteteAuf(zielOrdner, [...ersatzLiegt, ...andereSpracheZuRaeumen(geschrieben, s)], leise);
|
|
432
446
|
return {
|
|
433
447
|
geschrieben: geschrieben.map((n) => n.replace(/\.md$/, "")),
|
|
434
448
|
uebersprungen: uebersprungen.map((n) => n.replace(/\.md$/, "")),
|
|
@@ -436,6 +450,20 @@ function slashBefehleInstallieren() {
|
|
|
436
450
|
gestoert,
|
|
437
451
|
};
|
|
438
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Die Ursache, warum die Wache nicht eingetragen werden konnte, in der Sprache des Menschen.
|
|
455
|
+
* `einstellungen-schreiben.ts` nennt nur den SCHRITT (und die Meldung des Systems); den Satz dazu
|
|
456
|
+
* liefert die Textdatei -- dort steht er in beiden Sprachen.
|
|
457
|
+
*/
|
|
458
|
+
export function ursacheDes(wache, t) {
|
|
459
|
+
if (wache.schritt === "kontrolle")
|
|
460
|
+
return t.stepCheck(wache.detail);
|
|
461
|
+
if (wache.schritt === "lesen")
|
|
462
|
+
return t.stepRead(wache.detail);
|
|
463
|
+
if (wache.schritt === "sichern")
|
|
464
|
+
return t.stepBackup(wache.detail);
|
|
465
|
+
return t.stepWrite(wache.detail);
|
|
466
|
+
}
|
|
439
467
|
/** Liest eine Datei — `null`, wenn sie sich nicht lesen laesst. */
|
|
440
468
|
function leseWennMoeglich(pfad) {
|
|
441
469
|
try {
|
|
@@ -465,7 +493,7 @@ function leseWennMoeglich(pfad) {
|
|
|
465
493
|
* Gibt zurueck, was wirklich entfernt wurde -- eine Behauptung waere hier
|
|
466
494
|
* genau das Falsche.
|
|
467
495
|
*/
|
|
468
|
-
export function raeumeVeralteteAuf(zielOrdner, veraltet) {
|
|
496
|
+
export function raeumeVeralteteAuf(zielOrdner, veraltet, leise = false) {
|
|
469
497
|
const entfernt = [];
|
|
470
498
|
for (const eintrag of veraltet) {
|
|
471
499
|
/*
|
|
@@ -474,7 +502,7 @@ export function raeumeVeralteteAuf(zielOrdner, veraltet) {
|
|
|
474
502
|
ein" -- gejoint wird aber ein DATEIname. Wer `"k3alt"` eintraegt, bekam
|
|
475
503
|
keinen Fehler: nichts wird entfernt, nichts gemeldet.
|
|
476
504
|
*/
|
|
477
|
-
const alt = eintrag.endsWith(".md") ? eintrag : `${eintrag}.md`;
|
|
505
|
+
const alt = eintrag.endsWith(".md") ? eintrag : `${eintrag}.md`; // kein-anzeigetext: Dateiname
|
|
478
506
|
const weg = join(zielOrdner, alt);
|
|
479
507
|
try {
|
|
480
508
|
if (!existsSync(weg))
|
|
@@ -487,7 +515,8 @@ export function raeumeVeralteteAuf(zielOrdner, veraltet) {
|
|
|
487
515
|
}
|
|
488
516
|
rmSync(weg);
|
|
489
517
|
entfernt.push(alt);
|
|
490
|
-
|
|
518
|
+
if (!leise)
|
|
519
|
+
console.log(SKILL_TEXTE[sprache()].outdatedRemoved(alt.replace(/\.md$/, "")));
|
|
491
520
|
}
|
|
492
521
|
catch {
|
|
493
522
|
// Bewusst still: Die Befehle sind installiert. Eine liegengebliebene
|