kimal3 0.2.9 → 0.3.0
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 +135 -0
- package/dist/api.js +16 -7
- package/dist/arbeitsplatz-dateien.js +62 -30
- package/dist/argumente.js +25 -97
- package/dist/befehle/arbeitsplatz.js +51 -68
- package/dist/befehle/firma-daten.js +24 -26
- package/dist/befehle/kontext-fragen.js +62 -44
- package/dist/befehle/kontext-gemeinsam.js +86 -43
- 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 +118 -88
- package/dist/befehle-katalog-en.js +185 -0
- package/dist/befehle-katalog.js +10 -49
- 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 +42 -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 +68 -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/wache-schalter.js +40 -5
- package/dist/werkbank-name.js +28 -10
- package/dist/wort.js +98 -0
- package/package.json +27 -27
- package/skill/SKILL.md +18 -10
- package/skill/commands/k3.md +1 -1
- package/skill/commands/k3feierabend.md +99 -97
- package/skill/commands/k3kontext.md +2 -2
- 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
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { zerlege } from "../argumente.js";
|
|
2
|
+
import { sprache } from "../sprache.js";
|
|
3
|
+
import { ALLGEMEIN_TEXTE } from "../texte/allgemein.js";
|
|
4
|
+
import { KONTEXT_TEXTE } from "../texte/kontext.js";
|
|
5
|
+
import { ratNachUnberuehrt } from "../kontext-rat.js";
|
|
2
6
|
import { ruf } from "../api.js";
|
|
3
7
|
import { heuteAus } from "../nachricht.js";
|
|
4
8
|
import { istUnsichererPfad } from "../arbeitsplatz-dateien.js";
|
|
5
|
-
import { LEER,
|
|
9
|
+
import { LEER, verwalteteDateien, alsMerkzettel, sicherungsOrdner, sicherungsZiel, lageAusMerkzetteln, } from "../kontext-stand.js";
|
|
6
10
|
import { leseEineDatei, leseMerkzettelDatei, schreibeMerkzettelDatei, schreibeVerwalteteDatei, sichereDatei, } from "../kontext-dateien.js";
|
|
7
|
-
import { ERLAUBT, absage, arbeitsplatzOderAbsage } from "./kontext-gemeinsam.js";
|
|
11
|
+
import { ERLAUBT, absage, arbeitsplatzOderAbsage, fremdeSpracheAbsage } from "./kontext-gemeinsam.js";
|
|
8
12
|
/**
|
|
9
13
|
* Holt den Firmenkontext von der App und legt ihn in den verwalteten Dateien
|
|
10
14
|
* des gefundenen Arbeitsplatzes ab -- `CLAUDE.md` und die fuenf Dateien in
|
|
@@ -21,7 +25,7 @@ import { ERLAUBT, absage, arbeitsplatzOderAbsage } from "./kontext-gemeinsam.js"
|
|
|
21
25
|
* Git benutzt.
|
|
22
26
|
*
|
|
23
27
|
* ⛔ **Unterhalb von `werkbank-<name>/` wird nichts geschrieben** (Spec §4).
|
|
24
|
-
* Dieser Befehl kennt genau die Pfade aus `
|
|
28
|
+
* Dieser Befehl kennt genau die Pfade aus `verwalteteDateien(sprache)`, und in keinem
|
|
25
29
|
* davon steht ein Personenname.
|
|
26
30
|
*
|
|
27
31
|
* 🔁 **Ruling T9-2:** Die Gegenrichtung, `kontextHoch`, wohnt seit dem Split
|
|
@@ -29,25 +33,18 @@ import { ERLAUBT, absage, arbeitsplatzOderAbsage } from "./kontext-gemeinsam.js"
|
|
|
29
33
|
* Firmenwahl-Absage und die `--firma`/`--firma-id`-Optionen ueber
|
|
30
34
|
* `kontext-gemeinsam.ts`.
|
|
31
35
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
ihn in deinem KI-Arbeitsplatz ab. Was du selbst geaendert hast, fasst der
|
|
37
|
-
Befehl nicht an -- er sagt dir, wie du es einbringst.
|
|
38
|
-
|
|
39
|
-
--firma "Name" Noetig, wenn du zu mehreren Firmen gehoerst.
|
|
40
|
-
Die Absage nennt dir die Namen als fertige Befehle.
|
|
41
|
-
|
|
42
|
-
--firma-id "…" Fuer den Fall, dass zwei deiner Firmen GLEICH heissen.
|
|
43
|
-
Auch diese Absage nennt dir fertige Befehle.
|
|
44
|
-
`;
|
|
36
|
+
/** Die Hilfe von `kontext runter` / `context download` in der Sprache des Menschen. */
|
|
37
|
+
export function hilfe(s) {
|
|
38
|
+
return KONTEXT_TEXTE[s].downloadHelp;
|
|
39
|
+
}
|
|
45
40
|
export async function kontextRunter(argv) {
|
|
41
|
+
let s = sprache();
|
|
42
|
+
let t = KONTEXT_TEXTE[s];
|
|
46
43
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
47
|
-
console.log(
|
|
44
|
+
console.log(hilfe(s));
|
|
48
45
|
return 0;
|
|
49
46
|
}
|
|
50
|
-
const zerlegt = zerlege(argv, ERLAUBT);
|
|
47
|
+
const zerlegt = zerlege(argv, ERLAUBT, s);
|
|
51
48
|
if (!zerlegt.ok) {
|
|
52
49
|
console.error(zerlegt.meldung);
|
|
53
50
|
return 1;
|
|
@@ -66,23 +63,32 @@ export async function kontextRunter(argv) {
|
|
|
66
63
|
suche.set("firma", wunsch);
|
|
67
64
|
if (wunschId !== undefined)
|
|
68
65
|
suche.set("firma-id", wunschId);
|
|
69
|
-
const pfad = suche.toString() ? `/api/cli/kontext?${suche.toString()}` : "/api/cli/kontext";
|
|
70
|
-
const antwort = await ruf(pfad, { methode: "GET", mitAusweis: true });
|
|
66
|
+
const pfad = suche.toString() ? `/api/cli/kontext?${suche.toString()}` : "/api/cli/kontext"; // kein-anzeigetext: Pfad der Schnittstelle
|
|
67
|
+
const antwort = await ruf(pfad, { methode: "GET", mitAusweis: true }); // kein-anzeigetext: HTTP-Verb
|
|
68
|
+
// 🌐 Die Antwort kann die Kontosprache umgestellt haben (Spec 3.2, Schritt 3): Was ab hier gedruckt wird, in der neuen.
|
|
69
|
+
s = sprache();
|
|
70
|
+
t = KONTEXT_TEXTE[s];
|
|
71
71
|
if (antwort.status !== 200) {
|
|
72
|
-
return absage(antwort, "kontext runter");
|
|
72
|
+
return absage(antwort, "kontext runter", ""); // kein-anzeigetext: Befehlsschlüssel
|
|
73
73
|
}
|
|
74
74
|
// 🔴 Ein `200` mit unlesbarem Rumpf ist KEIN Erfolg -- dasselbe Muster wie
|
|
75
75
|
// in `firma-daten.ts`. `ruf` verschluckt Parse-Fehler zu `null`.
|
|
76
76
|
if (antwort.koerper === null || typeof antwort.koerper !== "object") {
|
|
77
|
-
console.error(
|
|
77
|
+
console.error(ALLGEMEIN_TEXTE[s].unreadableAnswer);
|
|
78
78
|
return 3;
|
|
79
79
|
}
|
|
80
80
|
const daten = alsKontextAntwort(antwort.koerper);
|
|
81
81
|
if (daten === null) {
|
|
82
|
-
console.error(
|
|
82
|
+
console.error(ALLGEMEIN_TEXTE[s].unreadableAnswer);
|
|
83
83
|
return 3;
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
// 🔴 US-13: Die Firma spricht inzwischen eine andere Sprache als dieser Ordner. VOR der
|
|
86
|
+
// Schleife pruefen -- sonst stuenden nach dem Schreiben zwei Sprachen nebeneinander.
|
|
87
|
+
if (daten.sprache !== ordner.sprache) {
|
|
88
|
+
fremdeSpracheAbsage(ordner.sprache, daten.sprache, daten.firma.name);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
const lokalerMerkzettel = leseMerkzettelDatei(ordner.pfad);
|
|
86
92
|
const heute = heuteAus(new Date());
|
|
87
93
|
const geschrieben = [];
|
|
88
94
|
// Punkt A-1: die Teilmenge von `geschrieben`, bei der NICHT die Firma etwas
|
|
@@ -92,23 +98,35 @@ export async function kontextRunter(argv) {
|
|
|
92
98
|
const vorlageErneuert = [];
|
|
93
99
|
const gesicherte = [];
|
|
94
100
|
const unberuehrt = [];
|
|
95
|
-
|
|
101
|
+
/*
|
|
102
|
+
🔴 **ERST ALLES PRUEFEN UND LESEN, DANN SCHREIBEN** (Final-Review, Fund 2).
|
|
103
|
+
Bis dahin lief Pruefen und Schreiben in EINER Schleife: Fehlte in der
|
|
104
|
+
Antwort die vierte Datei, waren `CLAUDE.md` und die ersten drei schon
|
|
105
|
+
ueberschrieben, der Merkzettel aber nicht -- ein halber Stand, den das
|
|
106
|
+
naechste `runter` falsch einordnet. Jetzt bricht eine unvollstaendige
|
|
107
|
+
Lieferung (oder eine unlesbare lokale Datei) ab, bevor ein Byte geschrieben ist.
|
|
108
|
+
*/
|
|
109
|
+
const vorbereitet = [];
|
|
110
|
+
for (const verwalteterPfad of verwalteteDateien(ordner.sprache)) {
|
|
96
111
|
const serverDatei = daten.dateien.find((d) => d.pfad === verwalteterPfad);
|
|
97
112
|
if (!serverDatei) {
|
|
98
|
-
console.error(
|
|
113
|
+
console.error(t.answerMissingFile(verwalteterPfad));
|
|
99
114
|
return 3;
|
|
100
115
|
}
|
|
101
116
|
// Zweites Netz -- s. Kommentar an `schreibeVerwalteteDatei`: `pfad` kommt
|
|
102
117
|
// hier aus der eigenen Iteration, nicht roh vom Server, trotzdem geprueft.
|
|
103
118
|
if (istUnsichererPfad(serverDatei.pfad)) {
|
|
104
|
-
console.error(
|
|
119
|
+
console.error(t.answerUnsafePath(serverDatei.pfad));
|
|
105
120
|
return 3;
|
|
106
121
|
}
|
|
107
|
-
const lokalGelesen = leseEineDatei(ordner, verwalteterPfad);
|
|
122
|
+
const lokalGelesen = leseEineDatei(ordner.pfad, verwalteterPfad);
|
|
108
123
|
if (!lokalGelesen.ok) {
|
|
109
124
|
console.error(lokalGelesen.meldung);
|
|
110
125
|
return 1;
|
|
111
126
|
}
|
|
127
|
+
vorbereitet.push({ pfad: verwalteterPfad, serverInhalt: serverDatei.inhalt, lokalerInhalt: lokalGelesen.inhalt });
|
|
128
|
+
}
|
|
129
|
+
for (const { pfad: verwalteterPfad, serverInhalt, lokalerInhalt } of vorbereitet) {
|
|
112
130
|
/*
|
|
113
131
|
🔴 **DIE BEIDEN MERKZETTEL GEHEN ALS GANZES HINEIN -- Punkt A-1,
|
|
114
132
|
2026-08-26.** Hier standen vorher drei Zeilen, die den Eintrag, den
|
|
@@ -125,15 +143,15 @@ export async function kontextRunter(argv) {
|
|
|
125
143
|
*/
|
|
126
144
|
const lage = lageAusMerkzetteln({
|
|
127
145
|
pfad: verwalteterPfad,
|
|
128
|
-
lokalerInhalt
|
|
129
|
-
serverInhalt
|
|
146
|
+
lokalerInhalt,
|
|
147
|
+
serverInhalt,
|
|
130
148
|
lokal: lokalerMerkzettel,
|
|
131
149
|
vomServer: daten.merkzettel,
|
|
132
150
|
});
|
|
133
151
|
if (lage === "beides") {
|
|
134
|
-
// `
|
|
152
|
+
// `lokalerInhalt` ist hier garantiert eine Zeichenkette:
|
|
135
153
|
// `lageFuerDatei` liefert "beides" nie, wenn `lokalerInhalt === null`.
|
|
136
|
-
const sicherung = sichereDatei(ordner, verwalteterPfad,
|
|
154
|
+
const sicherung = sichereDatei(ordner.pfad, verwalteterPfad, lokalerInhalt, heute, ordner.sprache);
|
|
137
155
|
if (!sicherung.ok) {
|
|
138
156
|
console.error(sicherung.meldung);
|
|
139
157
|
return 1;
|
|
@@ -163,7 +181,7 @@ export async function kontextRunter(argv) {
|
|
|
163
181
|
vermisst hat, weil ihn niemand erreichen konnte.
|
|
164
182
|
*/
|
|
165
183
|
if (lage === "server-neuer" || lage === "beides" || lage === "vorlage-neuer") {
|
|
166
|
-
const geschriebenErg = schreibeVerwalteteDatei(ordner, verwalteterPfad,
|
|
184
|
+
const geschriebenErg = schreibeVerwalteteDatei(ordner.pfad, verwalteterPfad, serverInhalt, ordner.sprache);
|
|
167
185
|
if (!geschriebenErg.ok) {
|
|
168
186
|
console.error(geschriebenErg.meldung);
|
|
169
187
|
return 1;
|
|
@@ -200,7 +218,7 @@ export async function kontextRunter(argv) {
|
|
|
200
218
|
dieselbe Sorte Fehlalarm wie eine, die nie ankommt.*
|
|
201
219
|
*/
|
|
202
220
|
const merkzettelZumSchreiben = { firma_id: daten.firma.id, dateien: daten.merkzettel.dateien };
|
|
203
|
-
const merkzettelSchreiben = schreibeMerkzettelDatei(ordner, merkzettelZumSchreiben);
|
|
221
|
+
const merkzettelSchreiben = schreibeMerkzettelDatei(ordner.pfad, merkzettelZumSchreiben);
|
|
204
222
|
if (!merkzettelSchreiben.ok) {
|
|
205
223
|
console.error(merkzettelSchreiben.meldung);
|
|
206
224
|
return 1;
|
|
@@ -217,49 +235,46 @@ export async function kontextRunter(argv) {
|
|
|
217
235
|
// (vor dem Ergebnis) und nennt den Ordner. Die eigentliche Reihenfolge
|
|
218
236
|
// (erst sichern, dann ueberschreiben) erzwingt schon der Code oben.
|
|
219
237
|
if (gesicherte.length > 0) {
|
|
220
|
-
console.log(
|
|
238
|
+
console.log(t.backupHeader(sicherungsOrdner(ordner.sprache, heute)));
|
|
221
239
|
for (const g of gesicherte) {
|
|
222
240
|
if (g.nebenVorhandener) {
|
|
223
|
-
const bisherige = sicherungsZiel(heute, g.pfad).split("/").pop();
|
|
241
|
+
const bisherige = sicherungsZiel(ordner.sprache, heute, g.pfad).split("/").pop();
|
|
224
242
|
const neue = g.ziel.split("/").pop();
|
|
225
|
-
console.log(
|
|
226
|
-
`unveraendert stehen. Deine neuere Fassung sichere ich zusaetzlich als ${neue}, ` +
|
|
227
|
-
"bevor ich die Datei im Ordner ersetze.");
|
|
243
|
+
console.log(t.backupAlreadyToday(g.pfad, bisherige, neue));
|
|
228
244
|
}
|
|
229
245
|
else {
|
|
230
|
-
console.log(
|
|
246
|
+
console.log(t.backupPlain(g.pfad, g.ziel.split("/").pop()));
|
|
231
247
|
}
|
|
232
248
|
}
|
|
233
249
|
}
|
|
234
250
|
if (geschrieben.length > 0) {
|
|
235
|
-
console.log(
|
|
251
|
+
console.log(t.updated(geschrieben.join(", ")));
|
|
236
252
|
}
|
|
237
253
|
// 🔴 Punkt A-1: Eine eigene Zeile, kein stilles Mitschreiben. „Aktualisiert"
|
|
238
254
|
// heisst fuer den Leser sonst automatisch „meine Firma hat etwas gesagt" --
|
|
239
255
|
// und dann sucht er in der App eine Aenderung, die es dort nicht gibt.
|
|
240
256
|
if (vorlageErneuert.length > 0) {
|
|
241
|
-
console.log(
|
|
242
|
-
console.log(
|
|
243
|
-
" von dir verloren gegangen. Hättest du darin etwas geändert, hätte ich vorher gesichert.");
|
|
257
|
+
console.log(t.fromOurTemplate(vorlageErneuert.join(", ")));
|
|
258
|
+
console.log(t.templateUnchanged);
|
|
244
259
|
}
|
|
245
260
|
// §8: Ein Fehlerfall darf nie denselben Namen tragen wie ein Erfolgsfall --
|
|
246
261
|
// "nichts geschrieben" heisst hier DREI verschiedene Dinge, und die Ausgabe
|
|
247
262
|
// muss sie unterscheiden, nicht nur die Rueckgabe (alle drei geben 0).
|
|
248
263
|
if (unberuehrt.length > 0) {
|
|
249
|
-
console.log(
|
|
264
|
+
console.log(t.leftAlone(unberuehrt.join(", ")));
|
|
250
265
|
// 🔴 Der Rat haengt an der DATEI, nicht nur an der Rolle -- s.
|
|
251
|
-
// `ratNachUnberuehrt` (kontext-
|
|
252
|
-
for (const zeile of ratNachUnberuehrt(unberuehrt, daten.rolle, daten.adminName)) {
|
|
266
|
+
// `ratNachUnberuehrt` (kontext-rat.ts), Review-Befund F-3.
|
|
267
|
+
for (const zeile of ratNachUnberuehrt(unberuehrt, daten.rolle, daten.adminName, ordner.sprache, s)) {
|
|
253
268
|
console.log(zeile);
|
|
254
269
|
}
|
|
255
270
|
}
|
|
256
271
|
if (geschrieben.length === 0 && unberuehrt.length === 0) {
|
|
257
|
-
const alleLeer =
|
|
272
|
+
const alleLeer = verwalteteDateien(ordner.sprache).every((p) => (daten.merkzettel.dateien[p]?.stand ?? LEER) === LEER);
|
|
258
273
|
if (alleLeer) {
|
|
259
|
-
console.log(
|
|
274
|
+
console.log(t.companyEmpty);
|
|
260
275
|
}
|
|
261
276
|
else {
|
|
262
|
-
console.log(
|
|
277
|
+
console.log(t.upToDate);
|
|
263
278
|
}
|
|
264
279
|
}
|
|
265
280
|
return 0;
|
|
@@ -277,6 +292,8 @@ function alsKontextAntwort(koerper) {
|
|
|
277
292
|
const firmaId = firmaRoh.id;
|
|
278
293
|
if (typeof firmaId !== "string" || firmaId.trim() === "")
|
|
279
294
|
return null;
|
|
295
|
+
const firmaNameRoh = firmaRoh.name;
|
|
296
|
+
const firmaName = typeof firmaNameRoh === "string" && firmaNameRoh.trim() !== "" ? firmaNameRoh : null;
|
|
280
297
|
const rolle = k.rolle;
|
|
281
298
|
if (rolle !== "admin" && rolle !== "member")
|
|
282
299
|
return null;
|
|
@@ -301,5 +318,7 @@ function alsKontextAntwort(koerper) {
|
|
|
301
318
|
const merkzettel = alsMerkzettel(k.merkzettel);
|
|
302
319
|
if (merkzettel === null)
|
|
303
320
|
return null;
|
|
304
|
-
|
|
321
|
+
// 🌐 Teil 2: die Firmensprache. Fehlt das Feld, antwortet ein Server vor 0.3.0 -- der kennt nur Deutsch.
|
|
322
|
+
const sprache = k.sprache === "en" ? "en" : "de";
|
|
323
|
+
return { firma: { id: firmaId, name: firmaName }, rolle, adminName, dateien, merkzettel, sprache };
|
|
305
324
|
}
|
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
|
}
|