kimal3 0.6.1 → 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
+ }
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
- {
2
- "name": "kimal3",
3
- "version": "0.6.1",
4
- "description": "Trag deine KI-Projekte dort ein, wo sie entstehen: im Terminal. · Log your AI projects where they happen: in the terminal.",
5
- "type": "module",
6
- "bin": {
7
- "kimal3": "dist/kimal3.js"
8
- },
9
- "files": [
10
- "dist",
11
- "skill",
12
- "claude-plugins",
13
- "coach",
14
- "zeigen",
15
- "README.md"
16
- ],
17
- "engines": {
18
- "node": ">=20"
19
- },
20
- "scripts": {
21
- "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
22
- "prepublishOnly": "npm run build"
23
- },
24
- "keywords": [
25
- "kimal3",
26
- "cli"
27
- ],
28
- "homepage": "https://app.kimal3.de/pro",
29
- "license": "SEE LICENSE IN README.md"
30
- }
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
+ }