kimal3 0.3.1 → 0.4.0-durchstich.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/arbeitsplatz-dateien.js +50 -0
- package/dist/befehle/einrichten.js +100 -0
- package/dist/befehle/login.js +34 -6
- package/dist/befehle/werkbank.js +7 -17
- package/dist/befehle-katalog-en.js +4 -4
- package/dist/befehle-katalog.js +2 -2
- package/dist/befehlswoerter.js +1 -0
- package/dist/browser.js +43 -0
- package/dist/kimal3.js +8 -2
- package/dist/nachricht.js +5 -1
- package/dist/sprache.js +28 -4
- package/dist/texte/einrichten-marken.js +9 -0
- package/dist/texte/einrichten.js +35 -0
- package/package.json +1 -1
- package/skill/SKILL.md +6 -6
- package/skill/en/SKILL.md +6 -5
|
@@ -315,6 +315,56 @@ export function dateienSchreiben(dateien, ordnerName, hinweis) {
|
|
|
315
315
|
}
|
|
316
316
|
return 0;
|
|
317
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Welche Dateien der Lieferung noch nicht auf der Platte liegen (Spec 2026-10-11, S-7).
|
|
320
|
+
* Rein, damit `check:cli` es ohne Platte misst; `gibtEs` fragt `existsSync`.
|
|
321
|
+
*/
|
|
322
|
+
export function welcheFehlen(dateien, gibtEs) {
|
|
323
|
+
return dateien.filter((d) => !gibtEs(d.pfad));
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Für `kimal3 einrichten`: schreibt NUR, was fehlt, und fasst nichts an, was schon liegt
|
|
327
|
+
* (Spec 2026-10-11, S-7, L-26, L-62) -- anders als `dateienSchreiben`, das bei einem nicht
|
|
328
|
+
* leeren Ordner absagt. Ein zweiter Lauf heilt so einen halb geschriebenen Arbeitsplatz.
|
|
329
|
+
*
|
|
330
|
+
* 🔑 `flag: "wx"`: Legt ein zweiter, gleichzeitiger Lauf (L-70) dieselbe Datei an, scheitert
|
|
331
|
+
* dieser hier, statt sie zu überschreiben. Dieselben Absagen und Rückgabewerte wie
|
|
332
|
+
* `dateienSchreiben` (Begründungen dort): unsicherer Pfad 3, Platte 1.
|
|
333
|
+
*/
|
|
334
|
+
export function fehlendeDateienSchreiben(dateien, ziel) {
|
|
335
|
+
const t = ARBEITSPLATZ_TEXTE[sprache()];
|
|
336
|
+
if (dateien.length === 0) {
|
|
337
|
+
console.error(t.noFiles1);
|
|
338
|
+
console.error(t.noFiles2);
|
|
339
|
+
return { ok: false, wert: 3 };
|
|
340
|
+
}
|
|
341
|
+
for (const datei of dateien) {
|
|
342
|
+
if (istUnsichererPfad(datei.pfad)) {
|
|
343
|
+
console.error(t.unsafePath(datei.pfad));
|
|
344
|
+
return { ok: false, wert: 3 };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const ort = (pfad) => join(ziel, ...pfad.split("/"));
|
|
348
|
+
try {
|
|
349
|
+
if (existsSync(ziel) && !statSync(ziel).isDirectory()) {
|
|
350
|
+
console.error(t.isAFile1(ziel));
|
|
351
|
+
console.error(t.isAFile2);
|
|
352
|
+
return { ok: false, wert: 1 };
|
|
353
|
+
}
|
|
354
|
+
const fehlen = welcheFehlen(dateien, (pfad) => existsSync(ort(pfad)));
|
|
355
|
+
for (const datei of fehlen) {
|
|
356
|
+
mkdirSync(dirname(ort(datei.pfad)), { recursive: true });
|
|
357
|
+
writeFileSync(ort(datei.pfad), datei.inhalt, { encoding: "utf8", flag: "wx" });
|
|
358
|
+
}
|
|
359
|
+
return { ok: true, neu: fehlen.length, schonDa: dateien.length - fehlen.length };
|
|
360
|
+
}
|
|
361
|
+
catch (fehler) {
|
|
362
|
+
console.error(t.writeFailed(fehler instanceof Error ? fehler.message : String(fehler)));
|
|
363
|
+
console.error(t.incomplete(ziel));
|
|
364
|
+
console.error(t.writeFailedBlame);
|
|
365
|
+
return { ok: false, wert: 1 };
|
|
366
|
+
}
|
|
367
|
+
}
|
|
318
368
|
/**
|
|
319
369
|
* Ein `..`-Segment oder ein absoluter Pfad duerften das Ziel verlassen.
|
|
320
370
|
*
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { leseAnmeldung } from "../anmeldung.js";
|
|
5
|
+
import { ruf } from "../api.js";
|
|
6
|
+
import { FIRMEN_OPTIONEN, fehlendeDateienSchreiben, istUnsichererOrdnername, vorlageHolen, werkbankOrdner, } from "../arbeitsplatz-dateien.js";
|
|
7
|
+
import { zerlege } from "../argumente.js";
|
|
8
|
+
import { sprache } from "../sprache.js";
|
|
9
|
+
import { ALLGEMEIN_TEXTE } from "../texte/allgemein.js";
|
|
10
|
+
import { EINRICHTEN_TEXTE } from "../texte/einrichten.js";
|
|
11
|
+
import { anmeldenMitBrowser } from "./login.js";
|
|
12
|
+
import { skillInstall } from "./skill-install.js";
|
|
13
|
+
/**
|
|
14
|
+
* `kimal3 einrichten` / `kimal3 setup` -- der eine Befehl, den Claude beim Einstieg mit Leitung
|
|
15
|
+
* aufruft (Spec 2026-10-11, Abschnitt 0, S-1, S-2, S-7). Er verbindet, was es schon gibt:
|
|
16
|
+
* Anmeldung (mit Browser), die Vorlage von `/api/cli/arbeitsplatz`, `skill install`, und setzt
|
|
17
|
+
* die Kurs-Haken.
|
|
18
|
+
*
|
|
19
|
+
* 🔑 Der Arbeitsplatz liegt IMMER im Benutzerordner (N-8), nie im Ordner, in dem Claude gerade
|
|
20
|
+
* steht -- die Sitzung ohne Ordner ist ein Wegwerf-Ordner der App (M-L2).
|
|
21
|
+
* 🔑 Nichts wird überschrieben: Ein zweiter Lauf ergänzt nur Fehlendes (`fehlendeDateienSchreiben`).
|
|
22
|
+
*
|
|
23
|
+
* 📌 Durchstich-Fassung: ohne Coach, Logo und Suche nach einem vorhandenen Arbeitsplatz an
|
|
24
|
+
* anderer Stelle (S-8) -- das kommt mit dem zweiten Bauplan, nach dem echten Lauf.
|
|
25
|
+
*/
|
|
26
|
+
export async function einrichten(argv) {
|
|
27
|
+
const s = sprache();
|
|
28
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
29
|
+
console.log(EINRICHTEN_TEXTE[s].help);
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
const zerlegt = zerlege(argv, FIRMEN_OPTIONEN, s);
|
|
33
|
+
if (!zerlegt.ok) {
|
|
34
|
+
console.error(`${zerlegt.meldung}\n${EINRICHTEN_TEXTE[s].help}`);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
const wunsch = { firma: zerlegt.werte.optionen.firma, firmaId: zerlegt.werte.optionen["firma-id"] };
|
|
38
|
+
console.log(EINRICHTEN_TEXTE[s].start);
|
|
39
|
+
const gespeichert = leseAnmeldung();
|
|
40
|
+
if (gespeichert)
|
|
41
|
+
console.log(ALLGEMEIN_TEXTE[s].signedInAs(gespeichert.email));
|
|
42
|
+
else {
|
|
43
|
+
const angemeldet = await anmeldenMitBrowser();
|
|
44
|
+
if (angemeldet !== 0)
|
|
45
|
+
return angemeldet;
|
|
46
|
+
}
|
|
47
|
+
let holen = await vorlageHolen("einrichten", "", wunsch, null);
|
|
48
|
+
// Die gespeicherte Anmeldung galt nicht mehr (401 -> 2): einmal neu anmelden, dann noch einmal holen.
|
|
49
|
+
if (!holen.ok && holen.wert === 2 && gespeichert) {
|
|
50
|
+
const angemeldet = await anmeldenMitBrowser();
|
|
51
|
+
if (angemeldet !== 0)
|
|
52
|
+
return angemeldet;
|
|
53
|
+
holen = await vorlageHolen("einrichten", "", wunsch, null);
|
|
54
|
+
}
|
|
55
|
+
if (!holen.ok)
|
|
56
|
+
return holen.wert;
|
|
57
|
+
// 🌐 Die Antwort kann die Kontosprache umgestellt haben -- ab hier in der neuen.
|
|
58
|
+
const t = EINRICHTEN_TEXTE[sprache()];
|
|
59
|
+
const { wurzel, dateien } = holen.daten;
|
|
60
|
+
if (istUnsichererOrdnername(wurzel)) {
|
|
61
|
+
console.error(t.unsafeFolder(wurzel));
|
|
62
|
+
return 3;
|
|
63
|
+
}
|
|
64
|
+
const werkbank = werkbankOrdner(dateien);
|
|
65
|
+
if (werkbank === null) {
|
|
66
|
+
console.error(t.noWorkbank);
|
|
67
|
+
return 3;
|
|
68
|
+
}
|
|
69
|
+
const ziel = join(homedir(), wurzel);
|
|
70
|
+
const geschrieben = fehlendeDateienSchreiben(dateien, ziel);
|
|
71
|
+
if (!geschrieben.ok)
|
|
72
|
+
return geschrieben.wert;
|
|
73
|
+
console.log(t.filesWritten(geschrieben.neu, geschrieben.schonDa));
|
|
74
|
+
console.log(t.workplaceLine(ziel));
|
|
75
|
+
console.log(t.workbenchLine(werkbank));
|
|
76
|
+
const befehle = await skillInstall();
|
|
77
|
+
await kursAbhaken(gitAntwortet() ? ["beat2-werkzeuge", "beat2-kimal3", "beat2-arbeitsplatz"] : ["beat2-kimal3", "beat2-arbeitsplatz"]);
|
|
78
|
+
if (befehle !== 0)
|
|
79
|
+
return befehle;
|
|
80
|
+
console.log(t.done);
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
/** Git ist da, wenn `git --version` antwortet -- Node.js ist es, sonst liefe dieses Programm nicht. */
|
|
84
|
+
function gitAntwortet() {
|
|
85
|
+
return spawnSync("git", ["--version"], { stdio: "ignore", windowsHide: true }).status === 0;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Die Haken sind eine Zugabe: Scheitern sie, ist der Arbeitsplatz trotzdem da -- eine Zeile,
|
|
89
|
+
* kein Abbruch, und der nächste Lauf setzt sie (die Route ist dafür wiederholbar).
|
|
90
|
+
*/
|
|
91
|
+
async function kursAbhaken(schritte) {
|
|
92
|
+
const t = EINRICHTEN_TEXTE[sprache()];
|
|
93
|
+
try {
|
|
94
|
+
const a = await ruf("/api/cli/kurs", { koerper: { schritte }, mitAusweis: true }); // kein-anzeigetext: Pfad der Schnittstelle
|
|
95
|
+
console.log(a.status === 200 ? t.courseMarked : t.courseNotMarked);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
console.log(t.courseNotMarked);
|
|
99
|
+
}
|
|
100
|
+
}
|
package/dist/befehle/login.js
CHANGED
|
@@ -2,23 +2,51 @@ import { hostname } from "node:os";
|
|
|
2
2
|
import { feld, ruf } from "../api.js";
|
|
3
3
|
import { BASIS_URL, schreibeAnmeldung } from "../anmeldung.js";
|
|
4
4
|
import { sprache } from "../sprache.js";
|
|
5
|
+
import { anmeldeAdresse, oeffneImBrowser } from "../browser.js";
|
|
5
6
|
import { ALLGEMEIN_TEXTE } from "../texte/allgemein.js";
|
|
7
|
+
import { EINRICHTEN_TEXTE } from "../texte/einrichten.js";
|
|
6
8
|
import { stelleSkillUm } from "./skill-tausch.js";
|
|
7
9
|
const warte = (ms) => new Promise((f) => setTimeout(f, ms));
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// 🌐 Die Sprache wird bei JEDER Ausgabe neu gelesen: Die Abhol-Antwort nennt die Kontosprache
|
|
11
|
+
// (Task 5) und kann sie mitten im Lauf aendern -- die Adresse vorher kam noch in der alten.
|
|
12
|
+
const t = () => ALLGEMEIN_TEXTE[sprache()];
|
|
13
|
+
async function starteAnmeldung() {
|
|
12
14
|
const start = await ruf("/api/cli/login/start", { koerper: { bezeichnung: hostname() } });
|
|
13
15
|
const nutzerCode = feld(start.koerper, "nutzer_code");
|
|
14
16
|
const geraeteCode = feld(start.koerper, "geraete_code");
|
|
15
17
|
if (start.status !== 200 || !nutzerCode || !geraeteCode) {
|
|
16
18
|
console.error(t().unreachable);
|
|
17
|
-
return
|
|
19
|
+
return null;
|
|
18
20
|
}
|
|
21
|
+
return { nutzerCode, geraeteCode };
|
|
22
|
+
}
|
|
23
|
+
export async function login() {
|
|
24
|
+
const start = await starteAnmeldung();
|
|
25
|
+
if (!start)
|
|
26
|
+
return 3;
|
|
19
27
|
console.log(t().loginOpen(BASIS_URL));
|
|
20
|
-
console.log(t().loginEnterCode(nutzerCode));
|
|
28
|
+
console.log(t().loginEnterCode(start.nutzerCode));
|
|
21
29
|
console.log(t().loginWaiting);
|
|
30
|
+
return warteAufBestaetigung(start.geraeteCode);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Die Anmeldung für `kimal3 einrichten` (Spec 2026-10-11, S-2): Code und Adresse auf eigenen
|
|
34
|
+
* Zeilen, die Claude liest (Marken in `texte/einrichten-marken.ts`), und der Browser öffnet
|
|
35
|
+
* die Seite mit dem Code -- die Seite fragt dort, ob er im Claude-Fenster steht.
|
|
36
|
+
*/
|
|
37
|
+
export async function anmeldenMitBrowser() {
|
|
38
|
+
const start = await starteAnmeldung();
|
|
39
|
+
if (!start)
|
|
40
|
+
return 3;
|
|
41
|
+
const e = EINRICHTEN_TEXTE[sprache()];
|
|
42
|
+
const adresse = anmeldeAdresse(BASIS_URL, start.nutzerCode);
|
|
43
|
+
console.log(e.codeLine(start.nutzerCode));
|
|
44
|
+
console.log(e.addressLine(adresse));
|
|
45
|
+
console.log(oeffneImBrowser(adresse) ? e.browserOpened : e.browserNotOpened);
|
|
46
|
+
console.log(t().loginWaiting);
|
|
47
|
+
return warteAufBestaetigung(start.geraeteCode);
|
|
48
|
+
}
|
|
49
|
+
async function warteAufBestaetigung(geraeteCode) {
|
|
22
50
|
const endeUm = Date.now() + 10 * 60 * 1000;
|
|
23
51
|
while (Date.now() < endeUm) {
|
|
24
52
|
await warte(2000);
|
package/dist/befehle/werkbank.js
CHANGED
|
@@ -34,24 +34,14 @@ import { leseEineDatei, leseMerkzettelDatei } from "../kontext-dateien.js";
|
|
|
34
34
|
* arbeitsplatz neu → einmal pro FIRMA (die ganze Familie)
|
|
35
35
|
* werkbank neu → einmal pro MENSCH (nur der eigene Teil)
|
|
36
36
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
37
|
+
* 🔑 **Kein Pro nötig (seit 2026-10-11, Spec „Einstieg mit Leitung", S-6)**: Der Befehl
|
|
38
|
+
* holt die Vorlage über `/api/cli/arbeitsplatz`, und diese Route hat keine Pro-Tür mehr —
|
|
39
|
+
* jedes angemeldete Konto bekommt den Ordner. Nur die gemeinsamen Firmenantworten gehen
|
|
40
|
+
* ausschließlich an Pro (`kontextFuerPlan` am Server). Hier im Befehl ändert sich dafür
|
|
41
|
+
* nichts: Die Entscheidung fällt am Server.
|
|
42
42
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* bekommt er `402` und Rückgabewert `4`, und `SKILL.md` weist Claude dort
|
|
46
|
-
* ausdrücklich an, NICHT zu wiederholen.
|
|
47
|
-
*
|
|
48
|
-
* ⚠️ Das ist **keine Panne, sondern eine offene Produktfrage**: Erbt ein
|
|
49
|
-
* Firmenmitglied den Zugang, oder kauft jeder einzeln? Sie gehört Dennis, nicht
|
|
50
|
-
* diesem Befehl — deshalb steht sie in `WEITER-HIER.md` unter „Entscheidungen"
|
|
51
|
-
* und nicht als stille Erweiterung von `istPro` hier drin.
|
|
52
|
-
* *Eine Grenze, die man benennt, ist etwas anderes als eine, die man
|
|
53
|
-
* übersieht* — und sie reiht sich neben die schon bekannte: Der Angestellte hat
|
|
54
|
-
* ohnehin noch keinen gebauten Weg an den Firmen-Ordner (Stufe 2).
|
|
43
|
+
* ⚠️ Eine Grenze bleibt, unabhängig davon: Der Angestellte hat noch keinen
|
|
44
|
+
* gebauten Weg an den Firmen-Ordner (Stufe 2).
|
|
55
45
|
*
|
|
56
46
|
* 🔴 **Die wichtigste Eigenschaft: Er fasst die Firmenebene NICHT an.** Kein
|
|
57
47
|
* einziges Byte in `kontext/`, `CLAUDE.md`, `README.md`, `.gitignore` — auch
|
|
@@ -25,14 +25,14 @@ export const BEFEHLE_EN = {
|
|
|
25
25
|
],
|
|
26
26
|
},
|
|
27
27
|
"kimal3 arbeitsplatz neu": {
|
|
28
|
-
stempel: "
|
|
28
|
+
stempel: "6fd35d27",
|
|
29
29
|
befehl: "kimal3 workplace new",
|
|
30
30
|
tippen: "! kimal3 workplace new",
|
|
31
31
|
terminal: "kimal3 workplace new",
|
|
32
32
|
beschreibung: "Create the whole AI Workplace — company level and workbench",
|
|
33
33
|
rhythmusOderSatz: "once per company",
|
|
34
34
|
details: [
|
|
35
|
-
"Fetches the template from your
|
|
35
|
+
"Fetches the template from your kimal3 account and creates it here as a real folder — CLAUDE.md, routine commands, the workbench for your projects. The same package is available to download on /pro.",
|
|
36
36
|
"The folder is called AI-Workplace (KI-Arbeitsplatz for a German-speaking company). To keep several companies apart, use the folder above it, e.g. Clients/ARNO/AI-Workplace. Add your first name and your workbench inside it is called workbench-sabine: kimal3 workplace new sabine.",
|
|
37
37
|
"If the target folder already exists and isn't empty, the command stops instead of overwriting anything — your work is safe. You'll know it worked when it tells you how many files it created.",
|
|
38
38
|
"⚠️ It also creates the company level (context/ with brand, systems, security — kontext/ in a German workplace). If that already exists, you need kimal3 workbench new — otherwise you end up with a second company inside the first.",
|
|
@@ -53,7 +53,7 @@ export const BEFEHLE_EN = {
|
|
|
53
53
|
],
|
|
54
54
|
},
|
|
55
55
|
"kimal3 kontext runter": {
|
|
56
|
-
stempel: "
|
|
56
|
+
stempel: "55d21a3c",
|
|
57
57
|
befehl: "kimal3 context download",
|
|
58
58
|
tippen: "! kimal3 context download",
|
|
59
59
|
terminal: "kimal3 context download",
|
|
@@ -63,7 +63,7 @@ export const BEFEHLE_EN = {
|
|
|
63
63
|
"Fetches CLAUDE.md and the five files in context/ (kontext/ in a German workplace) from your company and puts them in your AI Workplace. Anything you've changed locally is left alone — it tells you how to send it up.",
|
|
64
64
|
"If you changed a file locally AND the company has changed it too in the meantime, it saves your version in context/before-<date>/ (kontext/vorher-<datum>/ in a German workplace) first, then replaces it — nothing gets lost.",
|
|
65
65
|
"You'll know it worked when it names the updated files or says “Your company context is up to date.”",
|
|
66
|
-
"⚠️
|
|
66
|
+
"⚠️ It needs Pro — your own or through your company. Without it you get exit code 4 — then don't retry.",
|
|
67
67
|
],
|
|
68
68
|
},
|
|
69
69
|
"kimal3 kontext fragen": {
|
package/dist/befehle-katalog.js
CHANGED
|
@@ -64,7 +64,7 @@ export const BEFEHLE = [
|
|
|
64
64
|
tippen: "! kimal3 arbeitsplatz neu",
|
|
65
65
|
terminal: "kimal3 arbeitsplatz neu",
|
|
66
66
|
details: [
|
|
67
|
-
"Holt die Vorlage
|
|
67
|
+
"Holt die Vorlage aus deinem kimal3-Konto und legt sie hier als echten Ordner an — CLAUDE.md, Takt-Befehle, die Werkbank für deine Projekte. Dasselbe Paket, das auch als Download auf /pro liegt.",
|
|
68
68
|
"Der Ordner heißt KI-Arbeitsplatz (bei einer englischsprachigen Firma AI-Workplace) — mehrere Firmen trennst du über den Ordner darüber, z. B. Kunden/ARNO/KI-Arbeitsplatz. Gib deinen Vornamen mit, dann heißt deine Werkbank darin werkbank-sabine (bei einem englischen Konto workbench-sabine): kimal3 arbeitsplatz neu sabine.",
|
|
69
69
|
"Gibt es den Zielordner schon und ist er nicht leer, bricht der Befehl ab, statt etwas zu überschreiben — deine Arbeit ist sicher. Geklappt hat es, wenn er die Zahl der angelegten Dateien nennt.",
|
|
70
70
|
"⚠️ Er legt auch die Firmenebene an (kontext/ mit Marke, Systemen, Sicherheit). Steht die schon, brauchst du kimal3 werkbank neu — sonst entsteht eine zweite Firma in der ersten.",
|
|
@@ -112,7 +112,7 @@ export const BEFEHLE = [
|
|
|
112
112
|
"Holt CLAUDE.md und die fünf Dateien in kontext/ von deiner Firma und legt sie in deinem KI-Arbeitsplatz ab. Was du selbst lokal geändert hast, fasst er nicht an — er sagt dir, wie du es einbringst.",
|
|
113
113
|
"Hast du eine Datei lokal geändert UND hat die Firma sie inzwischen auch geändert, sichert er deine Fassung zuerst nach kontext/vorher-<datum>/, bevor er sie ersetzt — nichts geht verloren.",
|
|
114
114
|
"Geklappt hat es, wenn er die aktualisierten Dateien nennt oder „Dein Firmenkontext ist aktuell“ sagt.",
|
|
115
|
-
"⚠️
|
|
115
|
+
"⚠️ Er braucht Pro — eigenes oder über deine Firma. Ohne das kommt Rückgabewert 4 — dann nicht wiederholen.",
|
|
116
116
|
],
|
|
117
117
|
},
|
|
118
118
|
{
|
package/dist/befehlswoerter.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const BEFEHLSWOERTER = {
|
|
2
|
+
einrichten: { de: "einrichten", en: "setup" }, // kein-anzeigetext: Befehlswörter (Spec Abschnitt 6)
|
|
2
3
|
login: { de: "login", en: "login" },
|
|
3
4
|
logout: { de: "logout", en: "logout" },
|
|
4
5
|
"skill install": { de: "skill install", en: "skill install" }, // kein-anzeigetext: Befehlswörter (Spec Abschnitt 6)
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { platform } from "node:os";
|
|
3
|
+
/**
|
|
4
|
+
* Öffnet eine Adresse im Standard-Browser (Spec 2026-10-11, S-2: Sabine soll keinen Code abtippen).
|
|
5
|
+
*
|
|
6
|
+
* 🔑 Ohne Shell: `rundll32 url.dll,FileProtocolHandler` statt `cmd /c start` -- `cmd` deutet
|
|
7
|
+
* `&` und `^` in der Adresse als Befehl. Dazu die Positivliste (messen-und-pruefen.md, §23):
|
|
8
|
+
* Nur http(s) und nur Zeichen, die in unseren Adressen vorkommen.
|
|
9
|
+
* ⚠️ `platform()` aus `node:os`, nicht `process.platform` -- `check:beenden` lässt hinter
|
|
10
|
+
* `process` nur eine feste Liste zu.
|
|
11
|
+
*/
|
|
12
|
+
const SICHERE_ADRESSE = /^https?:\/\/[A-Za-z0-9.-]+(:[0-9]+)?(\/[A-Za-z0-9\-._~/?=&%]*)?$/;
|
|
13
|
+
/** Die Adresse der Bestätigungsseite mit mitgebrachtem Code (eine Adresse, kein Anzeigetext). */
|
|
14
|
+
export const anmeldeAdresse = (basis, code) => `${basis}/cli?code=${encodeURIComponent(code)}`; // kein-anzeigetext: Adresse der Bestätigungsseite
|
|
15
|
+
export function browserBefehl(plattform, url) {
|
|
16
|
+
if (!SICHERE_ADRESSE.test(url))
|
|
17
|
+
return null;
|
|
18
|
+
if (plattform === "win32")
|
|
19
|
+
return { befehl: "rundll32", argumente: ["url.dll,FileProtocolHandler", url] }; // kein-anzeigetext: Windows-Aufruf
|
|
20
|
+
if (plattform === "darwin")
|
|
21
|
+
return { befehl: "open", argumente: [url] };
|
|
22
|
+
return { befehl: "xdg-open", argumente: [url] };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `true` heißt „versucht", nicht „sichtbar" -- ob ein Fenster aufging, weiß nur der Mensch.
|
|
26
|
+
* Deshalb druckt `einrichten` die Adresse IMMER mit. Der Kindprozess wird abgehängt
|
|
27
|
+
* (`unref`), damit er das Programm nicht am Leben hält; ein Fehler beim Starten (kein
|
|
28
|
+
* `xdg-open`) wird geschluckt, statt das Programm abzubrechen.
|
|
29
|
+
*/
|
|
30
|
+
export function oeffneImBrowser(url) {
|
|
31
|
+
const b = browserBefehl(platform(), url);
|
|
32
|
+
if (b === null)
|
|
33
|
+
return false;
|
|
34
|
+
try {
|
|
35
|
+
const kind = spawn(b.befehl, b.argumente, { detached: true, stdio: "ignore", windowsHide: true });
|
|
36
|
+
kind.on("error", () => { });
|
|
37
|
+
kind.unref();
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
package/dist/kimal3.js
CHANGED
|
@@ -20,11 +20,12 @@ import { WACHE_TEXTE } from "./texte/wache.js";
|
|
|
20
20
|
import { leseWacheDatei, sitzungJetzt, wacheAusgefallen } from "./wache-schalter.js";
|
|
21
21
|
import { formatiere, heuteAus, skillZustand, sollZeigen, } from "./nachricht.js";
|
|
22
22
|
import { eigeneVersion } from "./version.js";
|
|
23
|
-
import { sprache } from "./sprache.js";
|
|
23
|
+
import { merkeBefehlsSprache, sprache } from "./sprache.js";
|
|
24
24
|
import { ALLGEMEIN_TEXTE } from "./texte/allgemein.js";
|
|
25
25
|
import { hilfeText } from "./hilfe.js";
|
|
26
|
-
import { erkenneBefehl } from "./befehlswoerter.js";
|
|
26
|
+
import { BEFEHLSWOERTER, erkenneBefehl } from "./befehlswoerter.js";
|
|
27
27
|
import { arbeitsplatzNeu } from "./befehle/arbeitsplatz.js";
|
|
28
|
+
import { einrichten } from "./befehle/einrichten.js";
|
|
28
29
|
import { werkbankNeu } from "./befehle/werkbank.js";
|
|
29
30
|
import { firmaDaten } from "./befehle/firma-daten.js";
|
|
30
31
|
import { kontextFragen } from "./befehle/kontext-fragen.js";
|
|
@@ -484,6 +485,7 @@ async function fuehreAus(name, rest) {
|
|
|
484
485
|
case "projekt liste": return allein ? projektListe() : null;
|
|
485
486
|
// ⚠️ Rest wird durchgereicht, nicht auf Gleichheit geprueft: `firma daten --firma X` gehoert dazu.
|
|
486
487
|
case "firma daten": return firmaDaten(rest);
|
|
488
|
+
case "einrichten": return einrichten(rest);
|
|
487
489
|
case "arbeitsplatz neu": return arbeitsplatzNeu(rest);
|
|
488
490
|
case "werkbank neu": return werkbankNeu(rest);
|
|
489
491
|
case "kontext runter": return kontextRunter(rest);
|
|
@@ -537,6 +539,10 @@ async function haupt() {
|
|
|
537
539
|
// extra` bleibt unbekannt. Der grosse Kommentar zur Nachsicht bei `wache --irgendwas` steht
|
|
538
540
|
// unten an `fuehreAus` und gilt unveraendert.
|
|
539
541
|
const erkannt = erkenneBefehl(argv);
|
|
542
|
+
// 🌐 `setup` spricht Englisch, `einrichten` Deutsch, solange keine Kontosprache gemerkt ist --
|
|
543
|
+
// die Anleitung, die den Befehl tippt, liest seine Marken (Begruendung an `startSprache`).
|
|
544
|
+
if (erkannt?.name === "einrichten")
|
|
545
|
+
merkeBefehlsSprache(argv[0] === BEFEHLSWOERTER.einrichten.en ? "en" : "de");
|
|
540
546
|
if (erkannt) {
|
|
541
547
|
const code = await fuehreAus(erkannt.name, erkannt.rest);
|
|
542
548
|
if (code !== null)
|
package/dist/nachricht.js
CHANGED
|
@@ -259,10 +259,14 @@ export function stempelZeile(version, sprache) {
|
|
|
259
259
|
}
|
|
260
260
|
/**
|
|
261
261
|
* Die Sprache des LETZTEN Stempels (wie `stempelAus`: ein Beispiel im Text darf nicht gewinnen).
|
|
262
|
+
*
|
|
263
|
+
* 🔴 Die Vorabfassung (`0.4.0-durchstich.1`) trägt einen Zusatz hinter den Zahlen -- ohne `(?:-…)?`
|
|
264
|
+
* fand das Muster bei ihr die Sprache nie, jeder englische Stempel galt als deutsch, und der Login
|
|
265
|
+
* stellte nichts mehr um (gemessen von `check:cli-live` L1/L6 beim Task 7 des Durchstichs).
|
|
262
266
|
* Ohne Zusatz -- und bei jedem anderen Zusatz als genau `en` -- Deutsch (T4-4).
|
|
263
267
|
*/
|
|
264
268
|
export function stempelSpracheAus(inhalt) {
|
|
265
|
-
const treffer = [...inhalt.matchAll(/kimal3:erzeugt version=[0-9][0-9.]*(?: sprache=([a-z]+))?/g)];
|
|
269
|
+
const treffer = [...inhalt.matchAll(/kimal3:erzeugt version=[0-9][0-9.]*(?:-[0-9A-Za-z.]+)?(?: sprache=([a-z]+))?/g)];
|
|
266
270
|
return treffer.length > 0 && treffer[treffer.length - 1][1] === "en" ? "en" : "de";
|
|
267
271
|
}
|
|
268
272
|
/**
|
package/dist/sprache.js
CHANGED
|
@@ -40,11 +40,21 @@ export function spracheAusSystem(gebiet) {
|
|
|
40
40
|
return klein === "en" || klein.startsWith("en-") ? "en" : "de";
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
* Die Sprache beim Start (T3-2):
|
|
44
|
-
*
|
|
45
|
-
* Deutsch) -> sonst das
|
|
43
|
+
* Die Sprache beim Start (T3-2): die Sprache des getippten Befehlsworts, wo es eine traegt ->
|
|
44
|
+
* sonst der Merkzettel -> sonst Deutsch, wenn schon eine Anmeldung liegt (wer von einer aelteren
|
|
45
|
+
* Fassung aktualisiert: Sabine mit englischem Windows sah bisher immer Deutsch) -> sonst das
|
|
46
|
+
* Betriebssystem.
|
|
47
|
+
*
|
|
48
|
+
* 🔑 DAS BEFEHLSWORT GANZ VORN (Durchstich, Gesamtpruefung Punkt 3 und Review a73305f, Befund 1):
|
|
49
|
+
* `kimal3 setup` tippt nur die englische Anleitung, und die wartet auf `Sign-in code:`. Jim mit
|
|
50
|
+
* englischem Konto und deutschem Windows bekam sonst `Anmelde-Code:`. Auch VOR dem Merkzettel:
|
|
51
|
+
* Der uebersteht `logout` -- meldet Jim auf Kemals Laptop erst Kemal ab, laege dort Kemals
|
|
52
|
+
* Deutsch. Gilt nur bis zur ersten Antwort des Servers; ab da die Kontosprache
|
|
53
|
+
* (`uebernimmKontosprache`).
|
|
46
54
|
*/
|
|
47
55
|
export function startSprache(lage) {
|
|
56
|
+
if (lage.befehlsSprache !== null)
|
|
57
|
+
return lage.befehlsSprache;
|
|
48
58
|
const gemerkt = alsSprache(lage.merkzettel);
|
|
49
59
|
if (gemerkt !== null)
|
|
50
60
|
return gemerkt;
|
|
@@ -78,12 +88,24 @@ function leseMerkzettel() {
|
|
|
78
88
|
}
|
|
79
89
|
let gemerkt;
|
|
80
90
|
let aktuell = null;
|
|
91
|
+
let befehlsSprache = null;
|
|
92
|
+
/**
|
|
93
|
+
* Der Verteiler meldet hier die Sprache des getippten Befehlsworts -- nur fuer `einrichten`/`setup`,
|
|
94
|
+
* den einen Befehl, den eine Anleitung auf einem frischen Rechner aufruft und dessen Ausgabe sie
|
|
95
|
+
* liest. Er laeuft vor der ersten Ausgabe; `aktuell` wird trotzdem verworfen, damit die Reihenfolge
|
|
96
|
+
* nicht stillschweigend daran haengt.
|
|
97
|
+
*/
|
|
98
|
+
export function merkeBefehlsSprache(s) {
|
|
99
|
+
befehlsSprache = s;
|
|
100
|
+
aktuell = null;
|
|
101
|
+
}
|
|
81
102
|
/** Die Sprache dieses Laufs. */
|
|
82
103
|
export function sprache() {
|
|
83
104
|
if (aktuell === null) {
|
|
84
105
|
gemerkt = leseMerkzettel();
|
|
85
106
|
aktuell = startSprache({
|
|
86
107
|
merkzettel: gemerkt,
|
|
108
|
+
befehlsSprache,
|
|
87
109
|
angemeldet: leseAnmeldung() !== null,
|
|
88
110
|
systemGebiet: Intl.DateTimeFormat().resolvedOptions().locale,
|
|
89
111
|
});
|
|
@@ -97,10 +119,12 @@ export function sprache() {
|
|
|
97
119
|
*/
|
|
98
120
|
export function uebernimmKontosprache(kopf) {
|
|
99
121
|
sprache();
|
|
122
|
+
// 🔑 `aktuell` folgt dem Kopf auch dann, wenn der Merkzettel schon dieselbe Sprache traegt --
|
|
123
|
+
// sonst bliebe ein Lauf, der mit dem Befehlswort begann (`setup` bei deutschem Konto), englisch.
|
|
124
|
+
aktuell = alsSprache(kopf) ?? aktuell;
|
|
100
125
|
const neu = nachAntwort(gemerkt, kopf);
|
|
101
126
|
if (neu === null)
|
|
102
127
|
return;
|
|
103
|
-
aktuell = neu;
|
|
104
128
|
gemerkt = neu;
|
|
105
129
|
try {
|
|
106
130
|
// ⚠️ `mode: 0o700` wie in anmeldung.ts und gesehen.ts -- wer den Ordner zuerst anlegt, legt
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Die Zeilenanfaenge, an denen Claude die Ausgabe von `kimal3 einrichten` liest
|
|
3
|
+
* (Anleitung `/einrichten` und `/setup`, Schritt 3). Eine Stelle fuer beide: Das
|
|
4
|
+
* Programm druckt sie, die Anleitung nennt sie, `check:kurs` haelt beide gleich.
|
|
5
|
+
*/
|
|
6
|
+
export const EINRICHTEN_MARKEN = {
|
|
7
|
+
de: { code: "Anmelde-Code:", adresse: "Adresse:", arbeitsplatz: "Arbeitsplatz:", werkbank: "Werkbank:", angemeldet: "Angemeldet als" },
|
|
8
|
+
en: { code: "Sign-in code:", adresse: "Address:", arbeitsplatz: "Workplace:", werkbank: "Workbench:", angemeldet: "Signed in as" },
|
|
9
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { EINRICHTEN_MARKEN } from "./einrichten-marken.js";
|
|
2
|
+
const M = EINRICHTEN_MARKEN;
|
|
3
|
+
const de = {
|
|
4
|
+
codeLine: (code) => `\n ${M.de.code} ${code}`,
|
|
5
|
+
addressLine: (url) => ` ${M.de.adresse} ${url}`,
|
|
6
|
+
browserOpened: " Ich öffne die Seite gerade in deinem Browser. Bestätige dort nur, wenn derselbe Code dasteht.",
|
|
7
|
+
browserNotOpened: " Öffne die Adresse oben in deinem Browser. Bestätige dort nur, wenn derselbe Code dasteht.",
|
|
8
|
+
help: "\n kimal3 einrichten [--firma \"Name\" | --firma-id \"…\"]\n\n Meldet dich an (der Browser öffnet sich mit deinem Code), legt deinen\n KI-Arbeitsplatz in deinem Benutzerordner an, richtet die /k3-Befehle und\n die Wache ein und hakt die Schritte im Kurs ab. Ein zweiter Lauf ergänzt\n nur, was fehlt.\n",
|
|
9
|
+
start: "\nIch richte deinen KI-Arbeitsplatz ein.",
|
|
10
|
+
filesWritten: (neu, schonDa) => `\n✓ ${neu} Dateien angelegt, ${schonDa} lagen schon da.`,
|
|
11
|
+
workplaceLine: (pfad) => ` ${M.de.arbeitsplatz} ${pfad}`,
|
|
12
|
+
workbenchLine: (name) => ` ${M.de.werkbank} ${name}`,
|
|
13
|
+
courseMarked: "✓ Im Kurs abgehakt.",
|
|
14
|
+
courseNotMarked: " Die Haken im Kurs konnte ich gerade nicht setzen — der nächste Lauf holt das nach.",
|
|
15
|
+
done: "\n✓ Fertig eingerichtet.",
|
|
16
|
+
unsafeFolder: (name) => `Der Server hat einen Ordnernamen geschickt, den ich nicht anlege: ${name}. Bitte melde das an kimal3.`,
|
|
17
|
+
noWorkbank: "In der Lieferung steckt keine Werkbank. Bitte melde das an kimal3.",
|
|
18
|
+
};
|
|
19
|
+
const en = {
|
|
20
|
+
codeLine: (code) => `\n ${M.en.code} ${code}`,
|
|
21
|
+
addressLine: (url) => ` ${M.en.adresse} ${url}`,
|
|
22
|
+
browserOpened: " Opening the page in your browser now. Only confirm there if it shows the same code.",
|
|
23
|
+
browserNotOpened: " Open the address above in your browser. Only confirm there if it shows the same code.",
|
|
24
|
+
help: "\n kimal3 setup [--company \"Name\" | --company-id \"…\"]\n\n Signs you in (your browser opens with your code), creates your AI Workplace\n in your user folder, sets up the /k3 commands and the watch, and ticks off\n the course steps. A second run only adds what is missing.\n",
|
|
25
|
+
start: "\nSetting up your AI Workplace.",
|
|
26
|
+
filesWritten: (neu, schonDa) => `\n✓ ${neu} files created, ${schonDa} were already there.`,
|
|
27
|
+
workplaceLine: (pfad) => ` ${M.en.arbeitsplatz} ${pfad}`,
|
|
28
|
+
workbenchLine: (name) => ` ${M.en.werkbank} ${name}`,
|
|
29
|
+
courseMarked: "✓ Ticked off in the course.",
|
|
30
|
+
courseNotMarked: " I couldn't tick off the course steps just now — the next run will catch up.",
|
|
31
|
+
done: "\n✓ All set up.",
|
|
32
|
+
unsafeFolder: (name) => `The server sent a folder name I won't create: ${name}. Please report this to kimal3.`,
|
|
33
|
+
noWorkbank: "The delivery contains no workbench. Please report this to kimal3.",
|
|
34
|
+
};
|
|
35
|
+
export const EINRICHTEN_TEXTE = { de, en };
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -130,10 +130,10 @@ frag ihn, welche Firma.** Was hier auf dem Spiel steht: Der Ordner trägt den Na
|
|
|
130
130
|
Firma auf die Platte, nach der er nicht gefragt hat — und er merkt es nicht, weil auf seinem
|
|
131
131
|
Bildschirm nur „✓ angelegt" steht.
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
📌 **`werkbank neu` (und `arbeitsplatz neu`) funktionieren für jedes angemeldete Konto — ohne Pro.**
|
|
134
|
+
Pro entscheidet nur darüber, ob der gemeinsame Firmenkontext (die Antworten, die der Firmen-Admin
|
|
135
|
+
gepflegt hat) im Ordner steht: Ohne Pro bekommt der Ordner den Firmennamen und die leeren Fragen,
|
|
136
|
+
mit Pro zusätzlich die Antworten.
|
|
137
137
|
|
|
138
138
|
**Was dabei entsteht:** ein Firmen-Ordner mit Regeln für Marke, Systeme und Sicherheit, darin
|
|
139
139
|
eine Werkbank zum Arbeiten und ein Beispiel-Projekt zum Ansehen. `werkbank neu` legt nur den
|
|
@@ -258,8 +258,8 @@ das Wissen der gewählten Firma **auf seine Platte**, `kontext hoch` schickt sei
|
|
|
258
258
|
die gewählte Firma** — dort sehen sie alle Kolleginnen und Kollegen. *Eine falsche Wahl ist
|
|
259
259
|
hier keine falsche Anzeige, sondern eine Datei am falschen Ort.*
|
|
260
260
|
|
|
261
|
-
⚠️ **Beide Befehle brauchen
|
|
262
|
-
Rückgabewert `4`, dann **nicht wiederholen**.
|
|
261
|
+
⚠️ **Beide Befehle brauchen Pro — eigenes oder über die Firma** — ohne das
|
|
262
|
+
kommt Rückgabewert `4`, dann **nicht wiederholen**.
|
|
263
263
|
|
|
264
264
|
`kimal3 kontext hoch --ueberschreiben` erzwingt das Speichern, auch wenn die Firma inzwischen von
|
|
265
265
|
jemand anderem geändert wurde. **Nur auf ausdrücklichen Wunsch einsetzen** — sonst geht die
|
package/skill/en/SKILL.md
CHANGED
|
@@ -131,9 +131,10 @@ in its company knowledge**. If you choose yourself, you put a company's knowledg
|
|
|
131
131
|
disk that they never asked for — and they don't notice, because their screen only says
|
|
132
132
|
“✓ … files created.”
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
📌 **`workbench new` (and `workplace new`) work for every signed-in account — without Pro.**
|
|
135
|
+
Pro only decides whether the shared company context (the answers the company admin has
|
|
136
|
+
maintained) is in the folder: without Pro the folder gets the company name and the empty
|
|
137
|
+
questions, with Pro the answers as well.
|
|
137
138
|
|
|
138
139
|
**What this creates:** a company folder with rules for brand, systems and security, inside it a
|
|
139
140
|
workbench to work in and an example project to look at. `workbench new` only creates the second
|
|
@@ -255,8 +256,8 @@ company's knowledge **onto their disk**, `context upload` sends their texts **in
|
|
|
255
256
|
company** — where all their colleagues see them. *A wrong choice here is not a wrong display but a
|
|
256
257
|
file in the wrong place.*
|
|
257
258
|
|
|
258
|
-
⚠️ **
|
|
259
|
-
exit code `4`, then **do not retry**.
|
|
259
|
+
⚠️ **Both commands need Pro — their own or through their company** —
|
|
260
|
+
without it comes exit code `4`, then **do not retry**.
|
|
260
261
|
|
|
261
262
|
`kimal3 context upload --overwrite` forces the save, even if someone else has changed the company
|
|
262
263
|
context in the meantime. **Only use it when explicitly asked** — otherwise the colleague's change
|