kimal3 0.6.2 → 0.6.3

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,6 @@
1
1
  {
2
2
  "name": "kimal3-logo",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
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
5
  "author": { "name": "KImal3", "url": "https://kimal3.de" },
6
6
  "types": "./types/index.d.ts"
@@ -10,6 +10,10 @@ const PROJEKTNUMMER = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]
10
10
  // Der eingebaute Browser der Claude-App, wie ihn das Werkzeug `mcp__Claude_Browser__navigate` nennt.
11
11
  const BROWSER = 'Claude_Browser'
12
12
  const NAVIGATE = 'mcp__Claude_Browser__navigate'
13
+ // Den Browser-Bereich neben dem Chat einblenden: `navigate` allein öffnet die Seite in einem VERSTECKTEN Bereich
14
+ // (gemessen 23.10. in einer frischen Sitzung: `navOk: true`, „Browser pane opened“, Dennis sah nichts).
15
+ const ANSICHT = 'ccd_view'
16
+ const EINBLENDEN = 'mcp__ccd_view__show_pane'
13
17
  const geoeffnet = atom({ plugin: 'kimal3-logo', key: 'appGeoeffnet' } as const, false)
14
18
 
15
19
  /** 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). */
@@ -32,6 +36,11 @@ export function istEigenerAufruf(plugin: string, input: unknown): boolean {
32
36
  return plugin === 'kimal3-logo' && typeof url === 'string' && url.startsWith(`${APP}/`)
33
37
  }
34
38
 
39
+ /** Die eigene Erlaubnis zum Einblenden: nur der Aufruf dieses Mods, nur der Browser-Bereich (`preview`), nie ein anderer. */
40
+ export function istEigenesEinblenden(plugin: string, input: unknown): boolean {
41
+ return plugin === 'kimal3-logo' && (input as { pane?: unknown } | null)?.pane === 'preview'
42
+ }
43
+
35
44
  // Gegen zwei gleichzeitige Versuche in diesem Lauf (`session.start` und `session.attach` kurz hintereinander).
36
45
  // Das Neuladen des Mods setzt die Variable zurück; dagegen hält der Merker `geoeffnet`.
37
46
  let laeuft = false
@@ -50,19 +59,57 @@ async function einmalOeffnen($: Engine): Promise<void> {
50
59
  }
51
60
  }
52
61
 
