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.
Files changed (67) hide show
  1. package/README.md +135 -0
  2. package/dist/api.js +16 -7
  3. package/dist/arbeitsplatz-dateien.js +62 -30
  4. package/dist/argumente.js +25 -97
  5. package/dist/befehle/arbeitsplatz.js +51 -68
  6. package/dist/befehle/firma-daten.js +24 -26
  7. package/dist/befehle/kontext-fragen.js +62 -44
  8. package/dist/befehle/kontext-gemeinsam.js +86 -43
  9. package/dist/befehle/kontext-hoch.js +40 -46
  10. package/dist/befehle/kontext-runter.js +70 -51
  11. package/dist/befehle/login.js +23 -9
  12. package/dist/befehle/logout.js +9 -4
  13. package/dist/befehle/projekt-liste.js +24 -29
  14. package/dist/befehle/projekt-neu.js +25 -31
  15. package/dist/befehle/skill-install.js +78 -49
  16. package/dist/befehle/skill-tausch.js +28 -0
  17. package/dist/befehle/wache.js +12 -6
  18. package/dist/befehle/werkbank.js +118 -88
  19. package/dist/befehle-katalog-en.js +185 -0
  20. package/dist/befehle-katalog.js +10 -49
  21. package/dist/befehlswoerter.js +69 -0
  22. package/dist/einstellungen-mischen.js +2 -2
  23. package/dist/einstellungen-schreiben.js +8 -5
  24. package/dist/fehler.js +5 -5
  25. package/dist/firmen-liste.js +42 -5
  26. package/dist/format.js +41 -0
  27. package/dist/fragen-lesen.js +3 -3
  28. package/dist/hilfe.js +24 -0
  29. package/dist/kimal3.js +77 -71
  30. package/dist/kontext-dateien.js +30 -23
  31. package/dist/kontext-hoch-zeile.js +5 -3
  32. package/dist/kontext-rat.js +151 -0
  33. package/dist/kontext-stand.js +68 -210
  34. package/dist/nachricht.js +39 -15
  35. package/dist/projekt-neu-firma.js +9 -9
  36. package/dist/projekt-zeilen.js +40 -54
  37. package/dist/slash-befehle.js +36 -0
  38. package/dist/sprache.js +114 -0
  39. package/dist/texte/allgemein.js +99 -0
  40. package/dist/texte/arbeitsplatz.js +135 -0
  41. package/dist/texte/kontext-hoch.js +128 -0
  42. package/dist/texte/kontext.js +171 -0
  43. package/dist/texte/projekte.js +126 -0
  44. package/dist/texte/skill.js +87 -0
  45. package/dist/texte/wache.js +36 -0
  46. package/dist/texte/werkbank.js +161 -0
  47. package/dist/wache-rechnung.js +27 -40
  48. package/dist/wache-schalter.js +40 -5
  49. package/dist/werkbank-name.js +28 -10
  50. package/dist/wort.js +98 -0
  51. package/package.json +27 -27
  52. package/skill/SKILL.md +18 -10
  53. package/skill/commands/k3.md +1 -1
  54. package/skill/commands/k3feierabend.md +99 -97
  55. package/skill/commands/k3kontext.md +2 -2
  56. package/skill/commands/k3kurs.md +6 -5
  57. package/skill/commands/k3start.md +2 -1
  58. package/skill/en/SKILL.md +451 -0
  59. package/skill/en/commands/k3.md +106 -0
  60. package/skill/en/commands/k3analyse.md +59 -0
  61. package/skill/en/commands/k3context.md +92 -0
  62. package/skill/en/commands/k3course.md +54 -0
  63. package/skill/en/commands/k3list.md +26 -0
  64. package/skill/en/commands/k3project.md +50 -0
  65. package/skill/en/commands/k3start.md +47 -0
  66. package/skill/en/commands/k3wrapup.md +99 -0
  67. package/dist/zz-kaputt.js +0 -1
@@ -4,7 +4,7 @@
4
4
  * πŸ”‘ Eine reine Rechnung, damit `check:cli` sie ohne Server pruefen kann --
5
5
  * dieselbe Ueberlegung wie bei `firmen-liste.ts` und `fehler.ts`.
6
6
  *
7
- * ⚠️ **Warum hier eigene Formatierer stehen, obwohl die App `euroBetrag` und
7
+ * ⚠️ **Warum die Formatierer in `format.ts` eigene sind, obwohl die App `euroBetrag` und
8
8
  * `datumLang` schon hat:** Die CLI ist ein eigenstaendiges npm-Paket mit
9
9
  * `rootDir: src` -- sie kann aus `../../src` nichts importieren. Das ist kein
10
10
  * Versehen, sondern der Preis dafuer, dass das Paket ohne die App baut.
@@ -16,6 +16,8 @@
16
16
  * Terminal "0 €" liest, haelt seine Eingabe fuer verloren. Das Terminal ist der
17
17
  * Ort der Nachschau, die oeffentliche Karte der Ort der Aussage.
18
18
  */
19
+ import { datumLang, euro, stunden } from "./format.js";
20
+ import { PROJEKTE_TEXTE } from "./texte/projekte.js";
19
21
  /** Ein Zahlenfeld: `null` heisst "nicht angegeben", `false` heisst "unlesbar". */
