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.
- package/claude-plugins/kimal3-logo/.claude-plugin/plugin.json +7 -6
- package/claude-plugins/kimal3-logo/hooks/app-oeffnen.ts +96 -0
- package/claude-plugins/kimal3-logo/hooks/register.tsx +84 -80
- package/claude-plugins/kimal3-logo/types/index.d.ts +8 -0
- package/dist/plan-datei.js +2 -2
- package/dist/texte/plan.js +4 -2
- package/package.json +30 -30
- package/skill/commands/k3kurs.md +41 -41
- package/skill/commands/k3plan.md +17 -6
- package/skill/en/commands/k3course.md +41 -41
- package/skill/en/commands/k3plan.md +17 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "kimal3-logo",
|
|
3
|
-
"version": "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
|
-
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.
|
|
15
|
-
.
|
|
16
|
-
.
|
|
17
|
-
.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<polygon class="tri
|
|
23
|
-
<polygon class="tri
|
|
24
|
-
|
|
25
|
-
</
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
}
|
package/dist/plan-datei.js
CHANGED
|
@@ -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 `
|
|
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: "
|
|
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 };
|
package/dist/texte/plan.js
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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
|
+
}
|
package/skill/commands/k3kurs.md
CHANGED
|
@@ -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.
|
package/skill/commands/k3plan.md
CHANGED
|
@@ -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
|
-
|
|
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
|
|
199
|
-
|
|
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
|
|
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
|
|
197
|
-
|
|
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
|
|
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
|
|