hmgr 0.0.5 → 0.0.6
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/README.md +10 -0
- package/dist/adapter/checkout-pfad.js +10 -2
- package/dist/adapter/claude-code.js +231 -7
- package/dist/adapter/composer.js +4 -3
- package/dist/adapter/datei-text.js +18 -0
- package/dist/adapter/dateisystem.js +42 -4
- package/dist/adapter/git.js +34 -8
- package/dist/adapter/hermes.js +73 -1
- package/dist/adapter/marketplace-json.js +2 -1
- package/dist/adapter/mcp-katalog.js +124 -0
- package/dist/adapter/quelle-text.js +10 -1
- package/dist/adapter/skill-md.js +2 -1
- package/dist/adapter/umgebung.js +43 -0
- package/dist/adapter/vergleich.js +20 -0
- package/dist/adapter/zustand-json.js +2 -1
- package/dist/cli/hilfe.js +98 -0
- package/dist/cli/main.js +97 -3
- package/dist/gui/beobachter.js +108 -0
- package/dist/gui/login-pfad.js +120 -0
- package/dist/gui/main.js +361 -19
- package/dist/gui/mcp-ansicht.js +57 -0
- package/dist/gui/oeffnen.js +1 -1
- package/dist/gui/preload.cjs +16 -1
- package/dist/gui/renderer/index.html +96 -6
- package/dist/gui/renderer/renderer.js +654 -38
- package/dist/gui/renderer/styles.css +65 -2
- package/dist/kern/anwendungsfaelle.js +175 -4
- package/dist/kern/modell.js +141 -3
- package/dist/kern/quelle-ort.js +14 -1
- package/dist/ports/harness.js +3 -0
- package/dist/ports/mcp-katalog.js +1 -0
- package/dist/ports/umgebung.js +1 -0
- package/package.json +43 -1
|
@@ -12,16 +12,34 @@
|
|
|
12
12
|
return el;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
// Home-Verzeichnis, einmal beim Laden über hmgr:home
|
|
16
|
-
// Anzeige: fließt nie in einen IPC-Aufruf oder
|
|
15
|
+
// Home-Verzeichnis und Plattform, einmal beim Laden über hmgr:home und hmgr:plattform geholt
|
|
16
|
+
// (siehe ladeUmgebung unten). Nur für die Anzeige: beides fließt nie in einen IPC-Aufruf oder
|
|
17
|
+
// einen zielKey zurück.
|
|
17
18
|
var HOME = '';
|
|
18
|
-
|
|
19
|
+
// Pfadtrenner der Anzeige (E68). Aus der Plattform und nicht aus dem Home-Pfad abgeleitet: die
|
|
20
|
+
// Pfade, die hier angezeigt werden, baut der Kern mit `node:path`, also mit dem Trenner der
|
|
21
|
+
// Plattform — auch dann, wenn HOME selbst in POSIX-Schreibweise gesetzt ist (MSYS/Git-Bash).
|
|
22
|
+
var TRENNER = '/';
|
|
23
|
+
/** Trenner einer Plattform, wie `process.platform` sie nennt. Rein, nur für die Anzeige. */
|
|
24
|
+
function plattformTrenner(plattform) { return plattform === 'win32' ? '\\' : '/'; }
|
|
25
|
+
/**
|
|
26
|
+
* Kürzt einen absoluten Pfad auf ~, wenn er mit dem Home-Verzeichnis beginnt. Grenze ist einer
|
|
27
|
+
* der beiden Trenner (E68): unter Windows steht dort „\", und der Rest des Pfads bleibt wörtlich
|
|
28
|
+
* stehen, die Kürzung heißt dort also von selbst `~\…` statt `~/…`.
|
|
29
|
+
*/
|
|
19
30
|
function pfadKurz(p) {
|
|
20
31
|
if (!HOME || typeof p !== 'string') return p;
|
|
21
32
|
if (p === HOME) return '~';
|
|
22
|
-
|
|
33
|
+
var grenze = p.charAt(HOME.length);
|
|
34
|
+
if (p.indexOf(HOME) === 0 && (grenze === '/' || grenze === '\\')) return '~' + p.slice(HOME.length);
|
|
23
35
|
return p;
|
|
24
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Schreibt einen fest im Quelltext stehenden POSIX-Pfad in der Schreibweise der Plattform (E68),
|
|
39
|
+
* damit `~/.claude.json` unter Windows nicht neben `~\Code\…` steht. Nur für Anzeigetexte, die
|
|
40
|
+
* hmgr selbst formuliert — nie für einen Pfad, der aus dem Kern kommt.
|
|
41
|
+
*/
|
|
42
|
+
function pfadSchreibweise(p) { return TRENNER === '/' ? p : p.split('/').join(TRENNER); }
|
|
25
43
|
|
|
26
44
|
function melde(text) {
|
|
27
45
|
$('meldungtext').textContent = text;
|
|
@@ -163,10 +181,13 @@
|
|
|
163
181
|
* dasselbe (`quelleAlsText`), aber nur so findet auch die Einstellungen-Liste ihr Icon.
|
|
164
182
|
*/
|
|
165
183
|
function quelleAdresse(quelle) { return quelle.text != null ? quelle.text : quelleKurz(quelle); }
|
|
166
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* Letztes nicht leeres Segment eines Pfads oder einer URL, ohne abschließende Schrägstriche.
|
|
186
|
+
* Trenner sind `/` und `\`, wie in `letztesSegment` im Kern (E65/B8, dort führend).
|
|
187
|
+
*/
|
|
167
188
|
function letztesSegment(text) {
|
|
168
|
-
var bereinigt = text.replace(
|
|
169
|
-
var segmente = bereinigt.split(
|
|
189
|
+
var bereinigt = text.replace(/[/\\]+$/, '');
|
|
190
|
+
var segmente = bereinigt.split(/[/\\]/).filter(function (s) { return s.length > 0; });
|
|
170
191
|
return segmente.length > 0 ? segmente[segmente.length - 1] : bereinigt;
|
|
171
192
|
}
|
|
172
193
|
/**
|
|
@@ -256,7 +277,7 @@
|
|
|
256
277
|
/**
|
|
257
278
|
* Eine Quelle, angeschrieben mit ihrem Namen und anklickbar (E60): keine Unterstreichung, das
|
|
258
279
|
* kleine Pfeilsymbol dahinter trägt das Signalorange. Klick öffnet über den Main-Prozess die
|
|
259
|
-
* Repo-Seite im Browser oder den Ordner im
|
|
280
|
+
* Repo-Seite im Browser oder den Ordner im Dateimanager (`window.hmgr.quelleOeffnen`), still im
|
|
260
281
|
* Fehlerfall statt in der Meldungsleiste, ein Öffnen-Fehlschlag soll die Ansicht nicht stören.
|
|
261
282
|
*/
|
|
262
283
|
function quelleLinkElement(quelle) {
|
|
@@ -265,7 +286,9 @@
|
|
|
265
286
|
if (lokal) el.type = 'button'; else el.href = '#';
|
|
266
287
|
el.className = 'qlink';
|
|
267
288
|
el.title = quelleAdresse(quelle);
|
|
268
|
-
|
|
289
|
+
// „Dateimanager" statt „Finder" (E68): die GUI läuft auch unter Windows, wo das Ding Explorer
|
|
290
|
+
// heißt, und unter Linux gibt es weder das eine noch das andere.
|
|
291
|
+
el.setAttribute('aria-label', quelleAnzeigename(quelle) + ' ' + (lokal ? 'im Dateimanager öffnen' : 'im Browser öffnen'));
|
|
269
292
|
el.appendChild(document.createTextNode(quelleAnzeigename(quelle)));
|
|
270
293
|
el.appendChild(externSvg());
|
|
271
294
|
el.addEventListener('click', function (ev) {
|
|
@@ -284,6 +307,19 @@
|
|
|
284
307
|
var HARNESSE_ALLE = [];
|
|
285
308
|
var AKTIVE_HARNESSE = [];
|
|
286
309
|
var HARNESSE_NICHT_INSTALLIERT = [];
|
|
310
|
+
// MCP-Katalog (E61-Folgerunde): je Eintrag { eintrag, eingehaengt, fehlendeVariablen }, dazu
|
|
311
|
+
// Einträge in Harness-Konfigurationen, die nicht im Katalog stehen (nur genannt). Seit F46 nur
|
|
312
|
+
// noch Grundlage für den MCP-Abschnitt der Ziel-Karten (mcpZeilenFuerZiel), die MCP-Ansicht selbst
|
|
313
|
+
// zeigt keine Fundorte mehr.
|
|
314
|
+
var MCP_KATALOG = [];
|
|
315
|
+
var MCP_FREMD = [];
|
|
316
|
+
// Nicht-Katalog-Einträge aus geteilten, nur lesbaren Fundorten (E61-Nachtrag), etwa der
|
|
317
|
+
// .mcp.json einer Projektwurzel.
|
|
318
|
+
var MCP_FREMD_GETEILT = [];
|
|
319
|
+
// Alle Orte, an denen grundsätzlich ein MCP-Server hängen könnte (E61-GUI-Nachtrag), roh aus dem
|
|
320
|
+
// Kern (F42, E16): Grundlage für den MCP-Abschnitt und den „MCP hinzufügen …"-Knopf an den
|
|
321
|
+
// Ziel-Karten — nie am Harness-Namen entschieden, sondern an dieser Liste.
|
|
322
|
+
var MCP_PLAETZE = [];
|
|
287
323
|
var quellenFilter = 'alle';
|
|
288
324
|
var zielFilter = 'alle';
|
|
289
325
|
// Standard-Ref je Quelle (E60), aus dem Katalog abgeleitet (SKILLS trägt ihn schon je Skill): ein
|
|
@@ -340,9 +376,18 @@
|
|
|
340
376
|
|
|
341
377
|
/* ---------------- Laden ---------------- */
|
|
342
378
|
|
|
343
|
-
/**
|
|
344
|
-
|
|
345
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Home-Verzeichnis und Plattform einmal beim Start holen (nur für die Anzeige, siehe oben).
|
|
381
|
+
* Danach stehen die fest formulierten Pfade im Bild in der Schreibweise der Plattform (E68).
|
|
382
|
+
*/
|
|
383
|
+
async function ladeUmgebung() {
|
|
384
|
+
var werte = await Promise.all([
|
|
385
|
+
rufe(function () { return window.hmgr.home(); }),
|
|
386
|
+
rufe(function () { return window.hmgr.plattform(); }),
|
|
387
|
+
]);
|
|
388
|
+
HOME = werte[0];
|
|
389
|
+
TRENNER = plattformTrenner(werte[1]);
|
|
390
|
+
$('mcpkatalogpfad').textContent = pfadSchreibweise('~/.hmgr/mcp.yaml');
|
|
346
391
|
}
|
|
347
392
|
|
|
348
393
|
async function ladeAlles() {
|
|
@@ -357,6 +402,8 @@
|
|
|
357
402
|
rufe(function () { return window.hmgr.harnessTypen(); }),
|
|
358
403
|
rufe(function () { return window.hmgr.aktiveHarnesses(); }),
|
|
359
404
|
rufe(function () { return window.hmgr.nichtVorhandeneHarnesses(); }),
|
|
405
|
+
rufe(function () { return window.hmgr.mcpUebersicht(); }),
|
|
406
|
+
rufe(function () { return window.hmgr.mcpPlaetze(); }),
|
|
360
407
|
]);
|
|
361
408
|
ZIELE = ergebnisse[0];
|
|
362
409
|
SKILLS = ergebnisse[1];
|
|
@@ -367,6 +414,10 @@
|
|
|
367
414
|
HARNESSE_ALLE = ergebnisse[4];
|
|
368
415
|
AKTIVE_HARNESSE = ergebnisse[5];
|
|
369
416
|
HARNESSE_NICHT_INSTALLIERT = ergebnisse[6];
|
|
417
|
+
MCP_KATALOG = ergebnisse[7].katalog;
|
|
418
|
+
MCP_FREMD = ergebnisse[7].unbekannt;
|
|
419
|
+
MCP_FREMD_GETEILT = ergebnisse[7].unbekannteGeteilteFundorte;
|
|
420
|
+
MCP_PLAETZE = ergebnisse[8];
|
|
370
421
|
STANDARD_REF = {};
|
|
371
422
|
SKILLS.forEach(function (s) { if (STANDARD_REF[s.quelleText] === undefined) STANDARD_REF[s.quelleText] = s.ref; });
|
|
372
423
|
|
|
@@ -375,6 +426,7 @@
|
|
|
375
426
|
baueQuellenfilter();
|
|
376
427
|
baueMarketplaces();
|
|
377
428
|
baueEinstellungen();
|
|
429
|
+
baueMcp();
|
|
378
430
|
aktualisiereKopf();
|
|
379
431
|
}
|
|
380
432
|
|
|
@@ -425,7 +477,7 @@
|
|
|
425
477
|
|
|
426
478
|
/* ---------------- Navigation ---------------- */
|
|
427
479
|
|
|
428
|
-
var ANSICHTEN = ['ziele', 'katalog', 'update', 'plugins'];
|
|
480
|
+
var ANSICHTEN = ['ziele', 'katalog', 'update', 'plugins', 'mcp'];
|
|
429
481
|
function zeigeAnsicht(name) {
|
|
430
482
|
document.querySelectorAll('.plakette').forEach(function (a) {
|
|
431
483
|
if (a.dataset.ansicht === name) a.setAttribute('aria-current', 'page');
|
|
@@ -628,11 +680,12 @@
|
|
|
628
680
|
}
|
|
629
681
|
}
|
|
630
682
|
|
|
683
|
+
var fussContainer;
|
|
631
684
|
if (eigene.length === 0) {
|
|
632
685
|
var leer = e('div', 'leerzeile');
|
|
633
686
|
leer.appendChild(e('p', 'ohne', 'Hier liegt noch nichts.'));
|
|
634
|
-
leer.appendChild(skillsKnopf(z));
|
|
635
687
|
teil.appendChild(leer);
|
|
688
|
+
fussContainer = leer;
|
|
636
689
|
} else {
|
|
637
690
|
// Fünf Spalten (E60): Icon, Skill, Beschreibung (die breiteste, sie beantwortet die Frage,
|
|
638
691
|
// wegen der die Tabelle gelesen wird), Quelle, Aktion.
|
|
@@ -666,14 +719,7 @@
|
|
|
666
719
|
|
|
667
720
|
// Die Beschreibung aus der SKILL.md (E39): die Frage, wegen der die Zeile gelesen wird,
|
|
668
721
|
// deshalb die breiteste Spalte. Ein Link ohne bekannte Quelle kennt seinen Skill nicht.
|
|
669
|
-
|
|
670
|
-
if (beschreibung) {
|
|
671
|
-
var wasEl = e('span', 'was', beschreibung);
|
|
672
|
-
if (beschreibung.length > 88) wasEl.title = beschreibung;
|
|
673
|
-
li.appendChild(wasEl);
|
|
674
|
-
} else {
|
|
675
|
-
li.appendChild(e('span', 'was fehlt', 'keine Beschreibung in der SKILL.md'));
|
|
676
|
-
}
|
|
722
|
+
li.appendChild(wasZelle(ei.art === 'link' ? ei.link.skill : null));
|
|
677
723
|
|
|
678
724
|
// Quelle: der Name, anklickbar, dahinter nur, was vom Normalfall abweicht — ein Ref, das
|
|
679
725
|
// vom Standard-Ref der Quelle abweicht, und der Grund „über Zeiger" (E57). „Manuell" (der
|
|
@@ -709,11 +755,27 @@
|
|
|
709
755
|
});
|
|
710
756
|
teil.appendChild(ul);
|
|
711
757
|
|
|
758
|
+
// MCP-Abschnitt (E61-GUI-Nachtrag): unter der Skill-Link-Tabelle, vor dem Kartenfuß mit den
|
|
759
|
+
// Knöpfen, Gegenrichtung zur MCP-Ansicht.
|
|
760
|
+
var mcpAbschnMitTabelle = mcpAbschnitt(z.ziel);
|
|
761
|
+
if (mcpAbschnMitTabelle) teil.appendChild(mcpAbschnMitTabelle);
|
|
762
|
+
|
|
712
763
|
var fuss = e('div', 'zielfuss');
|
|
713
|
-
fuss.appendChild(skillsKnopf(z));
|
|
714
764
|
teil.appendChild(fuss);
|
|
765
|
+
fussContainer = fuss;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Ohne eigene Links (leer-Fall) steht der MCP-Abschnitt hinter der Leerzeile, die dort schon den
|
|
769
|
+
// Kartenfuß bildet (Nachricht + Knöpfe in einer Zeile, wie überall sonst im leeren Zustand).
|
|
770
|
+
if (eigene.length === 0) {
|
|
771
|
+
var mcpAbschnOhneTabelle = mcpAbschnitt(z.ziel);
|
|
772
|
+
if (mcpAbschnOhneTabelle) teil.appendChild(mcpAbschnOhneTabelle);
|
|
715
773
|
}
|
|
716
774
|
|
|
775
|
+
fussContainer.appendChild(skillsKnopf(z));
|
|
776
|
+
var mcpKnopf = mcpHinzufuegenKnopf(z.ziel);
|
|
777
|
+
if (mcpKnopf) fussContainer.appendChild(mcpKnopf);
|
|
778
|
+
|
|
717
779
|
return teil;
|
|
718
780
|
}
|
|
719
781
|
|
|
@@ -739,6 +801,99 @@
|
|
|
739
801
|
return knopf;
|
|
740
802
|
}
|
|
741
803
|
|
|
804
|
+
/* ---------------- MCP an der Ziel-Karte (E61-GUI-Nachtrag) ---------------- */
|
|
805
|
+
/* Gegenrichtung zur MCP-Ansicht (E47-Muster): ob ein Harness an einem Ort MCP kann, entscheidet
|
|
806
|
+
nie der Harness-Name (E16), sondern einzig, ob MCP_PLAETZE (roh aus dem Kern, F42) diesen Ort
|
|
807
|
+
nennt. */
|
|
808
|
+
function mcpKannAn(ziel) {
|
|
809
|
+
var zk = zielKey(ziel);
|
|
810
|
+
return MCP_PLAETZE.some(function (p) { return zielKey(p) === zk; });
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/** Die MCP-Zeilen eines Ziels, aus den drei Quellen der MCP-Ansicht zusammengesucht: eingehängte
|
|
814
|
+
Katalog-Einträge (mit Entfernen-Knopf), ihre Fundorte in der geteilten .mcp.json des Projekts
|
|
815
|
+
(E61-Nachtrag, seit E64 mit Übernehmen-Knopf) und Einträge, die dort hängen, aber nicht im
|
|
816
|
+
Katalog stehen (stiller Vermerk). */
|
|
817
|
+
function mcpZeilenFuerZiel(ziel) {
|
|
818
|
+
var zk = zielKey(ziel);
|
|
819
|
+
var zeilen = [];
|
|
820
|
+
MCP_KATALOG.forEach(function (k) {
|
|
821
|
+
if (k.eingehaengt.some(function (z) { return zielKey(z) === zk; })) {
|
|
822
|
+
zeilen.push({ art: 'katalog', name: k.eintrag.name, beschreibung: k.eintrag.beschreibung, eintrag: k.eintrag });
|
|
823
|
+
}
|
|
824
|
+
k.geteilteFundorte.forEach(function (g) {
|
|
825
|
+
if (zielKey(g.ziel) === zk)
|
|
826
|
+
zeilen.push({ art: 'geteilt', name: k.eintrag.name, beschreibung: k.eintrag.beschreibung, datei: g.datei });
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
MCP_FREMD.forEach(function (x) { if (zielKey(x.ziel) === zk) zeilen.push({ art: 'fremd', name: x.name }); });
|
|
830
|
+
// Ein Eintrag aus der geteilten .mcp.json, den der Katalog nicht kennt: „nicht im Katalog" allein
|
|
831
|
+
// läse sich wie eine Einladung, ihn in die mcp.yaml zu schreiben. Wo er wirklich steht, gehört
|
|
832
|
+
// daneben — und übernehmen lässt er sich auch ohne Katalog-Eintrag (E64, der Block kommt aus der
|
|
833
|
+
// Datei).
|
|
834
|
+
MCP_FREMD_GETEILT.forEach(function (x) {
|
|
835
|
+
if (zielKey(x.ziel) === zk) zeilen.push({ art: 'fremd-geteilt', name: x.name, datei: x.datei });
|
|
836
|
+
});
|
|
837
|
+
zeilen.sort(function (a, b) { return a.name.localeCompare(b.name, 'de'); });
|
|
838
|
+
return zeilen;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/** Der MCP-Abschnitt unter der Skill-Link-Tabelle einer Ziel-Karte (E61-GUI-Nachtrag): nur, wenn
|
|
842
|
+
dort etwas eingehängt ist oder der „hinzufügen"-Knopf ohnehin sichtbar wäre (mcpKannAn). */
|
|
843
|
+
function mcpAbschnitt(ziel) {
|
|
844
|
+
var zeilen = mcpZeilenFuerZiel(ziel);
|
|
845
|
+
if (zeilen.length === 0 && !mcpKannAn(ziel)) return null;
|
|
846
|
+
var abschnitt = e('div', 'mcpkarteabschnitt');
|
|
847
|
+
abschnitt.appendChild(e('span', 'etikett', 'MCP'));
|
|
848
|
+
if (zeilen.length === 0) {
|
|
849
|
+
abschnitt.appendChild(e('p', 'ohne mcpkarteleer', 'Noch nichts eingehängt.'));
|
|
850
|
+
return abschnitt;
|
|
851
|
+
}
|
|
852
|
+
var ul = e('ul', 'mcpkartezeilen');
|
|
853
|
+
zeilen.forEach(function (row) {
|
|
854
|
+
var li = e('li');
|
|
855
|
+
li.appendChild(e('span', 'name', row.name));
|
|
856
|
+
if (row.art === 'katalog') {
|
|
857
|
+
li.appendChild(e('span', 'was', row.beschreibung || ''));
|
|
858
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
859
|
+
weg.type = 'button';
|
|
860
|
+
weg.setAttribute('aria-label', row.name + ' aus ' + zielTitel({ ziel: ziel }) + ' aushängen');
|
|
861
|
+
weg.addEventListener('click', function () { frageMcpAus(row.eintrag, ziel); });
|
|
862
|
+
li.appendChild(weg);
|
|
863
|
+
} else if (row.art === 'geteilt' || row.art === 'fremd-geteilt') {
|
|
864
|
+
// E64: der Vermerk nennt die Bedeutung („steht im Projekt, nicht bei dir"), nicht mehr bloß
|
|
865
|
+
// den Dateinamen — und daneben steht der Weg, das zu ändern.
|
|
866
|
+
li.appendChild(row.art === 'geteilt'
|
|
867
|
+
? e('span', 'was', row.beschreibung || '')
|
|
868
|
+
: e('span', 'was fehlt', 'nicht im Katalog'));
|
|
869
|
+
li.appendChild(e('span', 'geteiltvermerk', 'in der .mcp.json des Projekts'));
|
|
870
|
+
var her = e('button', 'knopf leise klein', 'Übernehmen …');
|
|
871
|
+
her.type = 'button';
|
|
872
|
+
her.setAttribute('aria-label', row.name + ' aus der .mcp.json des Projekts in deine private Konfiguration übernehmen');
|
|
873
|
+
her.addEventListener('click', function () { frageMcpUebernahme(row.name, ziel, row.datei); });
|
|
874
|
+
li.appendChild(her);
|
|
875
|
+
} else {
|
|
876
|
+
li.appendChild(e('span', 'was fehlt', 'nicht im Katalog'));
|
|
877
|
+
li.appendChild(e('span'));
|
|
878
|
+
}
|
|
879
|
+
ul.appendChild(li);
|
|
880
|
+
});
|
|
881
|
+
abschnitt.appendChild(ul);
|
|
882
|
+
return abschnitt;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/** Knopf „MCP hinzufügen …" im Kartenfuß neben „Skills hinzufügen" (E61-GUI-Nachtrag, Gegenrichtung
|
|
886
|
+
zur MCP-Ansicht): nur, wo der Harness an diesem Ort MCP kann (mcpKannAn, E16). */
|
|
887
|
+
function mcpHinzufuegenKnopf(ziel) {
|
|
888
|
+
if (!mcpKannAn(ziel)) return null;
|
|
889
|
+
var knopf = e('button', 'knopf leise klein mitsymbol', 'MCP hinzufügen …');
|
|
890
|
+
knopf.type = 'button';
|
|
891
|
+
knopf.insertBefore(harnessZeichen(ziel.harness), knopf.firstChild);
|
|
892
|
+
knopf.setAttribute('aria-label', 'MCP-Server zu ' + zielTitel({ ziel: ziel }) + ' hinzufügen (' + harnessName(ziel.harness) + ')');
|
|
893
|
+
knopf.addEventListener('click', function () { oeffneMcpHinzufuegen(ziel); });
|
|
894
|
+
return knopf;
|
|
895
|
+
}
|
|
896
|
+
|
|
742
897
|
function zielKarte(z) {
|
|
743
898
|
var karte = e('div', 'ziel');
|
|
744
899
|
var kopf = e('div', 'zielkopf');
|
|
@@ -849,7 +1004,7 @@
|
|
|
849
1004
|
quelle: defekt ? null : ei.link.checkout.quelle,
|
|
850
1005
|
ref: defekt ? null : ei.link.checkout.ref,
|
|
851
1006
|
einschraenkung: defekt ? null : z.einschraenkungen[name],
|
|
852
|
-
|
|
1007
|
+
skill: defekt ? null : ei.link.skill,
|
|
853
1008
|
teile: [],
|
|
854
1009
|
};
|
|
855
1010
|
reihenfolge.push(key);
|
|
@@ -858,11 +1013,28 @@
|
|
|
858
1013
|
});
|
|
859
1014
|
});
|
|
860
1015
|
|
|
1016
|
+
// MCPs hängen am Verzeichnis, nicht am Harness-Chip (E61-GUI-Nachtrag): der Abschnitt unter
|
|
1017
|
+
// „Alle" zeigt jedes Ziel, das an genau diesem Pfad MCP kann — dieselbe Fähigkeitsfrage wie unter
|
|
1018
|
+
// dem Harness-Chip, allein über den Ort aus MCP_PLAETZE gestellt. Kein Harness-Name im Filter
|
|
1019
|
+
// (E16): heute liefert das nur Claude Code, weil Hermes keine Projektebene für MCP kennt; käme
|
|
1020
|
+
// ein Harness mit Projektebene dazu, stünde er hier von selbst und fiele nicht stumm weg.
|
|
1021
|
+
var verzMcpZiele = MCP_PLAETZE.filter(function (p) {
|
|
1022
|
+
return p.ort.art === 'arbeitsverzeichnis' && p.ort.pfad === pfad;
|
|
1023
|
+
});
|
|
1024
|
+
|
|
861
1025
|
if (reihenfolge.length === 0) {
|
|
862
1026
|
var leer = e('div', 'leerzeile');
|
|
863
1027
|
leer.appendChild(e('p', 'ohne', 'Hier liegt noch nichts.'));
|
|
864
1028
|
leer.appendChild(alleSkillsKnopf(pfad, teile));
|
|
1029
|
+
verzMcpZiele.forEach(function (mz) {
|
|
1030
|
+
var mcpKnopfLeer = mcpHinzufuegenKnopf(mz);
|
|
1031
|
+
if (mcpKnopfLeer) leer.appendChild(mcpKnopfLeer);
|
|
1032
|
+
});
|
|
865
1033
|
teil.appendChild(leer);
|
|
1034
|
+
verzMcpZiele.forEach(function (mz) {
|
|
1035
|
+
var mcpAbschnLeer = mcpAbschnitt(mz);
|
|
1036
|
+
if (mcpAbschnLeer) teil.appendChild(mcpAbschnLeer);
|
|
1037
|
+
});
|
|
866
1038
|
return teil;
|
|
867
1039
|
}
|
|
868
1040
|
|
|
@@ -896,13 +1068,7 @@
|
|
|
896
1068
|
nameSpan.appendChild(document.createTextNode(row.name));
|
|
897
1069
|
li.appendChild(nameSpan);
|
|
898
1070
|
|
|
899
|
-
|
|
900
|
-
var wasEl = e('span', 'was', row.beschreibung);
|
|
901
|
-
if (row.beschreibung.length > 88) wasEl.title = row.beschreibung;
|
|
902
|
-
li.appendChild(wasEl);
|
|
903
|
-
} else {
|
|
904
|
-
li.appendChild(e('span', 'was fehlt', 'keine Beschreibung in der SKILL.md'));
|
|
905
|
-
}
|
|
1071
|
+
li.appendChild(wasZelle(row.skill));
|
|
906
1072
|
|
|
907
1073
|
var qz = e('span', 'quelle');
|
|
908
1074
|
if (row.defekt) {
|
|
@@ -949,8 +1115,19 @@
|
|
|
949
1115
|
});
|
|
950
1116
|
teil.appendChild(ul);
|
|
951
1117
|
|
|
1118
|
+
// MCP-Abschnitt (E61-GUI-Nachtrag): auf Kartenebene unter „Alle", wie unter dem Harness-Chip,
|
|
1119
|
+
// der an diesem Pfad MCP kann.
|
|
1120
|
+
verzMcpZiele.forEach(function (mz) {
|
|
1121
|
+
var mcpAbschnAlle = mcpAbschnitt(mz);
|
|
1122
|
+
if (mcpAbschnAlle) teil.appendChild(mcpAbschnAlle);
|
|
1123
|
+
});
|
|
1124
|
+
|
|
952
1125
|
var fuss = e('div', 'zielfuss');
|
|
953
1126
|
fuss.appendChild(alleSkillsKnopf(pfad, teile));
|
|
1127
|
+
verzMcpZiele.forEach(function (mz) {
|
|
1128
|
+
var mcpKnopfAlle = mcpHinzufuegenKnopf(mz);
|
|
1129
|
+
if (mcpKnopfAlle) fuss.appendChild(mcpKnopfAlle);
|
|
1130
|
+
});
|
|
954
1131
|
teil.appendChild(fuss);
|
|
955
1132
|
|
|
956
1133
|
return teil;
|
|
@@ -1048,7 +1225,10 @@
|
|
|
1048
1225
|
// gehört zum gewählten Harness, nicht zur Karte: er steht rechts in der Chip-Leiste, relativ
|
|
1049
1226
|
// zum Verzeichnis, mit der Inaktiv-Marke daneben.
|
|
1050
1227
|
if (gewaehlt) {
|
|
1051
|
-
|
|
1228
|
+
// Nur die Anzeige: liegt der gelesene Ordner unterhalb des Verzeichnisses, steht er
|
|
1229
|
+
// relativ, sonst gekürzt. Beide Trenner, sonst greift das unter Windows nie (E68).
|
|
1230
|
+
var unterhalb = gewaehlt.verzeichnis.indexOf(pfad + '/') === 0
|
|
1231
|
+
|| gewaehlt.verzeichnis.indexOf(pfad + '\\') === 0;
|
|
1052
1232
|
chipleiste.appendChild(e('span', 'kartenordner mono',
|
|
1053
1233
|
unterhalb ? gewaehlt.verzeichnis.slice(pfad.length + 1) : pfadKurz(gewaehlt.verzeichnis)));
|
|
1054
1234
|
if (!harnessAktiv(aktivChip)) chipleiste.appendChild(e('span', 'ruhend', 'Harness inaktiv'));
|
|
@@ -1215,7 +1395,7 @@
|
|
|
1215
1395
|
function frageLoeschen(name, ziel, verzeichnis) {
|
|
1216
1396
|
loeschenKontext = { name: name, ziel: ziel };
|
|
1217
1397
|
$('dl-titel').textContent = '„' + name + '“ löschen?';
|
|
1218
|
-
$('dl-text').textContent = 'hmgr löscht ' + pfadKurz(verzeichnis) +
|
|
1398
|
+
$('dl-text').textContent = 'hmgr löscht ' + pfadKurz(verzeichnis) + TRENNER + name
|
|
1219
1399
|
+ ' unwiderruflich von der Platte, ein Ordner mit allem Inhalt oder ein Link. Das lässt sich nicht rückgängig machen.';
|
|
1220
1400
|
zeige($('d-loeschen'));
|
|
1221
1401
|
}
|
|
@@ -1400,7 +1580,7 @@
|
|
|
1400
1580
|
chip.addEventListener('click', function () { setzeQuellenfilter(s.quelleSchluessel); });
|
|
1401
1581
|
|
|
1402
1582
|
// Quellen-Chip-Paar (E60): links filtert der Name, rechts öffnet das Pfeilsymbol die Quelle
|
|
1403
|
-
// (Repo-Seite im Browser, lokale Arbeitskopie im
|
|
1583
|
+
// (Repo-Seite im Browser, lokale Arbeitskopie im Dateimanager). Kein <a> im <button>, deshalb
|
|
1404
1584
|
// stehen sie nebeneinander in einer gemeinsamen Hülle.
|
|
1405
1585
|
var quelle = QUELLEN.filter(function (q) { return q.text === s.quelleText; })[0];
|
|
1406
1586
|
if (quelle) {
|
|
@@ -1411,7 +1591,7 @@
|
|
|
1411
1591
|
if (lokal) oeffnen.type = 'button'; else oeffnen.href = '#';
|
|
1412
1592
|
oeffnen.className = 'chipoeffnen';
|
|
1413
1593
|
oeffnen.title = quelle.text;
|
|
1414
|
-
oeffnen.setAttribute('aria-label', s.quelleName + ' ' + (lokal ? 'im
|
|
1594
|
+
oeffnen.setAttribute('aria-label', s.quelleName + ' ' + (lokal ? 'im Dateimanager öffnen' : 'im Browser öffnen'));
|
|
1415
1595
|
oeffnen.appendChild(externSvg());
|
|
1416
1596
|
oeffnen.addEventListener('click', function (ev) {
|
|
1417
1597
|
ev.preventDefault();
|
|
@@ -1455,6 +1635,24 @@
|
|
|
1455
1635
|
return box;
|
|
1456
1636
|
}
|
|
1457
1637
|
|
|
1638
|
+
/** Beschreibungs-Zelle der Ziel-Tabellen: die Beschreibung (E39) und darunter die immer
|
|
1639
|
+
sichtbaren Frontmatter-Marken (E55), derselbe Helfer wie im Katalog. Ohne Skill (Link ohne
|
|
1640
|
+
Quelle) bleibt nur die Fehlt-Zeile. */
|
|
1641
|
+
function wasZelle(skill) {
|
|
1642
|
+
var feld = e('span', 'wasfeld');
|
|
1643
|
+
var beschreibung = skill ? skill.beschreibung : undefined;
|
|
1644
|
+
if (beschreibung) {
|
|
1645
|
+
var wasEl = e('span', 'was', beschreibung);
|
|
1646
|
+
if (beschreibung.length > 88) wasEl.title = beschreibung;
|
|
1647
|
+
feld.appendChild(wasEl);
|
|
1648
|
+
} else {
|
|
1649
|
+
feld.appendChild(e('span', 'was fehlt', 'keine Beschreibung in der SKILL.md'));
|
|
1650
|
+
}
|
|
1651
|
+
var marken = skill ? skillMarkenBox(skill) : null;
|
|
1652
|
+
if (marken) feld.appendChild(marken);
|
|
1653
|
+
return feld;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1458
1656
|
/** Zeilen des Aufklappbereichs einer Skill-Zeile aus dem Frontmatter (E55), nur wenn vorhanden. */
|
|
1459
1657
|
function skillDetails(skill) {
|
|
1460
1658
|
var zeilen = [];
|
|
@@ -1629,7 +1827,7 @@
|
|
|
1629
1827
|
li.appendChild(e('span', r.ok ? 'ok' : 'aus', r.ok ? 'Link' : 'Fehler'));
|
|
1630
1828
|
var s = e('span');
|
|
1631
1829
|
s.appendChild(beschriftet(e('b', null, zielTitel(r.ziel)), r.ziel.ziel.harness));
|
|
1632
|
-
s.appendChild(e('span', 'mono', r.ok ? pfadKurz(r.ziel.verzeichnis) +
|
|
1830
|
+
s.appendChild(e('span', 'mono', r.ok ? pfadKurz(r.ziel.verzeichnis) + TRENNER + skill.name : r.fehler));
|
|
1633
1831
|
li.appendChild(s);
|
|
1634
1832
|
ul.appendChild(li);
|
|
1635
1833
|
});
|
|
@@ -1939,6 +2137,35 @@
|
|
|
1939
2137
|
});
|
|
1940
2138
|
window.hmgr.onAktualisierenAnstossen(function () { aktualisierenLauf(true); });
|
|
1941
2139
|
|
|
2140
|
+
// Stilles Neuladen bei Änderungen außerhalb der GUI (E62, Fokus-Wechsel und Datei-Watcher): nur
|
|
2141
|
+
// ladeAlles, keine Meldung, kein Spinner-Zwang. Nachrutsch-sicher: höchstens ein Durchlauf läuft,
|
|
2142
|
+
// höchstens ein weiterer ist vorgemerkt, ein laufender Durchlauf wird nie von einem zweiten
|
|
2143
|
+
// überholt. Fehler landen wie sonst über rufe() in der Meldungsleiste, kein stiller Absturz.
|
|
2144
|
+
var stillerReloadLaeuft = false;
|
|
2145
|
+
var stillerReloadVorgemerkt = false;
|
|
2146
|
+
async function stillerReload() {
|
|
2147
|
+
// Läuft gerade ein Aktualisieren-Lauf, wird nicht parallel geladen: er endet ohnehin mit
|
|
2148
|
+
// ladeAlles und nimmt die Änderung mit. Sonst bauten zwei Läufe gleichzeitig die Ansicht auf.
|
|
2149
|
+
if ($('updatestart').disabled) return;
|
|
2150
|
+
if (stillerReloadLaeuft) {
|
|
2151
|
+
stillerReloadVorgemerkt = true;
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
stillerReloadLaeuft = true;
|
|
2155
|
+
try {
|
|
2156
|
+
await ladeAlles();
|
|
2157
|
+
} catch (fehler) {
|
|
2158
|
+
// rufe() hat den Fehler schon gemeldet, hier bleibt nur: den Durchlauf nicht wiederholen.
|
|
2159
|
+
} finally {
|
|
2160
|
+
stillerReloadLaeuft = false;
|
|
2161
|
+
if (stillerReloadVorgemerkt) {
|
|
2162
|
+
stillerReloadVorgemerkt = false;
|
|
2163
|
+
void stillerReload();
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
window.hmgr.onNeuLadenAnstossen(function () { void stillerReload(); });
|
|
2168
|
+
|
|
1942
2169
|
function baueBericht(ergebnis) {
|
|
1943
2170
|
var box = $('bericht');
|
|
1944
2171
|
box.textContent = '';
|
|
@@ -2030,6 +2257,391 @@
|
|
|
2030
2257
|
});
|
|
2031
2258
|
}
|
|
2032
2259
|
|
|
2260
|
+
/* ================= MCP (E61-Folgerunde, F46: reiner Katalog) ================= */
|
|
2261
|
+
/* Der Katalog aus ~/.hmgr/mcp.yaml wird hier nicht bearbeitet, hmgr liest ihn nur: kein Anlegen,
|
|
2262
|
+
kein Ändern, nur Einhängen (entwurf/gui.html). Abweichung vom Entwurf (F43): kein Erfolgsdialog
|
|
2263
|
+
und kein Wirkungs-Vermerk an der Orts-Zeile, Erfolg läuft über die bestehende Meldungsleiste
|
|
2264
|
+
(melde()), bei Claude Code mit dem Hinweis des Kerns zur nächsten Sitzung angehängt, bei Hermes
|
|
2265
|
+
ohne (der Kern liefert den Hinweis nur für Claude Code).
|
|
2266
|
+
F46 („die Ziele gehören nicht in die MCP, sondern die MCP in die Ziele"): wo ein Server hängt und
|
|
2267
|
+
das Aushängen zeigen seither nur noch die Ziel-Karten (mcpAbschnitt weiter unten), diese Ansicht
|
|
2268
|
+
bleibt reiner Katalog — Name, Beschreibung, Homepage, Konfigblock, F39-Warnzeile, Notizen,
|
|
2269
|
+
Einhängen. mcpOrtDatei/-Scope/-Spur bleiben hier stehen, sie tragen weiterhin den Einhängen-
|
|
2270
|
+
Dialog (baueMcpWahl) und den Aushängen-Text (frageMcpAus), beide von den Ziel-Karten geteilt. */
|
|
2271
|
+
|
|
2272
|
+
/* Wohin der Eintrag geschrieben wird, rein zur Anzeige: Claude Code führt Projekt- wie globale
|
|
2273
|
+
Server in ~/.claude.json (die geteilte .mcp.json der Projektwurzel bleibt unangetastet, E57),
|
|
2274
|
+
Hermes je Ort in seiner config.yaml. */
|
|
2275
|
+
function mcpOrtDatei(ziel) {
|
|
2276
|
+
if (ziel.harness === 'hermes')
|
|
2277
|
+
return ziel.ort.art === 'profil'
|
|
2278
|
+
? pfadSchreibweise('~/.hermes/profiles/' + ziel.ort.name + '/config.yaml')
|
|
2279
|
+
: pfadSchreibweise('~/.hermes/config.yaml');
|
|
2280
|
+
return pfadSchreibweise('~/.claude.json');
|
|
2281
|
+
}
|
|
2282
|
+
function mcpOrtScope(ziel) {
|
|
2283
|
+
if (ziel.harness !== 'claude-code') return null;
|
|
2284
|
+
return ziel.ort.art === 'arbeitsverzeichnis' ? 'Scope local' : 'Scope user';
|
|
2285
|
+
}
|
|
2286
|
+
/** Was in der Ort-Zeile hinter dem Namen steht: die Datei mit dem Scope. Anders als im Entwurf
|
|
2287
|
+
auch beim Arbeitsverzeichnis, denn der Ortsname der App trägt den Pfad schon (`ortName`); im
|
|
2288
|
+
Entwurf hieß er nur „Arbeitsverzeichnis", weshalb der Pfad dort hier stand. Jede Angabe
|
|
2289
|
+
einmal: Pfad im Namen, Datei und Scope daneben. */
|
|
2290
|
+
function mcpOrtSpur(ziel) {
|
|
2291
|
+
var scope = mcpOrtScope(ziel);
|
|
2292
|
+
return mcpOrtDatei(ziel) + (scope ? ' · ' + scope : '');
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
function mcpKonfigArt(konfig) { return 'command' in konfig ? 'stdio' : 'HTTP'; }
|
|
2296
|
+
/** Der Konfigblock im Wortlaut, so wie er in der mcp.yaml steht: die ${VAR}-Verweise darin bleiben
|
|
2297
|
+
sichtbar, nur sie sind der Grund für die Warnzeile darunter. Nie ein Klartext-Secret (F41). */
|
|
2298
|
+
function mcpKonfigWortlaut(konfig) {
|
|
2299
|
+
if ('command' in konfig) {
|
|
2300
|
+
var text = [konfig.command].concat(konfig.args || []).join(' ');
|
|
2301
|
+
Object.keys(konfig.env || {}).forEach(function (k) { text += ' · ' + k + '=' + konfig.env[k]; });
|
|
2302
|
+
return text;
|
|
2303
|
+
}
|
|
2304
|
+
var url = konfig.url;
|
|
2305
|
+
Object.keys(konfig.headers || {}).forEach(function (k) { url += ' · ' + k + ': ' + konfig.headers[k]; });
|
|
2306
|
+
return url;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
/** Die Homepage eines Servers, dasselbe Muster wie eine Quelle (E60): der Name bleibt ruhig, das
|
|
2310
|
+
Signalorange trägt nur das Pfeilsymbol. Öffnet über denselben Kanal wie eine Quelle (E60): eine
|
|
2311
|
+
http(s)-Adresse geht in den Browser. */
|
|
2312
|
+
function mcpHeimLink(url) {
|
|
2313
|
+
var a = document.createElement('a');
|
|
2314
|
+
a.className = 'qlink';
|
|
2315
|
+
a.href = '#';
|
|
2316
|
+
a.title = url;
|
|
2317
|
+
a.setAttribute('aria-label', url + ' im Browser öffnen');
|
|
2318
|
+
a.appendChild(document.createTextNode(url));
|
|
2319
|
+
a.appendChild(externSvg());
|
|
2320
|
+
a.addEventListener('click', function (ev) {
|
|
2321
|
+
ev.preventDefault();
|
|
2322
|
+
window.hmgr.quelleOeffnen(url).catch(function () {});
|
|
2323
|
+
});
|
|
2324
|
+
return a;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
/** F39: die Prüf-Funktion ist dieselbe wie beim tatsächlichen Einhängen, hier aber nur gegen die
|
|
2328
|
+
Prozess-Umgebung von hmgr (der Main-Prozess liefert `fehlendeVariablen` schon fertig mit der
|
|
2329
|
+
Übersicht, ohne den ortsbezogenen .env-Abgleich, der bleibt dem Einhängen selbst vorbehalten
|
|
2330
|
+
und kommt dann als `warnung` zurück). Wiederverwendet die bestehende .ruhezeile-Optik. */
|
|
2331
|
+
function mcpWarnZeile(fehlend) {
|
|
2332
|
+
var mehrzahl = fehlend.length > 1;
|
|
2333
|
+
var namen = fehlend.map(function (v) { return '${' + v + '}'; }).join(', ');
|
|
2334
|
+
var rz = e('div', 'ruhezeile');
|
|
2335
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
2336
|
+
svg.setAttribute('viewBox', '0 0 16 16');
|
|
2337
|
+
svg.setAttribute('stroke-width', '1.6');
|
|
2338
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
2339
|
+
svg.innerHTML = '<circle cx="8" cy="8" r="6.5"/><path d="M8 4.5v4M8 10.5v.5"/>';
|
|
2340
|
+
rz.appendChild(svg);
|
|
2341
|
+
rz.appendChild(e('p', null, namen + (mehrzahl ? ' sind' : ' ist') + ' in der Umgebung von hmgr nicht gesetzt. Ob '
|
|
2342
|
+
+ (mehrzahl ? 'die Variablen' : 'die Variable') + ' beim Start des Harness gesetzt ' + (mehrzahl ? 'sind' : 'ist')
|
|
2343
|
+
+ ', kann hmgr nicht wissen.'));
|
|
2344
|
+
return rz;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
/** Notizen sind das Langsame am Eintrag (entwurf/gui.html): zugeklappt, öffnen sich nur, wenn
|
|
2348
|
+
jemand sie sucht. Der Wortlaut bleibt, wie er in der mcp.yaml steht, Zeilenumbrüche
|
|
2349
|
+
eingeschlossen (weiss-raum: pre-line in styles.css). */
|
|
2350
|
+
function mcpNotizenBlock(text) {
|
|
2351
|
+
var d = document.createElement('details');
|
|
2352
|
+
d.className = 'notizen';
|
|
2353
|
+
var s = document.createElement('summary');
|
|
2354
|
+
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
2355
|
+
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
2356
|
+
pfeil.setAttribute('class', 'keil');
|
|
2357
|
+
pfeil.setAttribute('fill', 'none');
|
|
2358
|
+
pfeil.setAttribute('stroke', 'currentColor');
|
|
2359
|
+
pfeil.setAttribute('stroke-width', '1.7');
|
|
2360
|
+
pfeil.setAttribute('stroke-linejoin', 'round');
|
|
2361
|
+
pfeil.setAttribute('aria-hidden', 'true');
|
|
2362
|
+
pfeil.innerHTML = '<path d="M4.3 2.5 7.8 6 4.3 9.5"/>';
|
|
2363
|
+
s.appendChild(pfeil);
|
|
2364
|
+
s.appendChild(document.createTextNode('Notizen'));
|
|
2365
|
+
d.appendChild(s);
|
|
2366
|
+
d.appendChild(e('p', null, text));
|
|
2367
|
+
return d;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
function mcpKarte(k) {
|
|
2371
|
+
var eintrag = k.eintrag;
|
|
2372
|
+
var kEl = e('div', 'mcp');
|
|
2373
|
+
|
|
2374
|
+
var kopf = e('div', 'mcpkopf');
|
|
2375
|
+
kopf.appendChild(e('b', null, eintrag.name));
|
|
2376
|
+
if (eintrag.homepage) kopf.appendChild(mcpHeimLink(eintrag.homepage));
|
|
2377
|
+
var ein = e('button', 'knopf leise klein', 'Einhängen …');
|
|
2378
|
+
ein.type = 'button';
|
|
2379
|
+
ein.setAttribute('aria-label', eintrag.name + ' einhängen');
|
|
2380
|
+
ein.addEventListener('click', function () { oeffneMcpEinhaengen(k); });
|
|
2381
|
+
kopf.appendChild(ein);
|
|
2382
|
+
kEl.appendChild(kopf);
|
|
2383
|
+
|
|
2384
|
+
kEl.appendChild(e('p', 'was', eintrag.beschreibung));
|
|
2385
|
+
|
|
2386
|
+
var konf = e('div', 'kette mcpkonfig');
|
|
2387
|
+
konf.appendChild(e('span', 'glied', mcpKonfigArt(eintrag.konfig)));
|
|
2388
|
+
konf.appendChild(e('span', 'mono', mcpKonfigWortlaut(eintrag.konfig)));
|
|
2389
|
+
kEl.appendChild(konf);
|
|
2390
|
+
|
|
2391
|
+
if (k.fehlendeVariablen.length > 0) kEl.appendChild(mcpWarnZeile(k.fehlendeVariablen));
|
|
2392
|
+
if (eintrag.notizen) kEl.appendChild(mcpNotizenBlock(eintrag.notizen));
|
|
2393
|
+
|
|
2394
|
+
return kEl;
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
/** Ohne Katalog steht hier kein leerer Kasten, sondern der Weg zur Datei: anlegen lässt sich ein
|
|
2398
|
+
Eintrag nur dort, hmgr schreibt sie nie (E61). */
|
|
2399
|
+
function mcpEinladung() {
|
|
2400
|
+
var kasten = e('div', 'startfeld mcpstart');
|
|
2401
|
+
var p1 = e('p');
|
|
2402
|
+
p1.appendChild(document.createTextNode('Noch kein Server im Katalog. hmgr liest ihn aus '));
|
|
2403
|
+
p1.appendChild(e('span', 'mono', pfadSchreibweise('~/.hmgr/mcp.yaml')));
|
|
2404
|
+
p1.appendChild(document.createTextNode(' und schreibt die Datei nie.'));
|
|
2405
|
+
kasten.appendChild(p1);
|
|
2406
|
+
var p2 = e('p');
|
|
2407
|
+
p2.appendChild(document.createTextNode('Trag einen Server dort von Hand ein, oder lass ihn von dem LLM eintragen, das ihn ohnehin gerade einrichtet. Das Format steht in '));
|
|
2408
|
+
p2.appendChild(e('span', 'mono', 'hmgr help mcp'));
|
|
2409
|
+
p2.appendChild(document.createTextNode('.'));
|
|
2410
|
+
kasten.appendChild(p2);
|
|
2411
|
+
return kasten;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
function baueMcp() {
|
|
2415
|
+
var w = $('mcpliste');
|
|
2416
|
+
w.textContent = '';
|
|
2417
|
+
if (MCP_KATALOG.length === 0) w.appendChild(mcpEinladung());
|
|
2418
|
+
else MCP_KATALOG.forEach(function (k) { w.appendChild(mcpKarte(k)); });
|
|
2419
|
+
$('navzahlmcp').textContent = MCP_KATALOG.length;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
/* ---------------- Einhängen (F42) ---------------- */
|
|
2423
|
+
|
|
2424
|
+
var mcpLaufend = null;
|
|
2425
|
+
var mcpPlaetzeAuswahl = [];
|
|
2426
|
+
// Sperre gegen Doppel-Anstoß (wie beim Hinzufügen einer Quelle, E54): zwischen Klick und Dialog
|
|
2427
|
+
// liegt ein IPC-Aufruf. Ein zweiter Klick in dieser Lücke riefe showModal() an einem schon
|
|
2428
|
+
// offenen Dialog auf, und das ist ein Fehler, kein zweiter Dialog.
|
|
2429
|
+
var mcpWahlOeffnet = false;
|
|
2430
|
+
|
|
2431
|
+
async function oeffneMcpEinhaengen(k) {
|
|
2432
|
+
if (mcpWahlOeffnet || $('d-mcp').open) return;
|
|
2433
|
+
mcpWahlOeffnet = true;
|
|
2434
|
+
try {
|
|
2435
|
+
await baueMcpWahl(k);
|
|
2436
|
+
} finally {
|
|
2437
|
+
mcpWahlOeffnet = false;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
async function baueMcpWahl(k) {
|
|
2442
|
+
mcpLaufend = k;
|
|
2443
|
+
$('dm-name').textContent = k.eintrag.name;
|
|
2444
|
+
$('dm-text').textContent = 'Ein Ort je Einhängen. agents kennt keine MCP-Server und steht deshalb nicht zur Wahl.';
|
|
2445
|
+
var box = $('dm-orte');
|
|
2446
|
+
box.textContent = '';
|
|
2447
|
+
// Die Plätze kommen aus dem Kern (F42, E16): die Fähigkeit hängt am Adapter, nicht am
|
|
2448
|
+
// Harness-Namen, hier wird nicht selbst entschieden, wer welche Ebene kennt.
|
|
2449
|
+
var plaetze = await rufe(function () { return window.hmgr.mcpPlaetze(); });
|
|
2450
|
+
mcpPlaetzeAuswahl = plaetze;
|
|
2451
|
+
plaetze.forEach(function (ziel, i) {
|
|
2452
|
+
var schon = k.eingehaengt.some(function (z) { return zielKey(z) === zielKey(ziel); });
|
|
2453
|
+
var aktiv = harnessAktiv(ziel.harness);
|
|
2454
|
+
var label = e('label', 'wahlzeile');
|
|
2455
|
+
var input = document.createElement('input');
|
|
2456
|
+
input.type = 'radio';
|
|
2457
|
+
input.name = 'mcport';
|
|
2458
|
+
input.value = String(i);
|
|
2459
|
+
input.disabled = schon || !aktiv;
|
|
2460
|
+
label.appendChild(input);
|
|
2461
|
+
|
|
2462
|
+
var txt = e('span');
|
|
2463
|
+
txt.appendChild(beschriftet(e('b', null, zielTitel({ ziel: ziel })), ziel.harness));
|
|
2464
|
+
txt.appendChild(e('span', 'mono', mcpOrtSpur(ziel)));
|
|
2465
|
+
|
|
2466
|
+
if (schon) {
|
|
2467
|
+
// F46: die Orte-Liste am Katalog-Eintrag gibt es nicht mehr, der Weg zum Aushängen führt
|
|
2468
|
+
// über den Entfernen-Knopf im MCP-Abschnitt der Ziel-Karte.
|
|
2469
|
+
txt.appendChild(e('span', 'notiz', 'Hängt hier schon. Zum Aushängen den Entfernen-Knopf am MCP-Abschnitt der Ziel-Karte benutzen.'));
|
|
2470
|
+
} else if (!aktiv) {
|
|
2471
|
+
txt.appendChild(e('span', 'notiz', harnessName(ziel.harness) + ' ist inaktiv. Schalt den Harness in den Einstellungen wieder ein, um hier einzuhängen.'));
|
|
2472
|
+
} else if (ziel.ort.art === 'arbeitsverzeichnis') {
|
|
2473
|
+
// E64: die Datei bleibt beim Einhängen unangetastet, aber nicht mehr grundsätzlich — der
|
|
2474
|
+
// Übernehmen-Knopf an der Ziel-Karte fasst sie auf ausdrücklichen Auftrag an.
|
|
2475
|
+
txt.appendChild(e('span', 'notiz', 'Gilt nur für dich. Eingehängt wird neben der geteilten .mcp.json des Projekts, die bleibt dabei unverändert.'));
|
|
2476
|
+
} else if (ziel.harness === 'hermes' && ziel.ort.art === 'home') {
|
|
2477
|
+
txt.appendChild(e('span', 'notiz', 'Hermes kennt keine Projektebene für MCP-Server, hier geht nur global.'));
|
|
2478
|
+
}
|
|
2479
|
+
label.appendChild(txt);
|
|
2480
|
+
box.appendChild(label);
|
|
2481
|
+
});
|
|
2482
|
+
zeige($('d-mcp'));
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
$('d-mcp').addEventListener('submit', function (ev) {
|
|
2486
|
+
if (ev.submitter && ev.submitter.value === 'los') {
|
|
2487
|
+
var gewaehlt = $('dm-orte').querySelector('input:checked');
|
|
2488
|
+
if (!gewaehlt) ev.preventDefault();
|
|
2489
|
+
}
|
|
2490
|
+
});
|
|
2491
|
+
|
|
2492
|
+
nachDialog($('d-mcp'), async function (wert) {
|
|
2493
|
+
var k = mcpLaufend;
|
|
2494
|
+
mcpLaufend = null;
|
|
2495
|
+
if (wert !== 'los' || !k) return;
|
|
2496
|
+
var gewaehlt = $('dm-orte').querySelector('input:checked');
|
|
2497
|
+
if (!gewaehlt) return;
|
|
2498
|
+
var ziel = mcpPlaetzeAuswahl[+gewaehlt.value];
|
|
2499
|
+
var ergebnis = await rufe(function () {
|
|
2500
|
+
return window.hmgr.mcpEinhaengen({ name: k.eintrag.name, harness: ziel.harness, ort: ziel.ort });
|
|
2501
|
+
});
|
|
2502
|
+
await ladeAlles();
|
|
2503
|
+
// Erfolg über die Meldungsleiste (F43): kein Erfolgsdialog, kein Vermerk an der Orts-Zeile. Der
|
|
2504
|
+
// Kern liefert den Hinweis zur Wirkung nur bei Claude Code, Hermes lädt selbst nach.
|
|
2505
|
+
var teile = [k.eintrag.name + ' hängt jetzt in ' + zielTitel({ ziel: ziel }) + '.'];
|
|
2506
|
+
if (ergebnis.hinweis) teile.push(ergebnis.hinweis);
|
|
2507
|
+
if (ergebnis.warnung) teile.push(ergebnis.warnung);
|
|
2508
|
+
melde(teile.join('\n'));
|
|
2509
|
+
});
|
|
2510
|
+
|
|
2511
|
+
/* ---------------- Aushängen ---------------- */
|
|
2512
|
+
|
|
2513
|
+
var mcpAusKontext = null;
|
|
2514
|
+
function frageMcpAus(eintrag, ziel) {
|
|
2515
|
+
mcpAusKontext = { eintrag: eintrag, ziel: ziel };
|
|
2516
|
+
$('dma-titel').textContent = eintrag.name + ' aus ' + zielTitel({ ziel: ziel }) + ' aushängen?';
|
|
2517
|
+
$('dma-text').textContent = 'Der Eintrag verschwindet aus ' + mcpOrtDatei(ziel)
|
|
2518
|
+
+ '. Der Katalog-Eintrag bleibt, du kannst ihn jederzeit wieder einhängen.';
|
|
2519
|
+
zeige($('d-mcpaus'));
|
|
2520
|
+
}
|
|
2521
|
+
nachDialog($('d-mcpaus'), async function (wert) {
|
|
2522
|
+
var kontext = mcpAusKontext;
|
|
2523
|
+
mcpAusKontext = null;
|
|
2524
|
+
if (wert !== 'ja' || !kontext) return;
|
|
2525
|
+
var ergebnis = await rufe(function () {
|
|
2526
|
+
return window.hmgr.mcpAushaengen({ name: kontext.eintrag.name, harness: kontext.ziel.harness, ort: kontext.ziel.ort });
|
|
2527
|
+
});
|
|
2528
|
+
await ladeAlles();
|
|
2529
|
+
var teile = [kontext.eintrag.name + ' aus ' + zielTitel({ ziel: kontext.ziel }) + ' ausgehängt.'];
|
|
2530
|
+
if (ergebnis.hinweis) teile.push(ergebnis.hinweis);
|
|
2531
|
+
melde(teile.join('\n'));
|
|
2532
|
+
});
|
|
2533
|
+
|
|
2534
|
+
/* ---------------- Übernehmen aus der geteilten .mcp.json (E64) ---------------- */
|
|
2535
|
+
/* Der einzige Weg, auf dem hmgr die geteilte Datei anfasst, deshalb eine ausdrückliche Rückfrage:
|
|
2536
|
+
der Eintrag wandert in die private Konfiguration und verschwindet aus der Datei — für alle, die
|
|
2537
|
+
sie teilen. Der Name muss nicht im Katalog stehen, übernommen wird der Block aus der Datei. */
|
|
2538
|
+
var mcpUebernahmeKontext = null;
|
|
2539
|
+
function frageMcpUebernahme(name, ziel, datei) {
|
|
2540
|
+
mcpUebernahmeKontext = { name: name, ziel: ziel };
|
|
2541
|
+
$('dmu-titel').textContent = name + ' übernehmen?';
|
|
2542
|
+
$('dmu-text').textContent = name + ' wird in deine private Konfiguration für '
|
|
2543
|
+
+ zielTitel({ ziel: ziel }) + ' übernommen und aus ' + (datei || '.mcp.json')
|
|
2544
|
+
+ ' entfernt. Ist die Datei mit dem Team geteilt, verlieren die anderen den Eintrag, sobald die '
|
|
2545
|
+
+ 'Änderung committet ist.';
|
|
2546
|
+
zeige($('d-mcpueb'));
|
|
2547
|
+
}
|
|
2548
|
+
nachDialog($('d-mcpueb'), async function (wert) {
|
|
2549
|
+
var kontext = mcpUebernahmeKontext;
|
|
2550
|
+
mcpUebernahmeKontext = null;
|
|
2551
|
+
if (wert !== 'ja' || !kontext) return;
|
|
2552
|
+
var ergebnis = await rufe(function () {
|
|
2553
|
+
return window.hmgr.mcpUebernehmen({ name: kontext.name, harness: kontext.ziel.harness, ort: kontext.ziel.ort });
|
|
2554
|
+
});
|
|
2555
|
+
await ladeAlles();
|
|
2556
|
+
var teile = [kontext.name + ' gehört jetzt zu deiner privaten Konfiguration für '
|
|
2557
|
+
+ zielTitel({ ziel: kontext.ziel }) + ' und steht nicht mehr in ' + ergebnis.datei + '.'];
|
|
2558
|
+
if (ergebnis.hinweis) teile.push(ergebnis.hinweis);
|
|
2559
|
+
melde(teile.join('\n'));
|
|
2560
|
+
});
|
|
2561
|
+
|
|
2562
|
+
/* ---------------- MCP hinzufügen (E61-GUI-Nachtrag) ---------------- */
|
|
2563
|
+
/* Gegenrichtung zu oeffneMcpEinhaengen: von der Ziel-Karte aus, Mehrfachauswahl über den ganzen
|
|
2564
|
+
Katalog, dasselbe Dialogmuster wie „Skills hinzufügen" (E47) statt der Ortswahl von d-mcp. */
|
|
2565
|
+
var mcpHinzufuegenKontext = null;
|
|
2566
|
+
function dmhBilanzAktualisieren() {
|
|
2567
|
+
var n = $('dmh-liste').querySelectorAll('input:checked').length;
|
|
2568
|
+
$('dmh-bilanz').textContent = zahlwort(n, 'ausgewählt', 'ausgewählt');
|
|
2569
|
+
}
|
|
2570
|
+
function oeffneMcpHinzufuegen(ziel) {
|
|
2571
|
+
mcpHinzufuegenKontext = { ziel: ziel };
|
|
2572
|
+
$('dmh-ziel').textContent = zielTitel({ ziel: ziel });
|
|
2573
|
+
var box = $('dmh-liste');
|
|
2574
|
+
box.textContent = '';
|
|
2575
|
+
// Ohne Katalog stünde hier ein leerer Kasten mit einem Knopf, der nichts tut: hmgr legt
|
|
2576
|
+
// MCP-Einträge nie selbst an (E61), also nennt der Dialog denselben Weg wie die MCP-Ansicht.
|
|
2577
|
+
if (MCP_KATALOG.length === 0) {
|
|
2578
|
+
box.appendChild(e('p', 'ohne', 'Noch kein Server im Katalog. hmgr liest ihn aus '
|
|
2579
|
+
+ pfadSchreibweise('~/.hmgr/mcp.yaml') + ' und schreibt die Datei nie.'));
|
|
2580
|
+
}
|
|
2581
|
+
MCP_KATALOG.forEach(function (k, i) {
|
|
2582
|
+
var schon = k.eingehaengt.some(function (z) { return zielKey(z) === zielKey(ziel); });
|
|
2583
|
+
var label = e('label', 'wahlzeile');
|
|
2584
|
+
var input = document.createElement('input');
|
|
2585
|
+
input.type = 'checkbox';
|
|
2586
|
+
input.value = String(i);
|
|
2587
|
+
input.disabled = schon;
|
|
2588
|
+
input.addEventListener('change', dmhBilanzAktualisieren);
|
|
2589
|
+
label.appendChild(input);
|
|
2590
|
+
var txt = e('span');
|
|
2591
|
+
txt.appendChild(e('b', null, k.eintrag.name));
|
|
2592
|
+
if (k.eintrag.beschreibung) txt.appendChild(e('span', 'mono', k.eintrag.beschreibung));
|
|
2593
|
+
if (schon) txt.appendChild(e('span', 'notiz', 'Hängt hier schon.'));
|
|
2594
|
+
label.appendChild(txt);
|
|
2595
|
+
box.appendChild(label);
|
|
2596
|
+
});
|
|
2597
|
+
dmhBilanzAktualisieren();
|
|
2598
|
+
zeige($('d-mcp-hinzufuegen'));
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
$('d-mcp-hinzufuegen').addEventListener('submit', function (ev) {
|
|
2602
|
+
if (ev.submitter && ev.submitter.value === 'los') {
|
|
2603
|
+
var gewaehlt = Array.from($('dmh-liste').querySelectorAll('input:checked'));
|
|
2604
|
+
if (gewaehlt.length === 0) ev.preventDefault();
|
|
2605
|
+
}
|
|
2606
|
+
});
|
|
2607
|
+
|
|
2608
|
+
nachDialog($('d-mcp-hinzufuegen'), async function (wert) {
|
|
2609
|
+
if (wert !== 'los' || mcpHinzufuegenKontext === null) return;
|
|
2610
|
+
var ziel = mcpHinzufuegenKontext.ziel;
|
|
2611
|
+
var indizes = Array.from($('dmh-liste').querySelectorAll('input:checked')).map(function (i) { return +i.value; });
|
|
2612
|
+
var erfolge = 0;
|
|
2613
|
+
var hinweise = [];
|
|
2614
|
+
var fehlschlaege = [];
|
|
2615
|
+
for (var n = 0; n < indizes.length; n++) {
|
|
2616
|
+
var k = MCP_KATALOG[indizes[n]];
|
|
2617
|
+
try {
|
|
2618
|
+
var ergebnis = await rufe(function () {
|
|
2619
|
+
return window.hmgr.mcpEinhaengen({ name: k.eintrag.name, harness: ziel.harness, ort: ziel.ort });
|
|
2620
|
+
});
|
|
2621
|
+
erfolge++;
|
|
2622
|
+
if (ergebnis.hinweis && hinweise.indexOf(ergebnis.hinweis) < 0) hinweise.push(ergebnis.hinweis);
|
|
2623
|
+
// F39-Warnungen gesammelt im Erfolgs-Toast statt je Einhängen einzeln zu melden.
|
|
2624
|
+
if (ergebnis.warnung) hinweise.push(k.eintrag.name + ': ' + ergebnis.warnung);
|
|
2625
|
+
} catch (fehler) {
|
|
2626
|
+
// rufe() hat den Fehler zwar schon in die Meldungsleiste gesetzt, die Schluss-Meldung unten
|
|
2627
|
+
// überschreibt sie aber gleich wieder. Ein Teilerfolg muss beide Seiten nennen, sonst behauptet
|
|
2628
|
+
// der Toast einen Lauf ohne Fehler. Die übrigen Einträge laufen weiter.
|
|
2629
|
+
fehlschlaege.push(k.eintrag.name + ': ' + fehler.message);
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
ZIEL_OFFEN[kartenKey(ziel)] = true;
|
|
2633
|
+
await ladeAlles();
|
|
2634
|
+
var zeilen = [erfolge > 0
|
|
2635
|
+
? zahlwort(erfolge, 'MCP-Server hängt', 'MCP-Server hängen') + ' jetzt in ' + zielTitel({ ziel: ziel }) + '.'
|
|
2636
|
+
: 'Nichts eingehängt in ' + zielTitel({ ziel: ziel }) + '.'];
|
|
2637
|
+
hinweise.forEach(function (h) { zeilen.push(h); });
|
|
2638
|
+
if (fehlschlaege.length > 0) {
|
|
2639
|
+
zeilen.push(zahlwort(fehlschlaege.length, 'Eintrag ließ', 'Einträge ließen') + ' sich nicht einhängen:');
|
|
2640
|
+
fehlschlaege.forEach(function (f) { zeilen.push(f); });
|
|
2641
|
+
}
|
|
2642
|
+
melde(zeilen.join('\n'));
|
|
2643
|
+
});
|
|
2644
|
+
|
|
2033
2645
|
/* ================= EINSTELLUNGEN ================= */
|
|
2034
2646
|
|
|
2035
2647
|
/** Ziele eines Harness-Typs mit mindestens einem eigenen Link oder liegen gebliebener
|
|
@@ -2559,7 +3171,11 @@
|
|
|
2559
3171
|
|
|
2560
3172
|
// Die Fragen kommen fertig formuliert aus dem Kern; für die Anzeige werden Home-Pfade darin
|
|
2561
3173
|
// wie überall sonst auf ~ gekürzt (nur Anzeige, die Antwort transportiert keinen Text zurück).
|
|
2562
|
-
|
|
3174
|
+
/** Wie pfadKurz, aber mitten im Fließtext einer Rückfrage — und ebenso mit beiden Trennern (E68). */
|
|
3175
|
+
function frageKurz(text) {
|
|
3176
|
+
if (!HOME) return text;
|
|
3177
|
+
return text.split(HOME + '/').join('~/').split(HOME + '\\').join('~\\');
|
|
3178
|
+
}
|
|
2563
3179
|
window.hmgr.onRueckfrage(function (nachricht) {
|
|
2564
3180
|
if (nachricht.art === 'bestaetigen') {
|
|
2565
3181
|
$('db-frage').textContent = frageKurz(nachricht.frage);
|
|
@@ -2607,5 +3223,5 @@
|
|
|
2607
3223
|
|
|
2608
3224
|
/* ---------------- Start ---------------- */
|
|
2609
3225
|
|
|
2610
|
-
|
|
3226
|
+
ladeUmgebung().then(ladeAlles).catch(function (fehler) { melde(fehler.message || String(fehler)); });
|
|
2611
3227
|
})();
|