20
22
  function zahlFeld(o, name) {
21
23
  const w = o[name];
@@ -71,69 +73,30 @@ export function projekteAusAntwort(koerper) {
71
73
  const liveSeit = o.live_seit;
72
74
  if (liveSeit !== null && liveSeit !== undefined && typeof liveSeit !== "string")
73
75
  return null;
74
- const stunden = zahlFeld(o, "zeitersparnis_std_wo");
76
+ const std = zahlFeld(o, "zeitersparnis_std_wo");
75
77
  const ergebnis = zahlFeld(o, "ergebnis_monat_eur");
76
78
  const umsatz = zahlFeld(o, "umsatz_monat_eur");
77
- if (stunden === false || ergebnis === false || umsatz === false)
79
+ if (std === false || ergebnis === false || umsatz === false)
78
80
  return null;
79
81
  projekte.push({
80
82
  titel: o.titel,
81
83
  live_seit: typeof liveSeit === "string" ? liveSeit : null,
82
84
  laeuft: o.laeuft,
83
- zeitersparnis_std_wo: stunden,
85
+ zeitersparnis_std_wo: std,
84
86
  ergebnis_monat_eur: ergebnis,
85
87
  umsatz_monat_eur: umsatz,
86
88
  });
87
89
  }
88
90
  return projekte;
89
91
  }
90
- const MONATE = [
91
- "Januar", "Februar", "MΓ€rz", "April", "Mai", "Juni",
92
- "Juli", "August", "September", "Oktober", "November", "Dezember",
93
- ];
94
- /**
95
- * "2026-07-01" β†’ "1. Juli 2026". Bewusst aus der Zeichenkette gerechnet statt
96
- * ueber `new Date()`: Ein ISO-Datum ohne Uhrzeit gilt als UTC-Mitternacht und
97
- * kippt sonst je nach Zeitzone einen Tag zurueck -- und ein Terminal steht
98
- * irgendwo auf der Welt.
99
- *
100
- * ⚠️ Erwartet ein REINES Datum (`JJJJ-MM-TT`), wie es die `date`-Spalte
101
- * `live_seit` liefert. Passt es nicht, steht lieber das rohe Datum da als ein
102
- * kaputter Satz ("NaN. August 2026" -- der Fehler vom 2026-08-12).
103
- */
104
- function datumLang(isoDatum) {
105
- const [jahr, monat, tag] = isoDatum.split("-");
106
- const monatsname = MONATE[Number(monat) - 1];
107
- if (!monatsname || !jahr || !Number.isFinite(Number(tag)))
108
- return isoDatum;
109
- return `${Number(tag)}. ${monatsname} ${jahr}`;
110
- }
111
- /**
112
- * "1200" β†’ "1.200 €", "0.1" β†’ "0,10 €".
113
- *
114
- * Cents erscheinen nur, wenn welche da sind: "1.200,00 €" taeuscht eine
115
- * Genauigkeit vor, die niemand behauptet hat -- "0 €" bei 0,10 € verschweigt
116
- * dagegen eine, die dasteht.
117
- */
118
- function euro(wert) {
119
- const hatCent = Math.round(wert * 100) % 100 !== 0;
120
- const stellen = hatCent ? 2 : 0;
121
- return `${new Intl.NumberFormat("de-DE", {
122
- minimumFractionDigits: stellen,
123
- maximumFractionDigits: stellen,
124
- }).format(wert)} €`;
125
- }
126
- /** "6" β†’ "6", "6.5" β†’ "6,5". Die Spalte ist numeric(6,2). */
127
- function stunden(wert) {
128
- return new Intl.NumberFormat("de-DE", { maximumFractionDigits: 2 }).format(wert);
129
- }
130
92
  /** Was oben in der zweiten Zeile steht: laeuft, geplant oder noch nicht live. */
