kimal3 0.9.0 → 0.9.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.
@@ -1,7 +1,7 @@
1
- {
2
- "name": "kimal3-plan",
3
- "version": "0.2.0",
4
- "description": "Hält das Board der Projektseite in der App aktuell: Punkt solange die Sitzung arbeitet, ob Claude gerade arbeitet oder wartet, „Besprechen“ an Claude weitergeben, Spec-Texte, hält Claude vor dem ersten Bau-Schritt an · Keeps the board on the app's project page current: dot while the session works, whether Claude is working or waiting, passes ‘Discuss’ on to Claude, spec texts, stops Claude before the first build step",
5
- "author": { "name": "KImal3", "url": "https://kimal3.de" },
6
- "types": "./types/index.d.ts"
7
- }
1
+ {
2
+ "name": "kimal3-plan",
3
+ "version": "0.2.1",
4
+ "description": "Hält das Board der Projektseite in der App aktuell: Punkt solange die Sitzung arbeitet, ob Claude gerade arbeitet oder wartet, „Besprechen“ an Claude weitergeben, Spec-Texte, hält Claude vor dem ersten Bau-Schritt an · Keeps the board on the app's project page current: dot while the session works, whether Claude is working or waiting, passes ‘Discuss’ on to Claude, spec texts, stops Claude before the first build step",
5
+ "author": { "name": "KImal3", "url": "https://kimal3.de" },
6
+ "types": "./types/index.d.ts"
7
+ }
@@ -67,6 +67,13 @@ export function gespraech(on: On, lauf: Lauf): void {
67
67
  })
68
68
  return next(e)
69
69
  })
70
+ // US-6 (T-12): Beim Stop nennt Claude Code die laufende Hintergrund-Arbeit. Ein Befehl darin hält „arbeitet“, bis ein
71
+ // späterer Stop (die Runde, die seine Fertig-Meldung verarbeitet) ihn nicht mehr nennt. Subagenten zählen schon über ihre
72
+ // Runden; Monitore zählen nicht, weil einer ohne Ende laufen kann. Nur die Hauptrunde: Ein Subagent endet mit SubagentStop.
73
+ on('classic.Stop', ($, e, next) => {
74
+ aendere($, lauf, () => { lauf.hintergrund = (e.background_tasks ?? []).some(t => t.type === 'shell') })
75
+ return next(e)
76
+ })
70
77
  // Nur beobachten: Die Abfrage selbst entscheidet der Mensch, der Haken antwortet nie mit `decision`.