53
- async function oeffne($: Engine): Promise<boolean> {
54
- const cwd = await $.session.cwd()
55
- let stand: string | null = null
62
+ /** Der Startordner und alle Ordner darüber, nächster zuerst; Trenner immer `/` (Windows nimmt beide). */
63
+ export function ordnerNachOben(cwd: string): string[] {
64
+ const teile = cwd.replace(/\\/g, '/').replace(/\/+$/, '').split('/')
65
+ return teile.map((_, i) => teile.slice(0, teile.length - i).join('/'))
66
+ }
67
+
68
+ /**
69
+ * Das Hauptprojekt einer Git-Arbeitskopie aus ihrer `.git`-Datei (`gitdir: C:/…/kimal3-app/.git/worktrees/s8`).
70
+ * `null` für alles andere, etwa ein Untermodul (`gitdir: ../.git/modules/…`).
71
+ */
72
+ export function hauptordnerAusGitdatei(text: string): string | null {
73
+ const treffer = /^gitdir:\s*(.+?)[\\/]\.git[\\/]worktrees[\\/][^\\/]+\s*$/.exec(text.trim())
74
+ return treffer === null ? null : treffer[1].replace(/\\/g, '/')
75
+ }
76
+
77
+ async function liesDatei($: Engine, pfad: string): Promise<string | null> {
56
78
  try {
57
- stand = await $.fs.read(`${cwd}/plan/stand.json`)
79
+ return await $.fs.read(pfad)
58
80
  } catch {
59
- stand = null
81
+ return null
60
82
  }
83
+ }
84
+
85
+ /**
86
+ * `plan/stand.json` des Projekts, in dem Claude läuft (S-5, S-24, S-25): vom Startordner nach oben, bis zur Wurzel des
87
+ * eigenen Git-Projekts und nie darüber hinaus — sonst bekäme ein Projekt ohne Plan die Seite eines anderen. Ist die
88
+ * Wurzel eine Arbeitskopie (`.git` ist dort eine Datei), zählt danach die Projektdatei ihres Hauptprojekts.
89
+ */
90
+ async function findeStand($: Engine, cwd: string): Promise<string | null> {
91
+ for (const ordner of ordnerNachOben(cwd)) {
92
+ const stand = await liesDatei($, `${ordner}/plan/stand.json`)
93
+ if (stand !== null) return stand
94
+ if (!(await $.fs.exists(`${ordner}/.git`))) continue
95
+ // Ein Ordner `.git` lässt sich nicht lesen: echte Projektwurzel, hier ist Schluss.
96
+ const gitdatei = await liesDatei($, `${ordner}/.git`)
97
+ const haupt = gitdatei === null ? null : hauptordnerAusGitdatei(gitdatei)
98
+ return haupt === null ? null : liesDatei($, `${haupt}/plan/stand.json`)
99
+ }
100
+ return null
101
+ }
102
+
103
+ async function oeffne($: Engine): Promise<boolean> {
104
+ const stand = await findeStand($, await $.session.cwd())
61
105
  // Scheitern meldet sich als Scheitern, nie im Chat (S-13): eine Zeile nur im Debug-Protokoll (`to: 'debug'` —
62
106
  // ohne steht sie im Chatverlauf, Befund der Durchsicht 23.10.).
63
107
  try {
64
108
  const antwort = await $.mcp.call(BROWSER, 'navigate', { url: adresseFuer(stand) })
65
- if (!antwort.isError) return true
109
+ if (!antwort.isError) {
110
+ await einblenden($)
111
+ return true
112
+ }
66
113
  $.ui.log(`kimal3-logo: Projektseite nicht geöffnet: ${JSON.stringify(antwort.content).slice(0, 300)}`, { to: 'debug' })
67
114
  } catch (fehler) {
68
115
  $.ui.log(`kimal3-logo: Projektseite nicht geöffnet: ${String(fehler).slice(0, 300)}`, { to: 'debug' })
@@ -70,6 +117,16 @@ async function oeffne($: Engine): Promise<boolean> {
70
117
  return false
71
118
  }
72
119
 
120
+ /** Die geöffnete Seite auch zeigen. Scheitert das, ist die Seite trotzdem offen — eine Zeile im Debug-Protokoll, kein zweiter Versuch. */
121
+ async function einblenden($: Engine): Promise<void> {
122
+ try {
123
+ const antwort = await $.mcp.call(ANSICHT, 'show_pane', { pane: 'preview' })
124
+ if (antwort.isError) $.ui.log(`kimal3-logo: Browser nicht eingeblendet: ${JSON.stringify(antwort.content).slice(0, 300)}`, { to: 'debug' })
125
+ } catch (fehler) {
126
+ $.ui.log(`kimal3-logo: Browser nicht eingeblendet: ${String(fehler).slice(0, 300)}`, { to: 'debug' })
127
+ }
128
+ }
129
+
73
130
  export const appOeffnen: Register = on => {
74
131
  // Die eigene Erlaubnis, eng: nur der Aufruf dieses Mods, nur eine Adresse der App. Ohne sie lehnt der Auto-Modus
75
132
  // ab, weil beim Start niemand um etwas gebeten hat (gemessen 23.10. mit einer Probe). Claudes eigene Aufrufe und
@@ -79,6 +136,11 @@ export const appOeffnen: Register = on => {
79
136
  ? { decision: 'allow', reason: 'kimal3: die eigene Projektseite beim Start' }
80
137
  : next(e),
81
138
  )
139
+ on('tool.check', { tool: EINBLENDEN }, ($, e, next) =>
140
+ istEigenesEinblenden(next.origin.plugin, e.input)
141
+ ? { decision: 'allow', reason: 'kimal3: den Browser mit der Projektseite einblenden' }
142
+ : next(e),
143
+ )
82
144
  // Den Browser gibt es nur in der Claude-App, nicht im Terminal oder in VS Code. Beim Start meldet die App
83
145
  // `isInteractive: false`, ist aber schon angehängt (gemessen 23.10.: surfaces ["desktop"]). `claude -p`, Skripte,
84
146
  // geplante Aufgaben, das Terminal: kein Aufruf (S-6).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kimal3",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "Trag deine KI-Projekte dort ein, wo sie entstehen: im Terminal. · Log your AI projects where they happen: in the terminal.",
5
5
  "type": "module",
6
6
  "bin": {