kimal3 0.6.0 → 0.6.2

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,6 +1,7 @@
1
- {
2
- "name": "kimal3-logo",
3
- "version": "0.2.0",
4
- "description": "KImal3-Logo über dem Eingabefeld · KImal3 logo above the prompt",
5
- "author": { "name": "KImal3", "url": "https://kimal3.de" }
6
- }
1
+ {
2
+ "name": "kimal3-logo",
3
+ "version": "0.3.0",
4
+ "description": "KImal3-Logo über dem Eingabefeld, öffnet beim Start das Projekt in der App · KImal3 logo above the prompt, opens the project in the app on start",
5
+ "author": { "name": "KImal3", "url": "https://kimal3.de" },
6
+ "types": "./types/index.d.ts"
7
+ }
@@ -0,0 +1,96 @@
1
+ import { atom, read, update, type Engine, type Register } from 'claude-code'
2
+
3
+ // Beim Start von Claude Code die Projektseite im Browser der Claude-App öffnen (Dennis, 23.10.: „Automatisch bei jedem
4
+ // Start“, und zwar „in dem Browser in der Claude-Code-App“ — nicht im Standardbrowser).
5
+ // Szenario-Tabelle: docs/superpowers/specs/2026-10-23-mod-app-oeffnen.md
6
+
7
+ const APP = 'https://app.kimal3.de'
8
+ // Positivliste (messen-und-pruefen §23): Nur eine Projektnummer kommt in die Adresse, nie ein anderer Wert aus der Datei (S-4).
9
+ const PROJEKTNUMMER = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
10
+ // Der eingebaute Browser der Claude-App, wie ihn das Werkzeug `mcp__Claude_Browser__navigate` nennt.
11
+ const BROWSER = 'Claude_Browser'
12
+ const NAVIGATE = 'mcp__Claude_Browser__navigate'
13
+ const geoeffnet = atom({ plugin: 'kimal3-logo', key: 'appGeoeffnet' } as const, false)
14
+
15
+ /** Die Adresse für den Inhalt von `plan/stand.json` (`null`: keine Datei). Ohne gültige Nummer der Kurs, wie der Link in der Leiste (S-3, S-4). */
16
+ export function adresseFuer(standJson: string | null): string {
17
+ if (standJson !== null) {
18
+ try {
19
+ // Ein BOM am Anfang (Windows-Editoren) lehnt JSON.parse ab, wie in `cli/src/plan-datei.ts`.
20
+ const id: unknown = JSON.parse(standJson.replace(/^\uFEFF/, ''))?.projekt_id
21
+ if (typeof id === 'string' && PROJEKTNUMMER.test(id)) return `${APP}/projekt/${id.toLowerCase()}`
22
+ } catch {
23
+ // kaputtes JSON: wie keine Datei
24
+ }
25
+ }
26
+ return `${APP}/kurs`
27
+ }
28
+
29
+ /** Die eigene Erlaubnis (unten): nur der Aufruf dieses Mods, nur eine Adresse der App. Herausgelöst, damit `check:logo` die Grenze prüft. */
30
+ export function istEigenerAufruf(plugin: string, input: unknown): boolean {
31
+ const url = (input as { url?: unknown } | null)?.url
32
+ return plugin === 'kimal3-logo' && typeof url === 'string' && url.startsWith(`${APP}/`)
33
+ }
34
+
35
+ // Gegen zwei gleichzeitige Versuche in diesem Lauf (`session.start` und `session.attach` kurz hintereinander).
36
+ // Das Neuladen des Mods setzt die Variable zurück; dagegen hält der Merker `geoeffnet`.
37
+ let laeuft = false
38
+
39
+ /**
40
+ * Höchstens einmal je Sitzung (S-7, S-8, S-10): Der Merker in `$.state` übersteht das Neuladen des Mods. Er wird erst
41
+ * nach einem gelungenen Aufruf gesetzt — scheitert der erste Versuch, darf ein späteres `session.attach` es noch einmal.
42
+ */
43
+ async function einmalOeffnen($: Engine): Promise<void> {
44
+ if (laeuft || (await read($, geoeffnet))) return
45
+ laeuft = true
46
+ try {
47
+ if (await oeffne($)) await update($, geoeffnet, () => true)
48
+ } finally {
49
+ laeuft = false
50
+ }
51
+ }
52
+
53
+ async function oeffne($: Engine): Promise<boolean> {
54
+ const cwd = await $.session.cwd()
55
+ let stand: string | null = null
56
+ try {
57
+ stand = await $.fs.read(`${cwd}/plan/stand.json`)
58
+ } catch {
59
+ stand = null
60
+ }
61
+ // Scheitern meldet sich als Scheitern, nie im Chat (S-13): eine Zeile nur im Debug-Protokoll (`to: 'debug'` —
62
+ // ohne steht sie im Chatverlauf, Befund der Durchsicht 23.10.).
63
+ try {
64
+ const antwort = await $.mcp.call(BROWSER, 'navigate', { url: adresseFuer(stand) })
65
+ if (!antwort.isError) return true
66
+ $.ui.log(`kimal3-logo: Projektseite nicht geöffnet: ${JSON.stringify(antwort.content).slice(0, 300)}`, { to: 'debug' })
67
+ } catch (fehler) {
68
+ $.ui.log(`kimal3-logo: Projektseite nicht geöffnet: ${String(fehler).slice(0, 300)}`, { to: 'debug' })
69
+ }
70
+ return false
71
+ }
72
+
73
+ export const appOeffnen: Register = on => {
74
+ // Die eigene Erlaubnis, eng: nur der Aufruf dieses Mods, nur eine Adresse der App. Ohne sie lehnt der Auto-Modus
75
+ // ab, weil beim Start niemand um etwas gebeten hat (gemessen 23.10. mit einer Probe). Claudes eigene Aufrufe und
76
+ // jede andere Adresse entscheidet die Kette darunter wie bisher.
77
+ on('tool.check', { tool: NAVIGATE }, ($, e, next) =>
78
+ istEigenerAufruf(next.origin.plugin, e.input)
79
+ ? { decision: 'allow', reason: 'kimal3: die eigene Projektseite beim Start' }
80
+ : next(e),
81
+ )
82
+ // Den Browser gibt es nur in der Claude-App, nicht im Terminal oder in VS Code. Beim Start meldet die App
83
+ // `isInteractive: false`, ist aber schon angehängt (gemessen 23.10.: surfaces ["desktop"]). `claude -p`, Skripte,
84
+ // geplante Aufgaben, das Terminal: kein Aufruf (S-6).
85
+ on('session.start', async ($, e, next) => {
86
+ const result = await next(e)
87
+ if ((await $.session.surfaces()).includes('desktop')) await einmalOeffnen($)
88
+ return result
89
+ })
90
+ // Hängt sich die App erst nach dem Start an, kommt das Signal hier.
91
+ on('session.attach', async ($, e, next) => {
92
+ const result = await next(e)
93
+ if (e.surface === 'desktop') await einmalOeffnen($)
94
+ return result
95
+ })
96
+ }
@@ -1,80 +1,84 @@
1
- import type { Register } from 'claude-code'
2
-
3
- // Die Sprache des Kontos. `kimal3 einrichten` setzt diese Zeile beim Ablegen in `~/.kimal3/claude-plugins` und prüft
4
- // danach, dass sie gesetzt ist (`cli/src/logo-ablegen.ts`) — der Mod selbst kann keine Datei lesen.
5
- const SPRACHE: 'de' | 'en' = 'de'
6
-
7
- // Icon: docs/design/kimal3-icon-animated.svg, unverändert bis auf die viewBox:
8
- // Sie schneidet den leeren Rand um die drei Dreiecke weg (Original: 0 0 80 80).
9
- const ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="20 19 40 36" width="24" height="22">
10
- <style>
11
- /* color-scheme: sonst malt der Browser hinter die Grafik eine eigene Fläche, wenn hell/dunkel nicht zur App passt */
12
- :root { background: transparent; color-scheme: light dark; }
13
- .icon { opacity: 0.8; }
14
- .tri { fill: none; stroke: hsl(194 100% 51%); stroke-width: 2.8; stroke-linejoin: round; opacity: 0.7; }
15
- .t1 { animation: trianglePulse 3s ease-in-out infinite; }
16
- .t2 { animation: trianglePulse 3.5s ease-in-out infinite; animation-delay: .4s; }
17
- .t3 { animation: trianglePulse 4s ease-in-out infinite; animation-delay: .8s; }
18
- @keyframes trianglePulse { 0%,100% { opacity: .5; } 50% { opacity: 1; } }
19
- </style>
20
- <g class="icon">
21
- <polygon class="tri t1" points="33.28,21.6 22.08,41.0 44.48,41.0" />
22
- <polygon class="tri t2" points="46.72,21.6 35.52,41.0 57.92,41.0" />
23
- <polygon class="tri t3" points="40.00,33.36 28.80,52.76 51.20,52.76" />
24
- </g>
25
- </svg>`
26
-
27
- // Das Cyan der Marke (kimal3.de, --brand in der App)
28
- const MARKEN_CYAN = '#00C3FF'
29
- // Der Kurs, für alle (Dennis, 15.10.: „mach alle auf den Kurs“). Abgemeldete schickt die App von dort zum Login.
30
- // Fest auf `/firma` zeigte der Link Kostenlosen ohne Firma eine leere Seite (Spec L-51). Im Terminal: Strg+Klick; Claude Code hängt dort die Adresse
31
- // grau dahinter, wenn es dem Terminal keine Links zutraut — FORCE_HYPERLINK hilft nicht.
32
- const APP = 'https://app.kimal3.de/kurs'
33
-
34
- // Vor und nach der Wortmarke. Englisch: „AI Workplace“, nie „workspace“ (Spec, Global Constraints).
35
- const TEXT = {
36
- de: { vor: 'Dein ', nach: '-Arbeitsplatz', alt: 'KImal3-Logo' },
37
- en: { vor: 'Your ', nach: ' AI Workplace', alt: 'KImal3 logo' },
38
- }[SPRACHE]
39
-
40
- export const register: Register = on => {
41
- on('ui.render', { component: 'AbovePrompt' }, ($, e, next) => {
42
- if (e.props.hasSurvey) {
43
- return next(e)
44
- }
45
-
46
- // Im Terminal gibt es kein Svg: drei Dreiecke als Zeichen, die Wortmarke wie auf kimal3.de
47
- // („KI" in Schriftfarbe, „mal3" im Marken-Cyan). Das Cyan steht als Farbwert, nicht als „cyan" —
48
- // das Terminal-Cyan ist je nach Farbschema ein anderes, oft ein blasses Türkis.
49
- if (e.surface === 'terminal') {
50
- const { Box, Link, Text } = $.ui.resolve(e)
51
- return (
52
- <Box flexDirection="row" gap={1}>
53
- <Text color={MARKEN_CYAN} bold>{'▲▲▲'}</Text>
54
- <Text bold>
55
- {TEXT.vor}
56
- <Link href={APP}>
57
- {'KI'}
58
- <Text color={MARKEN_CYAN}>{'mal3'}</Text>
59
- {TEXT.nach}
60
- </Link>
61
- {'.'}
62
- </Text>
63
- </Box>
64
- )
65
- }
66
-
67
- // isInteractive: erst damit spielt die Leiste die CSS-Animation ab, sonst steht das Icon still.
68
- const { Box, Link, Svg, Text } = $.ui.resolve(e)
69
- return (
70
- <Box flexDirection="row" alignItems="center" gap={1}>
71
- <Svg source={ICON} alt={TEXT.alt} width={24} height={22} isInteractive />
72
- <Text bold>
73
- {TEXT.vor}
74
- <Link href={APP}>{`KImal3${TEXT.nach}`}</Link>
75
- {'.'}
76
- </Text>
77
- </Box>
78
- )
79
- })
80
- }
1
+ import type { Register } from 'claude-code'
2
+ import { appOeffnen } from './app-oeffnen'
3
+
4
+ // Die Sprache des Kontos. `kimal3 einrichten` setzt diese Zeile beim Ablegen in `~/.kimal3/claude-plugins` und prüft
5
+ // danach, dass sie gesetzt ist (`cli/src/logo-ablegen.ts`).
6
+ const SPRACHE: 'de' | 'en' = 'de'
7
+
8
+ // Icon: docs/design/kimal3-icon-animated.svg, unverändert bis auf die viewBox:
9
+ // Sie schneidet den leeren Rand um die drei Dreiecke weg (Original: 0 0 80 80).
10
+ const ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="20 19 40 36" width="24" height="22">
11
+ <style>
12
+ /* color-scheme: sonst malt der Browser hinter die Grafik eine eigene Fläche, wenn hell/dunkel nicht zur App passt */
13
+ :root { background: transparent; color-scheme: light dark; }
14
+ .icon { opacity: 0.8; }
15
+ .tri { fill: none; stroke: hsl(194 100% 51%); stroke-width: 2.8; stroke-linejoin: round; opacity: 0.7; }
16
+ .t1 { animation: trianglePulse 3s ease-in-out infinite; }
17
+ .t2 { animation: trianglePulse 3.5s ease-in-out infinite; animation-delay: .4s; }
18
+ .t3 { animation: trianglePulse 4s ease-in-out infinite; animation-delay: .8s; }
19
+ @keyframes trianglePulse { 0%,100% { opacity: .5; } 50% { opacity: 1; } }
20
+ </style>
21
+ <g class="icon">
22
+ <polygon class="tri t1" points="33.28,21.6 22.08,41.0 44.48,41.0" />
23
+ <polygon class="tri t2" points="46.72,21.6 35.52,41.0 57.92,41.0" />
24
+ <polygon class="tri t3" points="40.00,33.36 28.80,52.76 51.20,52.76" />
25
+ </g>
26
+ </svg>`
27
+
28
+ // Das Cyan der Marke (kimal3.de, --brand in der App)
29
+ const MARKEN_CYAN = '#00C3FF'
30
+ // Der Kurs, für alle (Dennis, 15.10.: „mach alle auf den Kurs“). Abgemeldete schickt die App von dort zum Login.
31
+ // Fest auf `/firma` zeigte der Link Kostenlosen ohne Firma eine leere Seite (Spec L-51). Im Terminal: Strg+Klick; Claude Code hängt dort die Adresse
32
+ // grau dahinter, wenn es dem Terminal keine Links zutraut — FORCE_HYPERLINK hilft nicht.
33
+ const APP = 'https://app.kimal3.de/kurs'
34
+
35
+ // Vor und nach der Wortmarke. Englisch: „AI Workplace“, nie „workspace“ (Spec, Global Constraints).
36
+ const TEXT = {
37
+ de: { vor: 'Dein ', nach: '-Arbeitsplatz', alt: 'KImal3-Logo' },
38
+ en: { vor: 'Your ', nach: ' AI Workplace', alt: 'KImal3 logo' },
39
+ }[SPRACHE]
40
+
41
+ export const register: Register = (on, options) => {
42
+ // Beim Start die Projektseite in der App öffnen (hooks/app-oeffnen.ts).
43
+ appOeffnen(on, options)
44
+
45
+ on('ui.render', { component: 'AbovePrompt' }, ($, e, next) => {
46
+ if (e.props.hasSurvey) {
47
+ return next(e)
48
+ }
49
+
50
+ // Im Terminal gibt es kein Svg: drei Dreiecke als Zeichen, die Wortmarke wie auf kimal3.de
51
+ // („KI" in Schriftfarbe, „mal3" im Marken-Cyan). Das Cyan steht als Farbwert, nicht als „cyan" —
52
+ // das Terminal-Cyan ist je nach Farbschema ein anderes, oft ein blasses Türkis.
53
+ if (e.surface === 'terminal') {
54
+ const { Box, Link, Text } = $.ui.resolve(e)
55
+ return (
56
+ <Box flexDirection="row" gap={1}>
57
+ <Text color={MARKEN_CYAN} bold>{'▲▲▲'}</Text>
58
+ <Text bold>
59
+ {TEXT.vor}
60
+ <Link href={APP}>
61
+ {'KI'}
62
+ <Text color={MARKEN_CYAN}>{'mal3'}</Text>
63
+ {TEXT.nach}
64
+ </Link>
65
+ {'.'}
66
+ </Text>
67
+ </Box>
68
+ )
69
+ }
70
+
71
+ // isInteractive: erst damit spielt die Leiste die CSS-Animation ab, sonst steht das Icon still.
72
+ const { Box, Link, Svg, Text } = $.ui.resolve(e)
73
+ return (
74
+ <Box flexDirection="row" alignItems="center" gap={1}>
75
+ <Svg source={ICON} alt={TEXT.alt} width={24} height={22} isInteractive />
76
+ <Text bold>
77
+ {TEXT.vor}
78
+ <Link href={APP}>{`KImal3${TEXT.nach}`}</Link>
79
+ {'.'}
80
+ </Text>
81
+ </Box>
82
+ )
83
+ })
84
+ }
@@ -0,0 +1,8 @@
1
+ declare module 'claude-code' {
2
+ interface PluginState {
3
+ 'kimal3-logo': {
4
+ // Die Projektseite wurde in dieser Sitzung schon geöffnet (hooks/app-oeffnen.ts) — übersteht das Neuladen des Mods.
5
+ appGeoeffnet: boolean
6
+ }
7
+ }
8
+ }
@@ -121,7 +121,7 @@ export function specKoepfeAus(json) {
121
121
  const rumpfBytes = (text) => Buffer.byteLength(JSON.stringify(text), "utf8");
122
122
  /**
123
123
  * Liest den Text jeder Spec. Jede Spec bekommt GENAU einen Eintrag: den Text oder den Grund, warum keiner da ist.
124
- * Passt der fertige Anfrage-Rumpf nicht in 4 MB, werden die groessten Texte zu `zu_gross`, bis er passt (US-16:
124
+ * Passt der fertige Anfrage-Rumpf nicht in 4 MB, werden die groessten Texte zu `zusammen_zu_gross`, bis er passt (US-16:
125
125
  * „am fertigen Anfrage-Rumpf"). `rumpf` baut genau den Rumpf, den `plan hoch` schickt (Stand, Schalter, Texte) --
126
126
  * dieselbe Funktion baut ihn dort auch, damit gemessen wird, was hinausgeht (Befund M-3).
127
127
  */
@@ -165,7 +165,7 @@ export function leseSpecTexte(ordner, specs, rumpf) {
165
165
  });
