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.
|
|
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/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
|
+
}
|