131
- function zustand(p) {
93
+ function zustand(p, s) {
94
+ const t = PROJEKTE_TEXTE[s];
132
95
  if (p.live_seit === null)
133
- return "noch nicht live";
96
+ return t.notLiveYet;
134
97
  return p.laeuft
135
- ? `live seit ${datumLang(p.live_seit)}`
136
- : `geplant ab ${datumLang(p.live_seit)}`;
98
+ ? t.liveSince(datumLang(p.live_seit, s))
99
+ : t.plannedFrom(datumLang(p.live_seit, s));
137
100
  }
138
101
  /**
139
102
  * Zwei Zeilen je Projekt: Titel, darunter die Angaben.
@@ -144,26 +107,27 @@ function zustand(p) {
144
107
  * eines Menschen. Eine ausdrueckliche 0 ist dagegen eine ANGABE und wird
145
108
  * gezeigt -- deshalb wird auf `null` geprueft, nicht auf Wahrheitswert.
146
109
  */
147
- export function projektZeilen(projekte) {
110
+ export function projektZeilen(projekte, s) {
111
+ const t = PROJEKTE_TEXTE[s];
148
112
  const zeilen = [];
149
113
  projekte.forEach((p, i) => {
150
114
  if (i > 0)
151
115
  zeilen.push("");
152
116
  zeilen.push(` ${i + 1}. ${p.titel}`);
153
- const teile = [zustand(p)];
117
+ const teile = [zustand(p, s)];
154
118
  if (p.zeitersparnis_std_wo !== null) {
155
- teile.push(`${stunden(p.zeitersparnis_std_wo)} Std./Woche`);
119
+ teile.push(`${stunden(p.zeitersparnis_std_wo, s)}${t.perWeek}`);
156
120
  }
157
121
  if (p.ergebnis_monat_eur !== null) {
158
- teile.push(`${euro(p.ergebnis_monat_eur)} Ergebnis/Monat`);
122
+ teile.push(`${euro(p.ergebnis_monat_eur, s)}${t.resultPerMonth}`);
159
123
  }
160
124
  if (p.umsatz_monat_eur !== null) {
161
- teile.push(`${euro(p.umsatz_monat_eur)} Umsatz/Monat`);
125
+ teile.push(`${euro(p.umsatz_monat_eur, s)}${t.revenuePerMonth}`);
162
126
  }
163
127
  // Nur der Zustand steht da? Dann sagen wir das ausdruecklich, statt eine
164
128
  // halbe Zeile hinzustellen, die aussieht, als sei etwas abgeschnitten.
165
129
  if (teile.length === 1)
166
- teile.push("keine Zahlen eingetragen");
130
+ teile.push(t.noFigures);
167
131
  zeilen.push(` ${teile.join(" Β· ")}`);
168
132
  });
169
133
  return zeilen;
@@ -207,6 +171,28 @@ export function titelAusAlterAntwort(koerper) {
207
171
  * eingefangen hat.*
208
172
  */
209
173
  export const MAX_PROJEKTE_ANZEIGE = 12;
174
+ /**
175
+ * Die Liste OHNE Zahlen, wenn nur die Titel lesbar sind -- je Eintrag EIN `console.log`-Argument.
176
+ * `alt`: die App ist aelter als das Werkzeug (L-7); `neu`: die App ist neuer (Review-Runde 4/6).
177
+ * Rein, damit `check:cli` die ganze Ausgabe messen kann statt nach Schluesselnamen zu suchen.
178
+ */
179
+ export function titelListeZeilen(titel, art, s, url) {
180
+ const t = PROJEKTE_TEXTE[s];
181
+ const hinweis = art === "alt"
182
+ ? [t.oldAppNote1, t.oldAppNote2, t.oldAppNote3]
183
+ : [t.newAppNote1, t.newAppNote2, t.newAppNote3];
184
+ return [
185
+ t.projectsHeader(titel.length, MAX_PROJEKTE_ANZEIGE),
186
+ ...titel.map((x, i) => ` ${i + 1}. ${x}`),
187
+ ...hinweis,
188
+ t.viewOnline(url),
189
+ ];
190
+ }
191
+ /** Die Absage, wenn nicht einmal die Titel lesbar sind (`stderr`). */
192
+ export function werkzeugZuAltZeilen(s) {
193
+ const t = PROJEKTE_TEXTE[s];
194
+ return [t.toolTooOld1, t.toolTooOld2, t.toolTooOld3];
195
+ }
210
196
  /**
211
197
  * Sagt, ob die Antwort ueberhaupt ein Feld `projekte` mitbringt.
212
198
  *
@@ -0,0 +1,36 @@
1
+ export const SLASH_PAARE = [
2
+ { de: "k3", en: "k3" },
3
+ { de: "k3start", en: "k3start" },
4
+ { de: "k3feierabend", en: "k3wrapup" }, // kein-anzeigetext: Befehlsname (Spec Teil 4, Abschnitt 6)
5
+ { de: "k3kontext", en: "k3context" }, // kein-anzeigetext: Befehlsname
6
+ { de: "k3projekt", en: "k3project" }, // kein-anzeigetext: Befehlsname
7
+ { de: "k3liste", en: "k3list" }, // kein-anzeigetext: Befehlsname
8
+ { de: "k3analyse", en: "k3analyse" }, // britisch, in beiden Sprachen gleich
9
+ { de: "k3kurs", en: "k3course" }, // kein-anzeigetext: Befehlsname
10
+ ];
11
+ /** Der Name in der Sprache des Satzes, der ihn nennt (ohne `/`). Unbekannt -> unveraendert. */
12
+ export function slashIn(deutsch, sprache) {
13
+ return SLASH_PAARE.find((p) => p.de === deutsch)?.[sprache] ?? deutsch;
14
+ }
15
+ /** Die Liste fuer /pro: alle ausser `k3`, in Anzeige-Reihenfolge. */
16
+ export function slashListeFuer(sprache) {
17
+ return SLASH_PAARE.filter((p) => p.de !== "k3").map((p) => p[sprache]);
18
+ }
19
+ /** Wo Skill und Befehlssatz einer Sprache im Paket liegen (relativ zu `cli/`). */
20
+ export function skillQuelle(sprache) {
21
+ return sprache === "en"
22
+ ? { skill: "skill/en/SKILL.md", befehle: "skill/en/commands" } // kein-anzeigetext: Paketpfade
23
+ : { skill: "skill/SKILL.md", befehle: "skill/commands" }; // kein-anzeigetext: Paketpfade
24
+ }
25
+ /**
26
+ * Welche Dateien der ANDEREN Sprache nach einem Lauf weg duerfen (T4-3): nur die, deren Ersatz in
27
+ * DIESEM Lauf wirklich geschrieben wurde -- und nie ein gleichnamiger Befehl (`k3`, `k3start`,
28
+ * `k3analyse` wurden gerade selbst ueberschrieben). Ob eine Datei unsere Marke traegt, prueft
29
+ * `raeumeVeralteteAuf`; diese Rechnung kennt nur Namen.
30
+ */
31
+ export function andereSpracheZuRaeumen(geschrieben, sprache) {
32
+ const andere = sprache === "en" ? "de" : "en";
33
+ return SLASH_PAARE
34
+ .filter((p) => p.de !== p.en && geschrieben.includes(`${p[sprache]}.md`)) // kein-anzeigetext: Dateiendung
35
+ .map((p) => `${p[andere]}.md`); // kein-anzeigetext: Dateiendung
36
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * In welcher Sprache spricht das Programm? (Spec Teil 3, T3-1, T3-2, T3-10)
3
+ *
4
+ * πŸ”‘ EIN WERT FUER DEN GANZEN LAUF: Ein Programmlauf gehoert genau einem Menschen. `sprache()`
5
+ * wird beim ersten Gebrauch bestimmt und gilt bis zum Ende -- es sei denn, eine Antwort des
6
+ * Servers nennt eine andere Kontosprache (`uebernimmKontosprache`), dann ab sofort die neue.
7
+ *
8
+ * πŸ”΄ WARUM `Intl` UND NICHT `LANG` (gemessen am 01.10.): In Git Bash steht `LANG=en_US.UTF-8`,
9
+ * obwohl Windows deutsch ist -- und Claude Code ruft auf Windows jeden Befehl ueber Git Bash
10
+ * auf. Node meldet ueber `Intl` trotzdem richtig `de-DE`.
11
+ *
12
+ * πŸ”‘ DER MERKZETTEL UEBERSTEHT DAS ABMELDEN (T3-1): Er liegt in einer eigenen Datei neben der
13
+ * Anmeldung, und `kimal3 logout` loescht nur die Anmeldung.
14
+ *
15
+ * Die drei Rechnungen sind rein (check:cli); nur `sprache()` und `uebernimmKontosprache` lesen
16
+ * und schreiben die Platte.
17
+ */
18
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
+ import { homedir } from "node:os";
20
+ import { join } from "node:path";
21
+ import { leseAnmeldung } from "./anmeldung.js";
22
+ /** Der Kopf, den das Programm bei jedem Aufruf mitschickt (Spec 3.2, Schritt 2). */
23
+ export const SPRACH_KOPF = "X-Kimal3-Sprache"; // kein-anzeigetext: HTTP-Kopf
24
+ /** Der Kopf, in dem der Server die Kontosprache nennt -- klein, so liefert `Headers.get` ihn ohnehin. */
25
+ export const KONTO_KOPF = "x-kimal3-kontosprache"; // kein-anzeigetext: HTTP-Kopf
26
+ /** Nur genau "de" oder "en" -- "EN", "en " und alles andere sind keine Angabe. */
27
+ export function alsSprache(wert) {
28
+ return wert === "de" || wert === "en" ? wert : null;
29
+ }
30
+ /**
31
+ * Was Node ueber das Betriebssystem meldet -> Sprache. Englisch nur bei `en` oder `en-…`.
32
+ * πŸ”΄ `-u-va-posix` ist das C/POSIX-Gebiet (`LANG=C` auf macOS/Linux) -- das ist KEINE Angabe
33
+ * und faellt auf Deutsch zurueck (Messung c im Teil-3-Plan; dort ungemessen, aus der
34
+ * ICU-Dokumentation).
35
+ */
36
+ export function spracheAusSystem(gebiet) {
37
+ const klein = gebiet.toLowerCase();
38
+ if (klein.includes("-u-va-posix"))
39
+ return "de";
40
+ return klein === "en" || klein.startsWith("en-") ? "en" : "de";
41
+ }
42
+ /**
43
+ * Die Sprache beim Start (T3-2): Merkzettel -> sonst Deutsch, wenn schon eine Anmeldung liegt
44
+ * (wer von einer aelteren Fassung aktualisiert: Sabine mit englischem Windows sah bisher immer
45
+ * Deutsch) -> sonst das Betriebssystem.
46
+ */
47
+ export function startSprache(lage) {
48
+ const gemerkt = alsSprache(lage.merkzettel);
49
+ if (gemerkt !== null)
50
+ return gemerkt;
51
+ if (lage.angemeldet)
52
+ return "de";
53
+ return spracheAusSystem(lage.systemGebiet);
54
+ }
55
+ /**
56
+ * Was eine Antwort am Merkzettel aendert: die neue Sprache, wenn der Kopf eine gueltige Sprache
57
+ * nennt UND der Merkzettel sie noch nicht traegt -- sonst `null` (nichts zu tun).
58
+ *
59
+ * πŸ”‘ VERGLICHEN WIRD MIT DEM MERKZETTEL, NICHT MIT DER GERADE GESPROCHENEN SPRACHE: Sabine
60
+ * spricht nach dem Update Deutsch ohne Merkzettel. Wuerde nur bei einem Unterschied geschrieben,
61
+ * entstuende fuer sie nie einer -- und nach `kimal3 logout` fiele sie auf ihr englisches Windows
62
+ * zurueck.
63
+ */
64
+ export function nachAntwort(gemerkt, kopf) {
65
+ const neu = alsSprache(kopf);
66
+ return neu !== null && neu !== alsSprache(gemerkt) ? neu : null;
67
+ }
68
+ const ORDNER = join(homedir(), ".kimal3");
69
+ const DATEI = join(ORDNER, "sprache.json");
70
+ function leseMerkzettel() {
71
+ try {
72
+ const roh = JSON.parse(readFileSync(DATEI, "utf8"));
73
+ return roh && typeof roh === "object" ? roh.sprache : null;
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }
79
+ let gemerkt;
80
+ let aktuell = null;
81
+ /** Die Sprache dieses Laufs. */
82
+ export function sprache() {
83
+ if (aktuell === null) {
84
+ gemerkt = leseMerkzettel();
85
+ aktuell = startSprache({
86
+ merkzettel: gemerkt,
87
+ angemeldet: leseAnmeldung() !== null,
88
+ systemGebiet: Intl.DateTimeFormat().resolvedOptions().locale,
89
+ });
90
+ }
91
+ return aktuell;
92
+ }
93
+ /**
94
+ * Nach jeder Antwort des Servers: nennt sie eine Kontosprache, gilt die ab sofort -- auch fuer den
95
+ * Rest dieses Laufs (Spec 3.2, Schritt 3). Ohne gueltigen Kopf bleibt alles, wie es ist (Schritt 4).
96
+ * Wirft nie: Der Merkzettel ist eine Bequemlichkeit, kein Grund, einen Befehl umzuwerfen.
97
+ */
98
+ export function uebernimmKontosprache(kopf) {
99
+ sprache();
100
+ const neu = nachAntwort(gemerkt, kopf);
101
+ if (neu === null)
102
+ return;
103
+ aktuell = neu;
104
+ gemerkt = neu;
105
+ try {
106
+ // ⚠️ `mode: 0o700` wie in anmeldung.ts und gesehen.ts -- wer den Ordner zuerst anlegt, legt
107
+ // seine Rechte fuer immer fest (Kommentar in gesehen.ts).
108
+ mkdirSync(ORDNER, { recursive: true, mode: 0o700 });
109
+ writeFileSync(DATEI, JSON.stringify({ sprache: neu }, null, 2), "utf8");
110
+ }
111
+ catch {
112
+ /* Merkzettel nicht schreibbar: dieser Lauf spricht trotzdem richtig. */
113
+ }
114
+ }
@@ -0,0 +1,99 @@
1
+ const de = {
2
+ // ── Verteiler (kimal3.ts) ──
3
+ // Der Zeilenumbruch gehoert zum Satz: Dahinter steht die Hilfe, ein eigener Baustein.
4
+ unknownCommand: (wort) => `Den Befehl β€ž${wort}" kenne ich nicht.\n`,
5
+ signInFirst: "Bitte zuerst `kimal3 login` ausfΓΌhren.",
6
+ unreachable: "kimal3 ist gerade nicht erreichbar.",
7
+ credentialsStored: (email) => ` ● Ausweis hinterlegt fΓΌr ${email}\n` + " (ob er noch gilt, zeigt erst der nΓ€chste Befehl)",
8
+ notSignedIn: " ● Nicht angemeldet. β†’ kimal3 login",
9
+ versionLines: (version, url) => ` kimal3 ${version}\n spricht mit ${url}`,
10
+ versionUnknown: "unbekannt",
11
+ outputChannelError: (text) => `kimal3: Ausgabekanal-Fehler -- ${text}\n`,
12
+ // ── login.ts ──
13
+ loginOpen: (url) => `\n Γ–ffne ${url}/cli`,
14
+ loginEnterCode: (code) => ` und gib diesen Code ein: ${code}\n`,
15
+ loginWaiting: " Warte auf die BestΓ€tigung … (Abbruch mit Strg+C)",
16
+ loginExpired: "\nDer Anmeldecode ist abgelaufen. Bitte `kimal3 login` noch einmal.",
17
+ needsPro: "DafΓΌr brauchst du ein Pro-Konto.",
18
+ signedInAs: (email) => `\nβœ“ Angemeldet als ${email}.`,
19
+ loginFailed: "\nDas hat nicht geklappt.",
20
+ // Gesamt-Review Teil 4: Nach dem Login steht Claudes Anleitung in der Kontosprache (`skill-tausch.ts`).
21
+ // Die Zeile spricht immer die Sprache, in die umgestellt wurde -- deshalb steht der Sprachname fest im Satz.
22
+ skillSwitched: "βœ“ Claudes Anleitung (Skill und /k3-Befehle) ist jetzt auf Deutsch.",
23
+ skillSwitchIncomplete: "⚠ Claudes Anleitung ließ sich nicht ganz auf Deutsch umstellen. Bitte `kimal3 skill install` ausführen.",
24
+ // ── logout.ts ──
25
+ noSignIn: "Auf diesem Rechner war keine Anmeldung.",
26
+ unreachableNothingChanged: "kimal3 ist gerade nicht erreichbar. Es wurde nichts geΓ€ndert.",
27
+ signedOut: (email) => `Abgemeldet. (${email})`,
28
+ // ── argumente.ts (zerlege) ──
29
+ cannotUseWord: (teil) => `Mit β€ž${teil}" kann ich hier nichts anfangen. Erwartet werden nur Optionen wie β€ž--name wert".`,
30
+ unknownOption: (getippt) => `Die Option β€ž--${getippt}" kenne ich nicht.`,
31
+ duplicateOption: (getippt) => `Die Option β€ž--${getippt}" steht zweimal da. Bitte nenne sie nur einmal.`,
32
+ missingValue: (getippt) => `Der Option β€ž--${getippt}" fehlt ihr Wert.`,
33
+ // ── fehler.ts (schlussFehler) ──
34
+ appDoesNotKnowCommand: "Diese Version der App kennt den Befehl noch nicht. " +
35
+ "Hast du gerade aktualisiert, versuch es in ein paar Minuten noch einmal.",
36
+ serverOverloaded: "Der Server ist gerade ΓΌberlastet oder hat zu lange gebraucht. " +
37
+ "Versuch es in einem Moment noch einmal.",
38
+ // ── kontext-gemeinsam.ts (`absage`, die Firmen-Zeilen) und die unlesbare Antwort ──
39
+ genericFailure: "Das hat nicht geklappt.",
40
+ // Auch von kontext-runter.ts/kontext-hoch.ts (Task 10): ein `200` mit unlesbarem Rumpf.
41
+ unreadableAnswer: "Die Antwort war nicht lesbar. Bitte gleich noch einmal.",
42
+ pleaseSignIn: " Bitte `kimal3 login` ausfΓΌhren.",
43
+ sameNameUseId: "\n Diese beiden heissen gleich β€” nimm die ID:",
44
+ yourCompanies: "\n Deine Firmen:",
45
+ createCompany: (url) => `\n Eine Firma anlegen: ${url}/firma/neu`,
46
+ nameNotUnique: (name, wieOft) => ` β€ž${name}" β€” kommt ${wieOft}Γ— vor und ist ΓΌber den Namen nicht wΓ€hlbar.`,
47
+ // Befehlszeilen zum Kopieren, kein Satz: in beiden Sprachen mit Absicht gleich gebaut -- Befehl und
48
+ // Schalter kommen schon in der Sprache des Satzes herein (befehlIn, schalterIn).
49
+ companyLine: (befehl, schalter, wert) => ` kimal3 ${befehl} --${schalter} ${wert}`,
50
+ companyIdLine: (befehl, schalter, id, name, nr) => ` kimal3 ${befehl} --${schalter} "${id}" # ${name} (${nr}.)`,
51
+ // ── Hilfe (hilfe.ts) -- der Rahmen gehoert zum Schluessel (TU-2) ──
52
+ helpTagline: "\n kimal3 β€” trag deine KI-Projekte dort ein, wo sie entstehen.\n\n",
53
+ helpGermanWordsToo: "", // Deutsch: keine Zeile (T3-13) -- die deutsche Hilfe bleibt Zeichen fuer Zeichen
54
+ helpMore: "\n\n Mehr zu einem Befehl: kimal3 projekt neu --help\n",
55
+ };
56
+ const en = {
57
+ unknownCommand: (wort) => `Unknown command β€œ${wort}”.\n`,
58
+ signInFirst: "Please run `kimal3 login` first.",
59
+ unreachable: "Can't reach kimal3 right now.",
60
+ credentialsStored: (email) => ` ● Sign-in stored for ${email}\n` +
61
+ " (the next command will show whether it is still valid)",
62
+ notSignedIn: " ● Not signed in. β†’ kimal3 login",
63
+ versionLines: (version, url) => ` kimal3 ${version}\n connected to ${url}`,
64
+ versionUnknown: "unknown",
65
+ outputChannelError: (text) => `kimal3: output channel error -- ${text}\n`,
66
+ loginOpen: (url) => `\n Open ${url}/cli`,
67
+ loginEnterCode: (code) => ` and enter this code: ${code}\n`,
68
+ loginWaiting: " Waiting for confirmation … (cancel with Ctrl+C)",
69
+ loginExpired: "\nThe sign-in code has expired. Please run `kimal3 login` again.",
70
+ needsPro: "You need a Pro account for this.",
71
+ signedInAs: (email) => `\nβœ“ Signed in as ${email}.`,
72
+ loginFailed: "\nThat didn't work.",
73
+ skillSwitched: "βœ“ Claude's instructions (skill and /k3 commands) are now in English.",
74
+ skillSwitchIncomplete: "⚠ Claude's instructions couldn't be fully switched to English. Please run `kimal3 skill install`.",
75
+ noSignIn: "No sign-in found on this computer.",
76
+ unreachableNothingChanged: "Can't reach kimal3 right now. Nothing was changed.",
77
+ signedOut: (email) => `Signed out. (${email})`,
78
+ cannotUseWord: (teil) => `Unexpected β€œ${teil}”. Expected only options like β€œ--name value”.`,
79
+ unknownOption: (getippt) => `Unknown option β€œ--${getippt}”.`,
80
+ duplicateOption: (getippt) => `The option β€œ--${getippt}” was given twice. Please use it once only.`,
81
+ missingValue: (getippt) => `The option β€œ--${getippt}” is missing its value.`,
82
+ appDoesNotKnowCommand: "This version of the app doesn't know that command yet. " +
83
+ "If you've just updated, try again shortly.",
84
+ serverOverloaded: "The server is overloaded or timed out. " +
85
+ "Try again in a moment.",
86
+ genericFailure: "That didn't work.",
87
+ unreadableAnswer: "Couldn't read the reply. Please try again in a moment.",
88
+ pleaseSignIn: " Please run `kimal3 login`.",
89
+ sameNameUseId: "\n These two have the same name β€” use the ID:",
90
+ yourCompanies: "\n Your companies:",
91
+ createCompany: (url) => `\n Create a company: ${url}/firma/neu`,
92
+ nameNotUnique: (name, wieOft) => ` β€œ${name}” appears ${wieOft} times and can't be chosen by name.`,
93
+ companyLine: (befehl, schalter, wert) => ` kimal3 ${befehl} --${schalter} ${wert}`,
94
+ companyIdLine: (befehl, schalter, id, name, nr) => ` kimal3 ${befehl} --${schalter} "${id}" # ${name} (${nr}.)`,
95
+ helpTagline: "\n kimal3 β€” log your AI projects where they happen.\n\n",
96
+ helpGermanWordsToo: "\n\n The German command words work too, e.g. kimal3 kontext runter.",
97
+ helpMore: "\n\n More on a command: kimal3 project new --help\n",
98
+ };
99
+ export const ALLGEMEIN_TEXTE = { de, en };
@@ -0,0 +1,135 @@
1
+ const de = {
2
+ // ── arbeitsplatz.ts: Hilfe ──
3
+ help: `
4
+ kimal3 arbeitsplatz neu [dein-vorname] [--firma "Name" | --firma-id "…"]
5
+
6
+ Holt deine KI-Arbeitsplatz-Vorlage und legt sie hier als Ordner an --
7
+ die ganze Familie: Firmenebene, Werkbank, Beispielprojekt.
8
+
9
+ Der Ordner heisst "KI-Arbeitsplatz" (bei englischer Firma "AI-Workplace").
10
+ Mehrere Firmen trennst du ueber den Ordner darueber, z. B.
11
+ Kunden/ARNO/KI-Arbeitsplatz.
12
+
13
+ dein-vorname Optional. Er benennt die WERKBANK darin -- aus "sabine"
14
+ wird "werkbank-sabine" (bei englischem Konto
15
+ "workbench-sabine"). Ohne Angabe steht dort der Name,
16
+ den der Server aus deiner Anmelde-Adresse gebildet hat.
17
+
18
+ --firma "Name" Noetig, wenn du zu mehreren Firmen gehoerst -- der
19
+ Firmenkontext kommt gleich mit in den Ordner.
20
+ Die Absage nennt dir die Namen als fertige Befehle.
21
+
22
+ --firma-id "…" Fuer den Fall, dass zwei deiner Firmen GLEICH heissen.
23
+ Auch diese Absage nennt dir fertige Befehle.
24
+
25
+ πŸ”΄ Bricht ab, wenn der Zielordner schon existiert und NICHT leer ist --
26
+ eigene Arbeit wird dadurch nie versehentlich ueberschrieben.
27
+
28
+ Du hast schon einen Firmen-Ordner und brauchst nur eine eigene Werkbank?
29
+ kimal3 werkbank neu <dein-vorname>
30
+ `,
31
+ // ── arbeitsplatz.ts: der Rat in der Absage "Ordner nicht leer" ──
32
+ adviceWorkbenchOnly: (ziel, befehl) => " Steht dein Arbeitsplatz schon und dir fehlt nur die eigene Werkbank?\n" +
33
+ " Dann geh HINEIN und leg sie dort an:\n" +
34
+ ` cd ${ziel}\n` +
35
+ ` kimal3 ${befehl} <dein-vorname>`,
36
+ // ── arbeitsplatz.ts: Absagen vor dem Holen ──
37
+ emptyName: "Ein leerer Name ist kein gueltiger Name.\n",
38
+ nameSuggestion: (befehl, zusammen, optionen) => `Besteht dein Name aus mehreren Woertern? Dann schreib ihn zusammen:\n` +
39
+ ` kimal3 ${befehl} ${zusammen}${optionen}\n`,
40
+ onlyOneName: (wort) => `Mit β€ž${wort}" kann ich hier nichts anfangen -- nur EIN Name ist erlaubt.\n`,
41
+ unusableName1: (name) => `Aus β€ž${name}" kann ich keinen Ordnernamen machen -- es bleiben nur`,
42
+ unusableName2: "Zeichen uebrig, die in keinem Ordnernamen stehen duerfen.",
43
+ unusableName3: (befehl) => ` Nimm deinen Vornamen: kimal3 ${befehl} sabine`,
44
+ // ── arbeitsplatz.ts: Absagen nach dem Holen ──
45
+ unsafeFolderName: (name) => `Die Antwort enthaelt einen unsicheren Ordnernamen: β€ž${name}". Bitte melden.`,
46
+ noWorkbank1: "In der Vorlage steckt kein Werkbank-Ordner -- damit fehlt der Teil,",
47
+ noWorkbank2: "in dem du arbeiten wuerdest. Das liegt nicht an dir. Bitte melden.",
48
+ // ── arbeitsplatz.ts: Erfolg und Rat (nextStepsHeader und nextStepsSay benutzt auch werkbank neu) ──
49
+ filesCreated: (n) => `βœ“ ${n} ${n === 1 ? "Datei" : "Dateien"} angelegt.`,
50
+ workbenchNamed: (name) => ` Deine Werkbank darin heisst β€ž${name}".`,
51
+ nextStepsHeader: " Weiter geht's:",
52
+ nextStepsSay: ' dann "Lass uns einrichten" tippen',
53
+ questionsOpen: (offen, gesamt) => ` Danach: ${offen} von ${gesamt} Fragen ΓΌber deine Firma sind noch offen.`,
54
+ questionsAskedOneByOne: " Claude stellt sie dir einzeln und trΓ€gt deine Antworten ein:",
55
+ // ── arbeitsplatz-dateien.ts: das Schreiben der Dateien (auch von werkbank neu benutzt) ──
56
+ noFiles1: "Die Antwort enthielt keine einzige Datei -- das ist kein gueltiges Ergebnis.",
57
+ noFiles2: "Bitte gleich noch einmal versuchen.",
58
+ isAFile1: (ordner) => `β€ž${ordner}" gibt es schon -- als DATEI, nicht als Ordner.`,
59
+ isAFile2: "Benenn die Datei um oder geh in einen anderen Ordner.",
60
+ notEmpty1: (ordner) => `Der Ordner β€ž${ordner}" gibt es schon und ist nicht leer.`,
61
+ notEmpty2: "Ich fasse ihn nicht an -- so geht nie versehentlich eigene Arbeit verloren.",
62
+ cannotReach1: (ordner) => `Ich komme an den Ordner β€ž${ordner}" nicht heran.`,
63
+ reason: (grund) => `Grund: ${grund}`,
64
+ cannotReach2: "Das liegt an diesem Rechner, nicht am Server -- Rechte pruefen.",
65
+ unsafePath: (pfad) => `Die Antwort enthaelt einen unsicheren Pfad: β€ž${pfad}". Bitte melden.`,
66
+ writeFailed: (grund) => `Schreiben fehlgeschlagen: ${grund}`,
67
+ incomplete: (ordner) => `Der Ordner β€ž${ordner}" ist dadurch UNVOLLSTAENDIG -- er darf geloescht werden.`,
68
+ writeFailedBlame: "Das liegt an diesem Rechner, nicht am Server -- Platz und Rechte pruefen.",
69
+ };
70
+ const en = {
71
+ help: `
72
+ kimal3 workplace new [your-first-name] [--company "Name" | --company-id "…"]
73
+
74
+ Fetches your AI Workplace template and creates it here as a folder β€”
75
+ the whole family: company level, workbench, example project.
76
+
77
+ The folder is called "AI-Workplace" ("KI-Arbeitsplatz" if your company
78
+ works in German). To keep several companies apart, put each in its own
79
+ parent folder, e.g. Clients/ARNO/AI-Workplace.
80
+
81
+ your-first-name Optional. It names the WORKBENCH inside β€” "sabine"
82
+ becomes "workbench-sabine" ("werkbank-sabine" with a
83
+ German account). Without it, the workbench carries the
84
+ name the server built from your sign-in address.
85
+
86
+ --company "Name" Needed if you belong to several companies β€” the
87
+ company context is added to the folder.
88
+ The error message lists the names as ready-to-run commands.
89
+
90
+ --company-id "…" For when two of your companies have the SAME name.
91
+ This error message also lists ready-to-run commands.
92
+
93
+ The German option names (--firma, --firma-id) work too.
94
+
95
+ πŸ”΄ Stops if the target folder already exists and is NOT empty β€”
96
+ your own work is never overwritten by accident.
97
+
98
+ You already have a company folder and only need your own workbench?
99
+ kimal3 workbench new <your-first-name>
100
+ `,
101
+ adviceWorkbenchOnly: (ziel, befehl) => " Is your workplace already there and you only need your own workbench?\n" +
102
+ " Then go INTO it and create it there:\n" +
103
+ ` cd ${ziel}\n` +
104
+ ` kimal3 ${befehl} <your-first-name>`,
105
+ emptyName: "A name can't be empty.\n",
106
+ nameSuggestion: (befehl, zusammen, optionen) => "Does your name have several words? Then write it as one:\n" +
107
+ ` kimal3 ${befehl} ${zusammen}${optionen}\n`,
108
+ onlyOneName: (wort) => `β€œ${wort}” won't work here β€” only ONE name is allowed.\n`,
109
+ unusableName1: (name) => `I can't make a folder name out of β€œ${name}” β€” all that's left are`,
110
+ unusableName2: "characters that aren't allowed in any folder name.",
111
+ unusableName3: (befehl) => ` Use your first name: kimal3 ${befehl} sabine`,
112
+ unsafeFolderName: (name) => `The reply contains an unsafe folder name: β€œ${name}”. Please report this.`,
113
+ noWorkbank1: "The template has no workbench folder, so the place you'd work in is missing.",
114
+ noWorkbank2: "That isn't down to you. Please report this.",
115
+ filesCreated: (n) => `βœ“ ${n} ${n === 1 ? "file" : "files"} created.`,
116
+ workbenchNamed: (name) => ` Inside it, your workbench is called β€œ${name}”.`,
117
+ nextStepsHeader: " Next steps:",
118
+ nextStepsSay: " then type β€œLet's get set up”",
119
+ questionsOpen: (offen, gesamt) => ` After that: ${offen} of ${gesamt} questions about your company are still open.`,
120
+ questionsAskedOneByOne: " Claude asks them one at a time and records your answers:",
121
+ noFiles1: "The reply didn't contain a single file β€” that isn't a valid result.",
122
+ noFiles2: "Please try again in a moment.",
123
+ isAFile1: (ordner) => `β€œ${ordner}” already exists β€” as a FILE, not a folder.`,
124
+ isAFile2: "Rename the file or go to a different folder.",
125
+ notEmpty1: (ordner) => `The folder β€œ${ordner}” already exists and isn't empty.`,
126
+ notEmpty2: "I won't touch it β€” that way your own work is never lost by accident.",
127
+ cannotReach1: (ordner) => `I can't access the folder β€œ${ordner}”.`,
128
+ reason: (grund) => `Reason: ${grund}`,
129
+ cannotReach2: "That's down to this computer, not the server β€” check the permissions.",
130
+ unsafePath: (pfad) => `The reply contains an unsafe path: β€œ${pfad}”. Please report this.`,
131
+ writeFailed: (grund) => `Writing failed: ${grund}`,
132
+ incomplete: (ordner) => `The folder β€œ${ordner}” is therefore INCOMPLETE β€” you can delete it.`,
133
+ writeFailedBlame: "That's down to this computer, not the server β€” check the disk space and permissions.",
134
+ };
135
+ export const ARBEITSPLATZ_TEXTE = { de, en };