166
166
  if (groesster < 0)
167
167
  break;
168
- eintraege[groesster] = { schluessel: specs[groesster].schluessel, grund: "zu_gross", pfad: specs[groesster].datei };
168
+ eintraege[groesster] = { schluessel: specs[groesster].schluessel, grund: "zusammen_zu_gross", pfad: specs[groesster].datei };
169
169
  }
170
170
  const ohne = eintraege.flatMap((e, i) => "grund" in e ? [{ titel: specs[i].titel, grund: keineTextdatei.has(i) ? "keine_textdatei" : e.grund, pfad: e.pfad }] : []);
171
171
  return { eintraege, ohne };
@@ -38,7 +38,8 @@ const de = {
38
38
  : grund === "keine_datei" ? "keine Datei angegeben"
39
39
  : grund === "ausserhalb" ? "Pfad außerhalb des Projekts"
40
40
  : grund === "keine_textdatei" ? "keine Textdatei"
41
- : "größer als 300 KB",
41
+ : grund === "zusammen_zu_gross" ? "alle Spec-Texte zusammen größer als 4 MB"
42
+ : "größer als 300 KB",
42
43
  specTexte: (angekommen, gesamt, ohne) => `Spec-Texte hochgeladen: ${angekommen} von ${gesamt}.${ohne.length === 0 ? "" : ` Ohne Text: ${ohne.join(", ")}.`}`,
43
44
  specTextOhneEintrag: (titel, grundText) => `„${titel}“ (${grundText})`,
44
45
  idNowInFile: "projekt_id und basis stehen jetzt in plan/stand.json.",
@@ -83,7 +84,8 @@ const en = {
83
84
  : grund === "keine_datei" ? "no file given"
84
85
  : grund === "ausserhalb" ? "path outside the project"
85
86
  : grund === "keine_textdatei" ? "not a text file"
86
- : "larger than 300 KB",
87
+ : grund === "zusammen_zu_gross" ? "all spec texts together larger than 4 MB"
88
+ : "larger than 300 KB",
87
89
  specTexte: (angekommen, gesamt, ohne) => `Spec texts uploaded: ${angekommen} of ${gesamt}.${ohne.length === 0 ? "" : ` Without text: ${ohne.join(", ")}.`}`,
88
90
  specTextOhneEintrag: (titel, grundText) => `"${titel}" (${grundText})`,
89
91
  idNowInFile: "projekt_id and basis are now in plan/stand.json.",
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
- {
2
- "name": "kimal3",
3
- "version": "0.6.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.6.2",
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,41 +1,41 @@
1
- ---
2
- description: Zurück in den Mini-Kurs — da weiter, wo du aufgehört hast
3
- ---
4
-
5
- <!-- kimal3:erzeugt — diese Datei wird von `kimal3 skill install` gepflegt und bei
6
- jedem Update ueberschrieben. Eigene Aenderungen gehen dabei verloren; wer sie
7
- behalten will, kopiert die Datei unter einem anderen Namen. -->
8
- # Kurs
9
-
10
- > Zurück in den Mini-Kurs: „Wo war ich nochmal?" — macht da weiter, wo du aufgehört hast.
11
- > Den Stand holt `kimal3 kurs` frisch von kimal3 — dieselben Haken, die der Mensch im Browser unter
12
- > https://app.kimal3.de/kurs sieht.
13
-
14
- Führe die Schritte der Reihe nach aus. Bleib im Coaching-Ton: Deutsch, freundlich, ein Schritt
15
- nach dem anderen, das Warum vor dem Wie, null Vorwissen vorausgesetzt.
16
-
17
- ## 1. Den Stand holen
18
- - Führ `kimal3 kurs` aus (unter Windows so, wie es `SKILL.md` beschreibt).
19
- - Rückgabewert **2**: Der Mensch ist nicht angemeldet. Zeig ihm `kimal3 login`, lass ihn selbst im Browser bestätigen
20
- und nenn **keinen** Kursstand.
21
- - Rückgabewert **3**: Der Stand ist gerade nicht abrufbar. Sag es in einem Satz und **erzähl den Kurs nicht aus dem
22
- Gedächtnis**.
23
- - Kennt das Programm den Befehl nicht, ist es zu alt: Nenn `npm install -g kimal3@latest`, danach `kimal3 skill install`.
24
-
25
- ## 2. Stand in einem Satz sagen
26
- - Sag in **einem** Satz, wo ihr im Kurs steht — aus der Ausgabe (Zeile `Kurs:`), nichts dazuerfinden. Kein Vortrag.
27
-
28
- ## 3. Als Nächstes führen
29
- - Die Zeile `Als Nächstes:` nennt den nächsten Schritt. Führ **einen** Schritt nach dem anderen: zeigen, machen lassen,
30
- verstehen. Nie die ganze Liste auf einmal.
31
- - Steht dahinter **„(du hakst ab)"** und hat der Mensch den Schritt offensichtlich schon getan (zum Beispiel „Claude ganz
32
- beendet und neu geöffnet" — er sitzt ja gerade in Claude), führ ihn nicht noch einmal: Frag kurz nach und bitte ihn,
33
- den Schritt unter https://app.kimal3.de/kurs abzuhaken.
34
- - Steht dahinter **„(setzt Claude)"** und ist er offen, lass die Einrichtung noch einmal laufen — über die Anleitung
35
- `https://app.kimal3.de/einrichten` (lade sie wie der Kurs-Satz und folge ihr), **nicht** mit einem frei aufgerufenen
36
- `kimal3 einrichten`. Bleibt der Schritt danach offen, nenn den Grund aus der Ausgabe von `einrichten` und schlag
37
- keinen dritten Lauf vor.
38
-
39
- ## 4. Abgeschlossen
40
- - Steht in der Ausgabe `Kurs: abgeschlossen`, gratuliere kurz und frag mit einem Vorschlag, woran er hier arbeiten
41
- möchte. Kein Kurs-Gerede mehr.
1
+ ---
2
+ description: Zurück in den Mini-Kurs — da weiter, wo du aufgehört hast
3
+ ---
4
+
5
+ <!-- kimal3:erzeugt — diese Datei wird von `kimal3 skill install` gepflegt und bei
6
+ jedem Update ueberschrieben. Eigene Aenderungen gehen dabei verloren; wer sie
7
+ behalten will, kopiert die Datei unter einem anderen Namen. -->
8
+ # Kurs
9
+
10
+ > Zurück in den Mini-Kurs: „Wo war ich nochmal?" — macht da weiter, wo du aufgehört hast.
11
+ > Den Stand holt `kimal3 kurs` frisch von kimal3 — dieselben Haken, die der Mensch im Browser unter
12
+ > https://app.kimal3.de/kurs sieht.
13
+
14
+ Führe die Schritte der Reihe nach aus. Bleib im Coaching-Ton: Deutsch, freundlich, ein Schritt
15
+ nach dem anderen, das Warum vor dem Wie, null Vorwissen vorausgesetzt.
16
+
17
+ ## 1. Den Stand holen
18
+ - Führ `kimal3 kurs` aus (unter Windows so, wie es `SKILL.md` beschreibt).
19
+ - Rückgabewert **2**: Der Mensch ist nicht angemeldet. Zeig ihm `kimal3 login`, lass ihn selbst im Browser bestätigen
20
+ und nenn **keinen** Kursstand.
21
+ - Rückgabewert **3**: Der Stand ist gerade nicht abrufbar. Sag es in einem Satz und **erzähl den Kurs nicht aus dem
22
+ Gedächtnis**.
23
+ - Kennt das Programm den Befehl nicht, ist es zu alt: Nenn `npm install -g kimal3@latest`, danach `kimal3 skill install`.
24
+
25
+ ## 2. Stand in einem Satz sagen
26
+ - Sag in **einem** Satz, wo ihr im Kurs steht — aus der Ausgabe (Zeile `Kurs:`), nichts dazuerfinden. Kein Vortrag.
27
+
28
+ ## 3. Als Nächstes führen
29
+ - Die Zeile `Als Nächstes:` nennt den nächsten Schritt. Führ **einen** Schritt nach dem anderen: zeigen, machen lassen,
30
+ verstehen. Nie die ganze Liste auf einmal.
31
+ - Steht dahinter **„(du hakst ab)"** und hat der Mensch den Schritt offensichtlich schon getan (zum Beispiel „Claude ganz
32
+ beendet und neu geöffnet" — er sitzt ja gerade in Claude), führ ihn nicht noch einmal: Frag kurz nach und bitte ihn,
33
+ den Schritt unter https://app.kimal3.de/kurs abzuhaken.
34
+ - Steht dahinter **„(setzt Claude)"** und ist er offen, lass die Einrichtung noch einmal laufen — über die Anleitung
35
+ `https://app.kimal3.de/einrichten` (lade sie wie der Kurs-Satz und folge ihr), **nicht** mit einem frei aufgerufenen
36
+ `kimal3 einrichten`. Bleibt der Schritt danach offen, nenn den Grund aus der Ausgabe von `einrichten` und schlag
37
+ keinen dritten Lauf vor.
38
+
39
+ ## 4. Abgeschlossen
40
+ - Steht in der Ausgabe `Kurs: abgeschlossen`, gratuliere kurz und frag mit einem Vorschlag, woran er hier arbeiten
41
+ möchte. Kein Kurs-Gerede mehr.
@@ -183,7 +183,8 @@ Die App zeigt sie dem Menschen in genau diesem Wortlaut („So bleibt die Planun
183
183
  - **Regel 2 — Neues:** Was dir beim Prüfen auffällt oder der Mensch im Gespräch sagt und was
184
184
  **keine** Spec verspricht, kommt nach `themen`:
185
185
  `{ "schluessel": "T-1", "text": "…", "quelle": "claude" | "mensch", "seit": "<heute>" }`.
186
- Die fünf Plätze teilen sich `themen` und die offenen Notizen aus der App.
186
+ Nimm die nächste freie Nummer, auch über `eingeordnet` hinweg (dort bleibt ein Schlüssel
187
+ 14 Tage belegt). Die fünf Plätze teilen sich `themen` und die offenen Notizen aus der App.
187
188
  - **Regel 3 — einordnen:** Jedes `Thema` aus `kimal3 plan` **und** jedes Thema, das schon von
188
189
  früher in `themen` steht, ordnest du **jetzt** ein — eins von dreien:
189
190
  - **neue Story:** schreib sie in die Spec, zu der sie gehört (User Story, Akzeptanzkriterium mit
@@ -195,8 +196,11 @@ Die App zeigt sie dem Menschen in genau diesem Wortlaut („So bleibt die Planun
195
196
 
196
197
  Für eine Notiz aus der App gehört die Antwort als **ein Satz** in `uebernommen` (die App zeigt
197
198
  sie unter „Zuletzt eingeordnet" als `<Notiz> → <Antwort>`); schreib sie darum **ohne** Pfeil
198
- am Anfang. Ein Thema aus der Datei nimmst du aus `themen` heraus und nennst seine Einordnung im
199
- Bericht.
199
+ am Anfang. Ein Thema aus der Datei nimmst du aus `themen` heraus und schreibst es mit seiner
200
+ Antwort nach `eingeordnet`: `{ "schluessel": "T-4", "text": "<Text wie in themen>", "antwort":
201
+ "<ein Satz>", "am": "<heute>" }` — die App zeigt es 14 Tage lang unter „Zuletzt eingeordnet".
202
+ Einträge, deren `am` mehr als 14 Tage zurückliegt, nimmst du dabei heraus. Nenn die Einordnung
203
+ auch im Bericht.
200
204
  - **Regel 4:** Weil du bei jedem `/k3plan` alles einordnest, liegt nichts lange. Ein Thema mit
201
205
  `seit` vor mehr als 14 Tagen ordnest du **auf jeden Fall** jetzt ein.
202
206
  - **Regel 5 und 6 — keine neue Spec beginnen**, solange eine andere Spec in Arbeit ist (eine ihrer
@@ -248,12 +252,15 @@ Die App zeigt sie dem Menschen in genau diesem Wortlaut („So bleibt die Planun
248
252
  ],
249
253
  "uebernommen": [
250
254
  { "rueckmeldung": 12, "antwort": "Befund an US-2" }
255
+ ],
256
+ "eingeordnet": [
257
+ { "schluessel": "T-2", "text": "Export als PDF", "antwort": "gestrichen: Der Drucken-Knopf kann das schon.", "am": "2026-10-21" }
251
258
  ]
252
259
  }
253
260
  ```
254
261
 
255
- **Die Felder** — jedes muss da sein, auch wenn es `null` oder leer ist; ein unbekanntes Feld
256
- (ein Tippfehler wie `"befund"`) lehnt der Server ab:
262
+ **Die Felder** — jedes muss da sein, auch wenn es `null` oder leer ist (nur `eingeordnet` darf
263
+ fehlen); ein unbekanntes Feld (ein Tippfehler wie `"befund"`) lehnt der Server ab:
257
264
 
258
265
  - `format`: genau `1`.
259
266
  - `projekt`: der Name wie in der App, 1–200 Zeichen.
@@ -276,6 +283,9 @@ Die App zeigt sie dem Menschen in genau diesem Wortlaut („So bleibt die Planun
276
283
  Plätze, Regel 2).
277
284
  - `uebernommen[]`: `{ "rueckmeldung": <Nummer aus kimal3 plan>, "antwort": "<ein Satz>" }`. Leert
278
285
  `kimal3 plan hoch` nach dem Hochladen selbst.
286
+ - `eingeordnet[]`: `{ "schluessel", "text", "antwort": "<ein Satz>", "am": "JJJJ-MM-TT" }`, höchstens
287
+ 50 — Themen aus `themen`, die du eingeordnet hast (Regel 3). Ein Schlüssel steht nie zugleich in
288
+ `themen` und `eingeordnet`.
279
289
  - Die ganze Datei: höchstens 300 KB.
280
290
 
281
291
  ## 7. Hochladen
@@ -305,7 +315,8 @@ Sag dem Menschen, dass `plan/stand.json` geändert ist und in den nächsten Comm
305
315
  Danach steht eine Zeile `Spec-Texte hochgeladen: 1 von 3. Ohne Text: …` — der Text der Specs geht mit hoch.
306
316
  Eine Spec ohne Text ist ein **Hinweis, kein Fehlschlag** (Rückgabewert `0`): Nenn dem Menschen die
307
317
  Specs, die in der Klammer stehen, und was zu tun ist (Pfad in `datei` berichtigen; über 300 KB
308
- lässt sich nicht hochladen).
318
+ lässt sich nicht hochladen; bei „alle Spec-Texte zusammen größer als 4 MB" ist keine Datei allein
319
+ zu groß — die größten bleiben draußen, bis der Rest passt).
309
320
 
310
321
  **Absagen — die Meldung lesen, beheben, Probelauf, noch einmal:**
311
322
 
@@ -1,41 +1,41 @@
1
- ---
2
- description: Back into the Mini course — pick up where you left off
3
- ---
4
-
5
- <!-- kimal3:erzeugt — this file is maintained by `kimal3 skill install` and
6
- overwritten on every update. Your own changes will be lost; if you want
7
- to keep them, copy the file under a different name. -->
8
- # Course
9
-
10
- > Back into the Mini course: “Where was I again?” — picks up where you left off.
11
- > `kimal3 course` fetches the status fresh from kimal3 — the same ticks the person sees in the browser at
12
- > https://app.kimal3.de/kurs.
13
-
14
- Carry out the steps in order. Stay in the coaching tone: English, friendly, one step at a
15
- time, the why before the how, no prior knowledge assumed.
16
-
17
- ## 1. Fetch the status
18
- - Run `kimal3 course` (on Windows the way `SKILL.md` describes).
19
- - Exit code **2**: the person is not signed in. Show them `kimal3 login`, let them confirm in the browser themselves
20
- and name **no** course status.
21
- - Exit code **3**: the status can't be fetched right now. Say so in one sentence and **do not retell the course from
22
- memory**.
23
- - If the program doesn't know the command, it is too old: name `npm install -g kimal3@latest`, then `kimal3 skill install`.
24
-
25
- ## 2. Say where you stand in one sentence
26
- - Say in **one** sentence where you are in the course — from the output (the `Course:` line), invent nothing. No lecture.
27
-
28
- ## 3. Lead on with what comes next
29
- - The line `Next:` names the next step. Lead on **one** step at a time: show, let them do it, make sure they understand.
30
- Never the whole list at once.
31
- - If it says **“(you tick it off)”** after it and the person has obviously already done the step (for example “closed
32
- Claude completely and reopened it” — they are sitting in Claude right now), don't lead it again: ask briefly and
33
- ask them to tick it off at https://app.kimal3.de/kurs.
34
- - If it says **“(Claude sets it)”** and it is open, run the setup once more — through the guide
35
- `https://app.kimal3.de/setup` (load it the way the course sentence does and follow it), **not** with a freely called
36
- `kimal3 setup`. If the step is still open afterwards, name the reason from the output of `setup` and don't suggest
37
- a third run.
38
-
39
- ## 4. Finished
40
- - If the output says `Course: complete`, congratulate them briefly and ask, with a suggestion, what they would like to
41
- work on here. No more course talk.
1
+ ---
2
+ description: Back into the Mini course — pick up where you left off
3
+ ---
4
+
5
+ <!-- kimal3:erzeugt — this file is maintained by `kimal3 skill install` and
6
+ overwritten on every update. Your own changes will be lost; if you want
7
+ to keep them, copy the file under a different name. -->
8
+ # Course
9
+
10
+ > Back into the Mini course: “Where was I again?” — picks up where you left off.
11
+ > `kimal3 course` fetches the status fresh from kimal3 — the same ticks the person sees in the browser at
12
+ > https://app.kimal3.de/kurs.
13
+
14
+ Carry out the steps in order. Stay in the coaching tone: English, friendly, one step at a
15
+ time, the why before the how, no prior knowledge assumed.
16
+
17
+ ## 1. Fetch the status
18
+ - Run `kimal3 course` (on Windows the way `SKILL.md` describes).
19
+ - Exit code **2**: the person is not signed in. Show them `kimal3 login`, let them confirm in the browser themselves
20
+ and name **no** course status.
21
+ - Exit code **3**: the status can't be fetched right now. Say so in one sentence and **do not retell the course from
22
+ memory**.
23
+ - If the program doesn't know the command, it is too old: name `npm install -g kimal3@latest`, then `kimal3 skill install`.
24
+
25
+ ## 2. Say where you stand in one sentence
26
+ - Say in **one** sentence where you are in the course — from the output (the `Course:` line), invent nothing. No lecture.
27
+
28
+ ## 3. Lead on with what comes next
29
+ - The line `Next:` names the next step. Lead on **one** step at a time: show, let them do it, make sure they understand.
30
+ Never the whole list at once.
31
+ - If it says **“(you tick it off)”** after it and the person has obviously already done the step (for example “closed
32
+ Claude completely and reopened it” — they are sitting in Claude right now), don't lead it again: ask briefly and
33
+ ask them to tick it off at https://app.kimal3.de/kurs.
34
+ - If it says **“(Claude sets it)”** and it is open, run the setup once more — through the guide
35
+ `https://app.kimal3.de/setup` (load it the way the course sentence does and follow it), **not** with a freely called
36
+ `kimal3 setup`. If the step is still open afterwards, name the reason from the output of `setup` and don't suggest
37
+ a third run.
38
+
39
+ ## 4. Finished
40
+ - If the output says `Course: complete`, congratulate them briefly and ask, with a suggestion, what they would like to
41
+ work on here. No more course talk.
@@ -181,6 +181,7 @@ The app shows them to the person in exactly this wording ("How planning stays sh
181
181
  - **Rule 2 — something new:** what you notice while checking, or what the person says in the
182
182
  conversation, and what **no** spec promises goes into `themen`:
183
183
  `{ "schluessel": "T-1", "text": "…", "quelle": "claude" | "mensch", "seit": "<today>" }`.
184
+ Take the next free number, counting `eingeordnet` too (a key stays taken there for 14 days).
184
185
  The five places are shared by `themen` and the open notes from the app.
185
186
  - **Rule 3 — sort:** every `Topic` from `kimal3 plan` **and** every topic already in `themen` from
186
187
  earlier is sorted **now** — one of three:
@@ -193,8 +194,11 @@ The app shows them to the person in exactly this wording ("How planning stays sh
193
194
 
194
195
  For a note from the app, the answer goes into `uebernommen` as **one sentence** (the app shows
195
196
  it under "Recently sorted" as `<note> → <answer>`); so write it **without** an arrow at the
196
- start. A topic from the file you take out of `themen` and name how it was sorted in the
197
- report.
197
+ start. A topic from the file you take out of `themen` and write it with its answer into
198
+ `eingeordnet`: `{ "schluessel": "T-4", "text": "<text as in themen>", "antwort": "<one
199
+ sentence>", "am": "<today>" }` — the app shows it under "Recently sorted" for 14 days. Entries
200
+ whose `am` is more than 14 days ago you take out at the same time. Name how it was sorted in the
201
+ report too.
198
202
  - **Rule 4:** because you sort everything at every `/k3plan`, nothing stays long. A topic with
199
203
  `seit` more than 14 days ago is sorted now **in any case**.
200
204
  - **Rules 5 and 6 — don't start a new spec** while another spec is in progress (one of its
@@ -245,12 +249,15 @@ The app shows them to the person in exactly this wording ("How planning stays sh
245
249
  ],
246
250
  "uebernommen": [
247
251
  { "rueckmeldung": 12, "antwort": "Finding on US-2" }
252
+ ],
253
+ "eingeordnet": [
254
+ { "schluessel": "T-2", "text": "Export as PDF", "antwort": "dropped: the print button already does that.", "am": "2026-10-21" }
248
255
  ]
249
256
  }
250
257
  ```
251
258
 
252
- **The fields** — every one must be there, even if it is `null` or empty; the server refuses an
253
- unknown field (a typo like `"befund"`):
259
+ **The fields** — every one must be there, even if it is `null` or empty (only `eingeordnet` may
260
+ be missing); the server refuses an unknown field (a typo like `"befund"`):
254
261
 
255
262
  - `format`: exactly `1`.
256
263
  - `projekt`: the name as in the app, 1–200 characters.
@@ -273,6 +280,9 @@ unknown field (a typo like `"befund"`):
273
280
  rule 2).
274
281
  - `uebernommen[]`: `{ "rueckmeldung": <number from kimal3 plan>, "antwort": "<one sentence>" }`.
275
282
  `kimal3 plan upload` empties it itself after uploading.
283
+ - `eingeordnet[]`: `{ "schluessel", "text", "antwort": "<one sentence>", "am": "YYYY-MM-DD" }`, at
284
+ most 50 — topics from `themen` that you have sorted (rule 3). A key is never in `themen` and
285
+ `eingeordnet` at the same time.
276
286
  - The whole file: at most 300 KB.
277
287
 
278
288
  ## 7. Upload
@@ -300,7 +310,9 @@ person that `plan/stand.json` has changed and belongs in the next commit.
300
310
 
301
311
  After that comes a line `Spec texts uploaded: 1 of 3. Without text: …` — the text of the specs goes up too.
302
312
  A spec without text is a **hint, not a failure** (exit code `0`): tell the person which specs are in
303
- the brackets and what to do (correct the path in `datei`; over 300 KB cannot be uploaded).
313
+ the brackets and what to do (correct the path in `datei`; over 300 KB cannot be uploaded; with
314
+ "all spec texts together larger than 4 MB" no single file is too large — the largest stay out
315
+ until the rest fits).
304
316
 
305
317
  **Refusals — read the message, fix it, dry run, again:**
306
318