71
78
  on('classic.PermissionRequest', ($, e, next) => {
72
79
  aendere($, lauf, () => {
@@ -39,6 +39,8 @@ export type Lauf = {
39
39
  fehler: Set<Fehler>
40
40
  /** Die offenen Runden (`turnId`), Hauptrunde und Subagenten: nicht leer = Claude arbeitet (hooks/gespraech.ts, E-4). */
41
41
  runden: Set<string>
42
+ /** Beim letzten Stop der Hauptrunde lief ein Befehl im Hintergrund: Claude arbeitet weiter, auch ohne offene Runde (US-6, T-12). */
43
+ hintergrund: boolean
42
44
  /** Worauf Claude gerade wartet (Erlaubnis, Fragefenster) und in welcher Schleife; `null`: auf nichts (E-5). */
43
45
  wartet: { agent: string | undefined; werkzeug: string } | null
44
46
  /** Das Gespräch aus der letzten Antwort der App; `null`: keins, oder eine App ohne „Im Gespräch“ (R-18). */
@@ -61,7 +63,7 @@ export type GespraechFuerMod = { nummer: number; spec: string; titel: string; da
61
63
 
62
64
  /** Arbeitet Claude, wartet es, oder ist es fertig (hooks/gespraech.ts, E-4, E-5)? */
63
65
  export function zustandVon(lauf: Lauf): Zustand {
64
- if (lauf.runden.size === 0) return 'still'
66
+ if (lauf.runden.size === 0) return lauf.hintergrund ? 'arbeitet' : 'still'
65
67
  return lauf.wartet === null ? 'arbeitet' : 'wartet'
66
68
  }
67
69
 
@@ -88,7 +90,7 @@ export function neuerLauf(vorgabe: Sprache): Lauf {
88
90
  return {
89
91
  vorgabe, sprache: vorgabe, heim: null, basis: APP, lage: { art: 'kein-projektordner' }, inArbeit: null, meldungen: 0, specDateien: [],
90
92
  takt: null, startet: false, generation: 0, beendet: false, abgelehnt: null, gemeldetFuer: null, meldungenPruefen: true,
91
- fehler: new Set(), zeile: null, runden: new Set(), wartet: null, gespraech: null, abholen: null, nachzuegler: false, wartende: [],
93
+ fehler: new Set(), zeile: null, runden: new Set(), hintergrund: false, wartet: null, gespraech: null, abholen: null, nachzuegler: false, wartende: [],
92
94
  }
93
95
  }
94
96
 
package/dist/anmeldung.js CHANGED
@@ -49,7 +49,3 @@ export function loescheAnmeldung() {
49
49
  }
50
50
  catch { /* war schon weg */ }
51
51
  }
52
- /** Nur fuer Meldungen an den Menschen — damit er weiss, wo die Datei liegt. */
53
- export function anmeldungsPfad() {
54
- return DATEI;
55
- }
@@ -365,14 +365,6 @@ export function fehlendeDateienSchreiben(dateien, ziel) {
365
365
  return { ok: false, wert: 1 };
366
366
  }
367
367
  }
368
- /**
369
- * Ein `..`-Segment oder ein absoluter Pfad duerften das Ziel verlassen.
370
- *
371
- * 🔴 B-8: Zerlegt an `/` UND `\` -- eine fehlerhafte oder manipulierte
372
- * Server-Antwort koennte sonst mit `..\..\boese.md` durchkommen. `join()`
373
- * trennt unter Windows auch am Backslash, dieser Schutz muss also beide
374
- * Trenner kennen wie `join()` selbst.
375
- */
376
368
  /**
377
369
  * Taugt das als **Ordnername**? — schaerfer als `istUnsichererPfad`.
378
370
  *
@@ -405,6 +397,14 @@ export function istUnsichererOrdnername(name) {
405
397
  return true;
406
398
  return teile[0] === "" || teile[0] === ".";
407
399
  }
400
+ /**
401
+ * Ein `..`-Segment oder ein absoluter Pfad duerften das Ziel verlassen.
402
+ *
403
+ * 🔴 B-8: Zerlegt an `/` UND `\` -- eine fehlerhafte oder manipulierte
404
+ * Server-Antwort koennte sonst mit `..\..\boese.md` durchkommen. `join()`
405
+ * trennt unter Windows auch am Backslash, dieser Schutz muss also beide
406
+ * Trenner kennen wie `join()` selbst.
407
+ */
408
408
  export function istUnsichererPfad(pfad) {
409
409
  if (pfad.startsWith("/") || /^[a-zA-Z]:/.test(pfad))
410
410
  return true;
@@ -1,6 +1,7 @@
1
1
  import { resolve } from "node:path";
2
- import { FIRMEN_OPTIONEN, dateienSchreiben, vorlageHolen, werkbankOrdner, istUnsichererOrdnername, } from "../arbeitsplatz-dateien.js";
3
- import { alsBefehlszeile, alsWort, trenneNamenUndOptionen, zerlege } from "../argumente.js";
2
+ import { dateienSchreiben, vorlageHolen, werkbankOrdner, istUnsichererOrdnername, } from "../arbeitsplatz-dateien.js";
3
+ import { alsWort } from "../argumente.js";
4
+ import { nameUndFirma } from "./name-und-firma.js";
4
5
  import { befehlIn } from "../befehlswoerter.js";
5
6
  import { slashIn } from "../slash-befehle.js";
6
7
  import { sprache } from "../sprache.js";
@@ -98,90 +99,11 @@ export async function arbeitsplatzNeu(argv) {
98
99
  console.log(hilfe(s));
99
100
  return 0;
100
101
  }
101
- /*
102
- Dieselben drei Absagen wie bei `werkbank neu` -- bewusst wortgleich: Wer den
103
- einen Befehl kennt, soll beim anderen nichts Neues lernen muessen.
104
-
105
- ⚠️ Sie sprechen jetzt von einem NAMEN, nicht von einem Ordnernamen. Das ist
106
- keine Kosmetik: Wer nach der alten Hilfe `kimal3 arbeitsplatz neu ARNO-KI`
107
- tippt, bekommt keinen Fehler, sondern eine Werkbank namens
108
- `werkbank-arno-ki`. Diese Meldungen und die Hilfe sind die einzigen Stellen,
109
- an denen er das erfaehrt, bevor es passiert.
110
- */
111
- /*
112
- 🔴 **Erst schneiden, dann zerlegen** (Punkt A-3, 2026-08-26). Dieser Befehl
113
- hat ein Wort OHNE `--` (den Vornamen), und `zerlege` lehnt genau das ab.
114
- Deshalb trennt `trenneNamenUndOptionen` beim ersten `--`: davor die
115
- Woerter, ab dort die Optionen. Die Strenge von `zerlege` bleibt damit
116
- unangetastet -- sie war teuer erkauft (B-3 und B-4, 2026-08-18).
117
- */
118
- const { woerter, optionen: optionsArgv } = trenneNamenUndOptionen(argv);
119
- const nameArg = woerter[0];
120
- if (nameArg !== undefined && nameArg.trim() === "") {
121
- console.error(t.emptyName + hilfe(s));
122
- return 1;
123
- }
124
- if (woerter.length > 1) {
125
- /*
126
- 🔴 **Der Vorschlag darf keine Optionen einsammeln** (Review-Runde 5 zu
127
- PR #25). Hier stand `argv.join("-")` -- aus `arbeitsplatz neu sabine --force`
128
- wurde damit der Rat `arbeitsplatz neu sabine---force`.
129
- *Ein Rat, der ein falsches Kommando erzeugt, ist schlimmer als keiner* --
130
- dieselbe Klasse wie die Absagen aus Runde 1 und 2, nur kleiner.
131
-
132
- Deshalb: Sieht irgendein Wort wie eine Option aus, gibt es gar keinen
133
- Vorschlag. Ein Mensch, der `--force` tippt, sucht keine Schreibhilfe fuer
134
- seinen Vornamen.
135
-
136
- ⚠️ Seit dem 2026-08-26 stehen die `--`-Optionen schon gar nicht mehr in
137
- `woerter` (der Schnitt oben). Der Filter bleibt trotzdem: Er faengt das
138
- EINFACHE `-` (`-force`), das kein Schnitt-Zeichen ist und das der
139
- Vorschlag genauso wenig einsammeln darf.
140
- */
141
- const einfacheWoerter = woerter.filter((w) => !w.startsWith("-"));
142
- /*
143
- 🔴 **Die getippten Optionen reisen mit** (Review-Befund 3, 2026-08-26).
144
- Ohne sie riet die Zeile zu einem Befehl, der bei einem Menschen in zwei
145
- Firmen sofort wieder in dieselbe Absage laeuft -- s. `alsBefehlszeile`.
146
- */
147
- const optionsAnhang = optionsArgv.length === 0 ? "" : ` ${alsBefehlszeile(optionsArgv)}`;
148
- const vorschlag = einfacheWoerter.length === woerter.length
149
- ? t.nameSuggestion(befehlIn("arbeitsplatz neu", s), einfacheWoerter.join("-"), optionsAnhang) // kein-anzeigetext: Befehlsschlüssel
150
- : "";
151
- console.error(t.onlyOneName(woerter[1]) + vorschlag + hilfe(s));
102
+ // Name und Firmen-Optionen, mit denselben Namens-Absagen wie `werkbank neu` (gemeinsam seit 26.10.2026).
103
+ const gelesen = nameUndFirma(argv, "arbeitsplatz neu", t, hilfe(s), s); // kein-anzeigetext: Befehlsschlüssel
104
+ if (gelesen === null)
152
105
  return 1;
153
- }
154
- /*
155
- 🔑 Erst JETZT die Optionen -- nach den Namens-Absagen, damit ein Mensch mit
156
- zwei Vornamen die Schreibhilfe bekommt und nicht eine Options-Meldung.
157
- */
158
- const zerlegt = zerlege(optionsArgv, FIRMEN_OPTIONEN, s);
159
- if (!zerlegt.ok) {
160
- console.error(`${zerlegt.meldung}\n${hilfe(s)}`);
161
- return 1;
162
- }
163
- const wunsch = {
164
- firma: zerlegt.werte.optionen.firma,
165
- firmaId: zerlegt.werte.optionen["firma-id"],
166
- };
167
- /*
168
- Der Name wird UMGESCHRIEBEN, nicht abgelehnt (s. `werkbankOrdnerAus`) --
169
- "Sabine Mueller" ist ein gueltiger Name, nur kein gueltiger Ordner. `null`
170
- heisst: Nach dem Umschreiben war nichts mehr uebrig (etwa bei "!!!"). Das ist
171
- HIER ein Fehler, weil ein Mensch etwas getippt hat und eine Antwort verdient
172
- -- der Server schweigt im selben Fall und bleibt beim Vordruck.
173
- */
174
- // 🌐 Teil 2: Hier geht es nur um „bleibt etwas uebrig?" -- das Praefix ist egal. Welches es
175
- // wird, sagt erst die Lieferung (die Werkbank vom Server traegt die Kontosprache), s. unten.
176
- if (nameArg !== undefined) {
177
- if (werkbankOrdnerAus(nameArg, "de") === null) {
178
- console.error(t.unusableName1(nameArg));
179
- console.error(t.unusableName2);
180
- console.error("");
181
- console.error(t.unusableName3(befehlIn("arbeitsplatz neu", s))); // kein-anzeigetext: Befehlsschlüssel
182
- return 1;
183
- }
184
- }
106
+ const { nameArg, wunsch } = gelesen;
185
107
  /*
186
108
  🔴 **HIER ENTSCHEIDET SICH, OB UEBERHAUPT ETWAS ENTSTEHT** (Punkt A-3).
187
109
  Gehoert der Mensch zu mehreren Firmen und hat keine genannt, antwortet die
@@ -227,9 +149,7 @@ export async function arbeitsplatzNeu(argv) {
227
149
  }
228
150
  /*
229
151
  🔑 Der Zielordner ist IMMER `daten.wurzel` ("KI-Arbeitsplatz") und kommt damit
230
- vom Server statt aus der Eingabe. Die Pfad-Pruefung darueber ist deshalb
231
- nicht mehr die Vorsicht fuer einen selten benutzten Zweig, sondern der
232
- einzige Schutz, den dieser Wert ueberhaupt hat.
152
+ vom Server statt aus der Eingabe (die Pfad-Pruefung darueber ist sein einziger Schutz).
233
153
 
234
154
  Der Name des Menschen wirkt eine Ebene tiefer, auf die Werkbank. Der Server
235
155
  hat sie schon benannt (aus der Anmelde-Adresse); ein mitgegebener Name
@@ -343,9 +263,9 @@ export async function arbeitsplatzNeu(argv) {
343
263
  `kimal3 kontext hoch` -- nicht an der Zahl.** Hier stand bis zur zweiten
344
264
  Review-Runde „der deutet Rueckgabewert 1 als ‚du bist kein Admin'", und
345
265
  genau diese Gleichung hat dieselbe Scheibe an zwei anderen Stellen als
346
- falsch geloescht: `kontext hoch` gibt `1` auch dann zurueck, wenn die Firma
347
- erfolgreich gespeichert hat und nur der lokale Merkzettel scheiterte
348
- (`kontext-hoch.ts`, Merkzettel-Zweig). *Ein Kommentar, der ein Verhalten erklaert, wird
266
+ falsch geloescht: `kontext hoch` gab damals `1` auch dann zurueck, wenn die
267
+ Firma erfolgreich gespeichert hatte und nur der lokale Merkzettel scheiterte
268
+ (seit B-d, 17.09., ist das `0`; `kontext-hoch.ts`, Merkzettel-Zweig). *Ein Kommentar, der ein Verhalten erklaert, wird
349
269
  beim naechsten Lesen abgenickt -- dieser haette den Fehler zurueckgeholt,
350
270
  den der Commit daneben entfernt.*
351
271
 
@@ -2,10 +2,8 @@ import { zerlege } from "../argumente.js";
2
2
  import { sprache } from "../sprache.js";
3
3
  import { ALLGEMEIN_TEXTE } from "../texte/allgemein.js";
4
4
  import { PROJEKTE_TEXTE } from "../texte/projekte.js";
5
- import { feld, ruf } from "../api.js";
6
- import { schlussFehler } from "../fehler.js";
7
- import { firmenIdZeilen, firmenZeilen } from "../firmen-liste.js";
8
- import { BASIS_URL } from "../anmeldung.js";
5
+ import { ruf } from "../api.js";
6
+ import { absage } from "./kontext-gemeinsam.js";
9
7
  const ERLAUBT = ["firma", "firma-id"];
10
8
  /**
11
9
  * Die Firmendaten holen — als JSON, unveraendert.
@@ -79,67 +77,7 @@ export async function firmaDaten(argv) {
79
77
  console.log(JSON.stringify(antwort.koerper, null, 2));
80
78
  return 0;
81
79
  }
82
- const meldung = feld(antwort.koerper, "meldung") ?? a.genericFailure;
83
- // 402 VOR 401: Beide heissen "kommst hier nicht rein", verlangen aber
84
- // Gegenteiliges. Wer sie zusammenwirft, raet dem Menschen `kimal3 login` --
85
- // und der meldet sich dann erfolgreich an, ohne dass es etwas aendert.
86
- if (antwort.status === 402) {
87
- console.error(meldung);
88
- return 4;
89
- }
90
- if (antwort.status === 401) {
91
- console.error(`${meldung}${a.pleaseSignIn}`);
92
- return 2;
93
- }
94
- if (antwort.status === 409) {
95
- const grund = feld(antwort.koerper, "grund");
96
- const koerper = antwort.koerper;
97
- const namen = Array.isArray(koerper?.firmen)
98
- ? koerper.firmen.filter((x) => typeof x === "string")
99
- : [];
100
- console.error(meldung);
101
- /*
102
- 🔑 **Bei gleichnamigen Firmen fuehrt der Weg ueber die ID** (B-5, zweite
103
- Haelfte). Die Route legt dafuer `auswahl` bei -- und erst damit ist die
104
- Absage keine Sackgasse mehr.
105
- */
106
- const auswahl = Array.isArray(koerper.auswahl)
107
- ? koerper.auswahl.filter((x) => !!x &&
108
- typeof x === "object" &&
109
- typeof x.id === "string" &&
110
- typeof x.name === "string")
111
- : [];
112
- if (auswahl.length > 0) {
113
- console.error(a.sameNameUseId);
114
- firmenIdZeilen(auswahl, s).forEach((z) => console.error(z));
115
- }
116
- else {
117
- // Die Namen als FERTIGE Befehle ausgeben, nicht als blosse Liste: Der
118
- // naechste Schritt ist dann Kopieren statt Abtippen -- und ein Firmenname
119
- // mit Leerzeichen braucht Anfuehrungszeichen, an die sonst niemand denkt.
120
- //
121
- // ⚠️ Welche Namen einen Befehl bekommen, entscheidet `firmenZeilen` (B-5):
122
- // Ein doppelt vergebener Name bekommt KEINEN -- er waere ein Vorschlag,
123
- // der garantiert wieder in derselben Absage endet.
124
- const zeilen = firmenZeilen(namen, s);
125
- if (zeilen.length > 0) {
126
- console.error(a.yourCompanies);
127
- zeilen.forEach((z) => console.error(z));
128
- }
129
- }
130
- if (grund === "keine-firma") {
131
- console.error(a.createCompany(BASIS_URL));
132
- }
133
- return 1;
134
- }
135
- // ⚠️ Die 503 landet HIER und nicht oben bei der 402: Rueckgabewert 3 heisst
136
- // "nochmal versuchen kann helfen", 4 heisst laut SKILL.md ausdruecklich
137
- // "nicht wiederholen". Ein Aussetzer gehoert in den ersten Topf.
138
- //
139
- // 🔑 Den Rest entscheidet `schlussFehler` fuer alle Befehle gemeinsam --
140
- // insbesondere die `404` (B-7), die hier frueher auf die `1` fiel und damit
141
- // "Eingabe passt nicht" meldete, obwohl die Eingabe stimmte.
142
- const schluss = schlussFehler(antwort.status, meldung, s);
143
- console.error(schluss.text);
144
- return schluss.wert;
80
+ // Alles ausser 200: dieselbe Absage wie bei `kontext runter`/`kontext hoch` (bis 26.10.2026 hier ein drittes Mal
81
+ // abgeschrieben) -- 402 vor 401, 409 mit den eigenen Firmen als fertigen `firma daten`-Zeilen, Rest an `schlussFehler`.
82
+ return absage(antwort, "firma daten", ""); // kein-anzeigetext: Befehlsschluessel
145
83
  }
@@ -8,7 +8,7 @@ import { KONTEXT_TEXTE } from "../texte/kontext.js";
8
8
  import { BASIS_URL } from "../anmeldung.js";
9
9
  import { findeArbeitsplatz } from "../kontext-dateien.js";
10
10
  import { ORDNER_NAMEN } from "../kontext-stand.js";
11
- import { angeboteneFirmen } from "../firmen-liste.js";
11
+ import { angeboteneFirmen, firmenIdZeilen, firmenZeilen } from "../firmen-liste.js";
12
12
  /**
13
13
  * Was `kontext runter` (`kontext-runter.ts`) und `kontext hoch`
14
14
  * (`kontext-hoch.ts`) TEILEN -- die `--firma`/`--firma-id`-Optionen und die
@@ -131,13 +131,10 @@ export const ERLAUBT = ["firma", "firma-id"];
131
131
  * (`kontextRunter`) und POST (`kontextHoch`) gleichermassen. `befehl` sagt,
132
132
  * welcher der beiden fuer die kopierfertigen Zeilen richtig ist.
133
133
  *
134
- * 🔑 Dieselbe Struktur wie `firmaDaten` in `firma-daten.ts` -- 402 vor 401
135
- * (verlangen Gegenteiliges), 409 nennt die eigenen Firmen bzw. die IDs bei
136
- * gleichnamigen, der Rest geht an `schlussFehler`. Die kopierfertigen Zeilen
137
- * unten sind eine BEWUSSTE Kopie und keine Wiederverwendung von
138
- * `firmen-liste.ts`: Dessen `firmenZeilen`/`firmenIdZeilen` schreiben den
139
- * Befehl `kimal3 firma daten …` fest in die Zeile -- fuer `kontext *`
140
- * waere das ein falscher, nicht ausfuehrbarer Rat.
134
+ * 🔑 Seit dem 26.10.2026 auch fuer `firma daten` (`/api/cli/firma`), das bis dahin dieselbe Logik ein drittes Mal
135
+ * abschrieb: 402 vor 401 (verlangen Gegenteiliges), 409 nennt die eigenen Firmen bzw. die IDs bei gleichnamigen,
136
+ * der Rest geht an `schlussFehler`. Die kopierfertigen Zeilen kommen aus `firmen-liste.ts`; `befehl` sagt, welcher
137
+ * Befehl in der Zeile stehen muss.
141
138
  *
142
139
  * 🔴 **`409` heisst hier ZWEI verschiedene Dinge** (Task 9): Bei GET und bei
143
140
  * POST kommt ein `409` mit `grund` von `waehleFirma` (mehrdeutige Firmenwahl,
@@ -178,10 +175,10 @@ export function absage(antwort, befehl, optionen) {
178
175
  : [];
179
176
  if (auswahl.length > 0) {
180
177
  console.error(t.sameNameUseId);
181
- firmenIdZeilenKontext(auswahl, befehl, optionen, s).forEach((z) => console.error(z));
178
+ firmenIdZeilen(auswahl, befehl, optionen, s).forEach((z) => console.error(z));
182
179
  }
183
180
  else {
184
- const zeilen = firmenZeilenKontext(namen, befehl, optionen, s);
181
+ const zeilen = firmenZeilen(namen, befehl, optionen, s);
185
182
  if (zeilen.length > 0) {
186
183
  console.error(t.yourCompanies);
187
184
  zeilen.forEach((z) => console.error(z));
@@ -196,33 +193,3 @@ export function absage(antwort, befehl, optionen) {
196
193
  console.error(schluss.text);
197
194
  return schluss.wert;
198
195
  }
199
- /**
200
- * `firmenZeilen` aus `firmen-liste.ts`, nur mit dem richtigen Befehl in der
201
- * Zeile -- s. Kommentar an `absage`.
202
- */
203
- export function firmenZeilenKontext(namen, befehl, optionen, s) {
204
- const t = ALLGEMEIN_TEXTE[s];
205
- // Befehl, schon getippte Optionen, dann der Firmen-Schalter -- in der Sprache des Satzes.
206
- const vorne = `${befehlIn(befehl, s)}${optionen}`;
207
- const anzahl = (name) => namen.filter((n) => n === name).length;
208
- const gesehen = new Set();
209
- const zeilen = [];
210
- for (const name of namen) {
211
- if (gesehen.has(name))
212
- continue;
213
- gesehen.add(name);
214
- const wieOft = anzahl(name);
215
- if (wieOft === 1) {
216
- zeilen.push(t.companyLine(vorne, schalterIn("firma", s), alsWort(name, true)));
217
- }
218
- else {
219
- zeilen.push(t.nameNotUnique(name, wieOft));
220
- }
221
- }
222
- return zeilen;
223
- }
224
- /** `firmenIdZeilen` aus `firmen-liste.ts`, nur mit dem richtigen Befehl. */
225
- export function firmenIdZeilenKontext(auswahl, befehl, optionen, s) {
226
- const vorne = `${befehlIn(befehl, s)}${optionen}`;
227
- return auswahl.map((f, i) => ALLGEMEIN_TEXTE[s].companyIdLine(vorne, schalterIn("firma-id", s), f.id, f.name, i + 1));
228
- }
@@ -0,0 +1,81 @@
1
+ import { FIRMEN_OPTIONEN } from "../arbeitsplatz-dateien.js";
2
+ import { alsBefehlszeile, trenneNamenUndOptionen, zerlege } from "../argumente.js";
3
+ import { befehlIn } from "../befehlswoerter.js";
4
+ import { werkbankOrdnerAus } from "../werkbank-name.js";
5
+ /**
6
+ * Name und Firmen-Optionen aus den Argumenten von `arbeitsplatz neu` und `werkbank neu` -- mit den drei
7
+ * Namens-Absagen, die beide Befehle wortgleich geben: Wer den einen Befehl kennt, soll beim anderen nichts Neues
8
+ * lernen muessen.
9
+ *
10
+ * 🔑 Bis zum 26.10.2026 stand das als Zwilling in beiden Befehlen, und der eigene Kommentar dort erzaehlte schon von
11
+ * einem Fehler, der nur bei einem der beiden behoben worden war. Jetzt gibt es die Absagen einmal; die Saetze kommen
12
+ * weiter aus dem Textobjekt des Befehls (`t`), das Befehlswort aus `befehl`.
13
+ *
14
+ * ⚠️ Die Absagen sprechen von einem NAMEN, nicht von einem Ordnernamen. Das ist keine Kosmetik: Wer nach der alten
15
+ * Hilfe `kimal3 arbeitsplatz neu ARNO-KI` tippt, bekommt keinen Fehler, sondern eine Werkbank namens
16
+ * `werkbank-arno-ki`. Diese Meldungen und die Hilfe sind die einzigen Stellen, an denen er das erfaehrt, bevor es
17
+ * passiert. Und die alte Hilfe von `werkbank neu` brachte `kimal3 werkbank neu werkbank-sabine` bei -- deshalb
18
+ * schneidet `werkbankOrdnerAus` ein mitgetipptes `werkbank-` wieder ab. *Alte Anleitungen ueberleben laenger als der
19
+ * Code, den sie beschreiben.*
20
+ *
21
+ * Gibt `null` zurueck, wenn eine Absage gedruckt wurde (der Befehl endet dann mit `1`).
22
+ */
23
+ export function nameUndFirma(argv, befehl, t, hilfe, s) {
24
+ /*
25
+ 🔴 **Erst schneiden, dann zerlegen** (Punkt A-3, 2026-08-26). Diese Befehle haben ein Wort OHNE `--` (den
26
+ Vornamen), und `zerlege` lehnt genau das ab. Deshalb trennt `trenneNamenUndOptionen` beim ersten `--`: davor die
27
+ Woerter, ab dort die Optionen. Die Strenge von `zerlege` bleibt damit unangetastet -- sie war teuer erkauft (B-3
28
+ und B-4, 2026-08-18).
29
+ */
30
+ const { woerter, optionen: optionsArgv } = trenneNamenUndOptionen(argv);
31
+ const nameArg = woerter[0];
32
+ if (nameArg !== undefined && nameArg.trim() === "") {
33
+ console.error(t.emptyName + hilfe);
34
+ return null;
35
+ }
36
+ if (woerter.length > 1) {
37
+ /*
38
+ 🔴 **Der Vorschlag darf keine Optionen einsammeln** (Review-Runde 5 zu PR #25). Hier stand `argv.join("-")` --
39
+ aus `arbeitsplatz neu sabine --force` wurde damit der Rat `arbeitsplatz neu sabine---force`. *Ein Rat, der ein
40
+ falsches Kommando erzeugt, ist schlimmer als keiner.* Deshalb: Sieht irgendein Wort wie eine Option aus, gibt
41
+ es gar keinen Vorschlag. Ein Mensch, der `--force` tippt, sucht keine Schreibhilfe fuer seinen Vornamen.
42
+
43
+ ⚠️ Seit dem 2026-08-26 stehen die `--`-Optionen schon gar nicht mehr in `woerter` (der Schnitt oben). Der Filter
44
+ bleibt trotzdem: Er faengt das EINFACHE `-` (`-force`), das kein Schnitt-Zeichen ist.
45
+ */
46
+ const einfacheWoerter = woerter.filter((w) => !w.startsWith("-"));
47
+ /*
48
+ 🔴 **Die getippten Optionen reisen mit** (Review-Befund 3, 2026-08-26). Ohne sie riet die Zeile zu einem
49
+ Befehl, der bei einem Menschen in zwei Firmen sofort wieder in dieselbe Absage laeuft -- s. `alsBefehlszeile`.
50
+ */
51
+ const optionsAnhang = optionsArgv.length === 0 ? "" : ` ${alsBefehlszeile(optionsArgv)}`;
52
+ const vorschlag = einfacheWoerter.length === woerter.length
53
+ ? t.nameSuggestion(befehlIn(befehl, s), einfacheWoerter.join("-"), optionsAnhang)
54
+ : "";
55
+ console.error(t.onlyOneName(woerter[1]) + vorschlag + hilfe);
56
+ return null;
57
+ }
58
+ // 🔑 Erst JETZT die Optionen -- nach den Namens-Absagen, damit ein Mensch mit zwei Vornamen die Schreibhilfe
59
+ // bekommt und nicht eine Options-Meldung.
60
+ const zerlegt = zerlege(optionsArgv, FIRMEN_OPTIONEN, s);
61
+ if (!zerlegt.ok) {
62
+ console.error(`${zerlegt.meldung}\n${hilfe}`);
63
+ return null;
64
+ }
65
+ /*
66
+ Der Name wird UMGESCHRIEBEN, nicht abgelehnt (s. `werkbankOrdnerAus`) -- "Sabine Mueller" ist ein gueltiger
67
+ Name, nur kein gueltiger Ordner. `null` heisst: Nach dem Umschreiben war nichts mehr uebrig (etwa bei "!!!"). Das
68
+ ist HIER ein Fehler, weil ein Mensch etwas getippt hat und eine Antwort verdient -- der Server schweigt im selben
69
+ Fall und bleibt beim Vordruck.
70
+ */
71
+ // 🌐 Teil 2: Hier geht es nur um „bleibt etwas uebrig?" -- das Praefix ist egal. Welches es wird, sagt erst die
72
+ // Lieferung (die Werkbank vom Server traegt die Kontosprache).
73
+ if (nameArg !== undefined && werkbankOrdnerAus(nameArg, "de") === null) {
74
+ console.error(t.unusableName1(nameArg));
75
+ console.error(t.unusableName2);
76
+ console.error("");
77
+ console.error(t.unusableName3(befehlIn(befehl, s)));
78
+ return null;
79
+ }
80
+ return { nameArg, wunsch: { firma: zerlegt.werte.optionen.firma, firmaId: zerlegt.werte.optionen["firma-id"] } };
81
+ }
@@ -494,8 +494,9 @@ function leseWennMoeglich(pfad) {
494
494
  * Entfernt Befehlsdateien, die frueher einmal ausgeliefert wurden.
495
495
  *
496
496
  * 🔴 **Warum das eine eigene, exportierte Funktion ist** (Review-Runde 3,
497
- * 2026-08-18): `VERALTETE_BEFEHLE` ist leer, also ist diese Schleife noch nie
498
- * gelaufen -- und `check:cli` "belegte" sie nur ueber eine Textsuche. Genau
497
+ * 2026-08-18): `VERALTETE_BEFEHLE` war damals leer, die Schleife also noch nie
498
+ * gelaufen (seit 28.08. steht `k3firma.md` darin) -- und `check:cli` "belegte"
499
+ * sie nur ueber eine Textsuche. Genau
499
500
  * die Falle aus der Projekt-Notiz *wache-am-echten-fehlerfall-pruefen*.
500
501
  * Mit dem Zielordner als Parameter laesst sie sich gegen einen Wegwerf-Ordner
501
502
  * mit einer echten Datei fahren.
@@ -1,7 +1,8 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
- import { FIRMEN_OPTIONEN, dateienSchreiben, istUnsichererOrdnername, vorlageHolen, werkbankOrdner, } from "../arbeitsplatz-dateien.js";
4
- import { alsBefehlszeile, alsWort, trenneNamenUndOptionen, zerlege } from "../argumente.js";
3
+ import { dateienSchreiben, istUnsichererOrdnername, vorlageHolen, werkbankOrdner, } from "../arbeitsplatz-dateien.js";
4
+ import { alsWort } from "../argumente.js";
5
+ import { nameUndFirma } from "./name-und-firma.js";
5
6
  import { befehlIn, schalterIn } from "../befehlswoerter.js";
6
7
  import { sprache } from "../sprache.js";
7
8
  import { ARBEITSPLATZ_TEXTE } from "../texte/arbeitsplatz.js";
@@ -89,80 +90,11 @@ export async function werkbankNeu(argv) {
89
90
  console.log(hilfe(s));
90
91
  return 0;
91
92
  }
92
- /*
93
- Dieselben drei Absagen wie bei `arbeitsplatz neu` -- bewusst wortgleich: Wer
94
- den einen Befehl kennt, soll beim anderen nichts Neues lernen muessen.
95
-
96
- ⚠️ Sie sprechen seit dem 2026-08-20 von einem NAMEN statt von einem
97
- Ordnernamen. Die alte Hilfe hier hat ausdruecklich
98
- `kimal3 werkbank neu werkbank-sabine` beigebracht -- deshalb schneidet
99
- `werkbankOrdnerAus` ein bereits mitgetipptes `werkbank-` wieder ab, statt
100
- `werkbank-werkbank-sabine` zu bauen. *Alte Anleitungen ueberleben laenger
101
- als der Code, den sie beschreiben.*
102
- */
103
- /*
104
- 🔴 **Erst schneiden, dann zerlegen** -- wortgleich zum Zwilling in
105
- `arbeitsplatz.ts` (Punkt A-3, 2026-08-26). Dieser Befehl hat ein Wort OHNE
106
- `--` (den Vornamen), und `zerlege` lehnt genau das ab.
107
- */
108
- const { woerter, optionen: optionsArgv } = trenneNamenUndOptionen(argv);
109
- const nameArg = woerter[0];
110
- if (nameArg !== undefined && nameArg.trim() === "") {
111
- console.error(t.emptyName + hilfe(s));
93
+ // Name und Firmen-Optionen, mit denselben Namens-Absagen wie `arbeitsplatz neu` (gemeinsam seit 26.10.2026).
94
+ const gelesen = nameUndFirma(argv, "werkbank neu", t, hilfe(s), s); // kein-anzeigetext: Befehlsschlüssel
95
+ if (gelesen === null)
112
96
  return 1;
113
- }
114
- if (woerter.length > 1) {
115
- /*
116
- 🔴 **Der Vorschlag darf keine Optionen einsammeln** (Review-Runde 5 zu
117
- PR #25). Hier stand `argv.join("-")` -- aus `werkbank neu sabine --force`
118
- wurde damit der Rat `werkbank neu sabine---force`.
119
- *Ein Rat, der ein falsches Kommando erzeugt, ist schlimmer als keiner* --
120
- dieselbe Klasse wie die Absagen aus Runde 1 und 2, nur kleiner.
121
-
122
- Deshalb: Sieht irgendein Wort wie eine Option aus, gibt es gar keinen
123
- Vorschlag. Ein Mensch, der `--force` tippt, sucht keine Schreibhilfe fuer
124
- seinen Vornamen.
125
-
126
- ⚠️ Seit dem 2026-08-26 stehen die `--`-Optionen schon gar nicht mehr in
127
- `woerter` (der Schnitt oben). Der Filter bleibt trotzdem: Er faengt das
128
- EINFACHE `-` (`-force`), das kein Schnitt-Zeichen ist.
129
- */
130
- const einfacheWoerter = woerter.filter((w) => !w.startsWith("-"));
131
- /*
132
- 🔴 **Die getippten Optionen reisen mit** (Review-Befund 3, 2026-08-26).
133
- Ohne sie riet die Zeile zu einem Befehl, der bei einem Menschen in zwei
134
- Firmen sofort wieder in dieselbe Absage laeuft -- s. `alsBefehlszeile`.
135
- */
136
- const optionsAnhang = optionsArgv.length === 0 ? "" : ` ${alsBefehlszeile(optionsArgv)}`;
137
- const vorschlag = einfacheWoerter.length === woerter.length
138
- ? t.nameSuggestion(befehlIn("werkbank neu", s), einfacheWoerter.join("-"), optionsAnhang) // kein-anzeigetext: Befehlsschlüssel
139
- : "";
140
- console.error(t.onlyOneName(woerter[1]) + vorschlag + hilfe(s));
141
- return 1;
142
- }
143
- // 🔑 Erst NACH den Namens-Absagen -- s. Zwilling in `arbeitsplatz.ts`.
144
- const zerlegt = zerlege(optionsArgv, FIRMEN_OPTIONEN, s);
145
- if (!zerlegt.ok) {
146
- console.error(`${zerlegt.meldung}\n${hilfe(s)}`);
147
- return 1;
148
- }
149
- const wunsch = {
150
- firma: zerlegt.werte.optionen.firma,
151
- firmaId: zerlegt.werte.optionen["firma-id"],
152
- };
153
- // `null` heisst: nach dem Umschreiben blieb kein Zeichen uebrig, das in einem
154
- // Ordnernamen stehen darf. Ein Mensch hat hier getippt und verdient eine
155
- // Antwort -- der Server schweigt im selben Fall und bleibt beim Vordruck.
156
- // 🌐 Teil 2: nur „bleibt etwas uebrig?" -- das Praefix kommt erst aus der Lieferung, s. unten.
157
- if (nameArg !== undefined) {
158
- if (werkbankOrdnerAus(nameArg, "de") === null) {
159
- console.error(t.unusableName1(nameArg));
160
- console.error(t.unusableName2);
161
- console.error("");
162
- console.error(t.unusableName3(befehlIn("werkbank neu", s))); // kein-anzeigetext: Befehlsschlüssel
163
- return 1;
164
- }
165
- }
97
+ const { nameArg, wunsch } = gelesen;
166
98
  /*
167
99
  🔴 **HIER ENTSCHEIDET SICH, OB UEBERHAUPT ETWAS ENTSTEHT** (Punkt A-3) --
168
100
  dieselbe Stelle wie beim Zwilling: Bei mehreren Firmen ohne Angabe kommt
@@ -32,9 +32,15 @@ import { ALLGEMEIN_TEXTE } from "./texte/allgemein.js";
32
32
  * die ID statt ueber den Namen -- eine eigene Entscheidung, keine Fussnote.
33
33
  * Diese Funktion sorgt nur dafuer, dass niemand mehr einen Ausweg vorgespielt
34
34
  * bekommt, den es nicht gibt.
35
+ *
36
+ * `befehl` und `optionen` sagen, welcher Befehl in der Zeile steht: `firma daten` ohne weitere Optionen, oder
37
+ * `kontext runter`/`kontext hoch` mit dem, was schon getippt war (bis 26.10.2026 eine zweite, gleiche Fassung in
38
+ * `kontext-gemeinsam.ts`).
35
39
  */
36
- export function firmenZeilen(namen, s) {
40
+ export function firmenZeilen(namen, befehl, optionen, s) {
37
41
  const t = ALLGEMEIN_TEXTE[s];
42
+ // Befehl, schon getippte Optionen, dann der Firmen-Schalter -- in der Sprache des Satzes.
43
+ const vorne = `${befehlIn(befehl, s)}${optionen}`;
38
44
  const anzahl = (name) => namen.filter((n) => n === name).length;
39
45
  const gesehen = new Set();
40
46
  const zeilen = [];
@@ -47,7 +53,7 @@ export function firmenZeilen(namen, s) {
47
53
  // Fertiger Befehl statt blosser Liste: Der naechste Schritt ist dann
48
54
  // Kopieren statt Abtippen -- und ein Name mit Leerzeichen braucht
49
55
  // Anfuehrungszeichen, an die sonst niemand denkt.
50
- zeilen.push(t.companyLine(befehlIn("firma daten", s), schalterIn("firma", s), alsWort(name, true))); // kein-anzeigetext: Befehlsschluessel
56
+ zeilen.push(t.companyLine(vorne, schalterIn("firma", s), alsWort(name, true)));
51
57
  }
52
58
  else {
53
59
  zeilen.push(t.nameNotUnique(name, wieOft));
@@ -70,8 +76,9 @@ export function firmenZeilen(namen, s) {
70
76
  * kopierfertige Befehle. Der Name steht als Kommentar dahinter -- eine nackte
71
77
  * UUID sagt einem Menschen nichts, und zwei davon untereinander noch weniger.
72
78
  */
73
- export function firmenIdZeilen(auswahl, s) {
74
- return auswahl.map((f, i) => ALLGEMEIN_TEXTE[s].companyIdLine(befehlIn("firma daten", s), schalterIn("firma-id", s), f.id, f.name, i + 1));
79
+ export function firmenIdZeilen(auswahl, befehl, optionen, s) {
80
+ const vorne = `${befehlIn(befehl, s)}${optionen}`;
81
+ return auswahl.map((f, i) => ALLGEMEIN_TEXTE[s].companyIdLine(vorne, schalterIn("firma-id", s), f.id, f.name, i + 1));
75
82
  }
76
83
  /**
77
84
  * Die Namen der eigenen Firmen aus einer `409`-Antwort -- was nicht als Text
@@ -116,7 +116,6 @@ export function gemerkteMitUnlesbaren() {
116
116
  });
117
117
  return { liste: ordneGemerkte(liste), unlesbar: unlesbar.sort() };
118
118
  }
119
- export const gemerkte = () => gemerkteMitUnlesbaren().liste;
120
119
  export const gemerktOrdner = () => ORDNER;
121
120
  export function vergiss(datei) {
122
121
  try {
@@ -52,19 +52,11 @@ function schreibeWacheDatei(inhalt) {
52
52
  /* Merkzettel nicht schreibbar -- der Befehl laeuft trotzdem durch. */
53
53
  }
54
54
  }
55
- /** Ist die Wache dauerhaft abgeschaltet? Wirft nie. */
56
- export function wacheIstAus() {
57
- return leseWacheDatei()?.aus === true;
58
- }
59
55
  /** Wache an- oder abschalten. Der Tages-Merkzettel ("zuletzt") bleibt erhalten. */
60
56
  export function setzeWache(an) {
61
57
  const bisher = leseWacheDatei() ?? {};
62
58
  schreibeWacheDatei({ ...bisher, aus: !an });
63
59
  }
64
- /** Ist die Wache heute schon gelaufen? Wirft nie. */
65
- export function schonGelaufenAm(heute) {
66
- return leseWacheDatei()?.zuletzt === heute;
67
- }
68
60
  /** Merken, dass die Wache heute gelaufen ist. Der Ausschalter bleibt erhalten. */
69
61
  export function merkeLauf(heute) {
70
62
  const bisher = leseWacheDatei() ?? {};
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
- {
2
- "name": "kimal3",
3
- "version": "0.9.0",
4
- "description": "Trag deine KI-Projekte dort ein, wo sie entstehen: im Terminal. · Log your AI projects where they happen: in the terminal.",
5
- "type": "module",
6
- "bin": {
7
- "kimal3": "dist/kimal3.js"
8
- },
9
- "files": [
10
- "dist",
11
- "skill",
12
- "claude-plugins",
13
- "coach",
14
- "zeigen",
15
- "README.md"
16
- ],
17
- "engines": {
18
- "node": ">=20"
19
- },
20
- "scripts": {
21
- "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
22
- "prepublishOnly": "npm run build"
23
- },
24
- "keywords": [
25
- "kimal3",
26
- "cli"
27
- ],
28
- "homepage": "https://app.kimal3.de/pro",
29
- "license": "SEE LICENSE IN README.md"
30
- }
1
+ {
2
+ "name": "kimal3",
3
+ "version": "0.9.1",
4
+ "description": "Trag deine KI-Projekte dort ein, wo sie entstehen: im Terminal. · Log your AI projects where they happen: in the terminal.",
5
+ "type": "module",
6
+ "bin": {
7
+ "kimal3": "dist/kimal3.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "skill",
12
+ "claude-plugins",
13
+ "coach",
14
+ "zeigen",
15
+ "README.md"
16
+ ],
17
+ "engines": {
18
+ "node": ">=20"
19
+ },
20
+ "scripts": {
21
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "kimal3",
26
+ "cli"
27
+ ],
28
+ "homepage": "https://app.kimal3.de/pro",
29
+ "license": "SEE LICENSE IN README.md"
30
+ }