hmgr 0.0.5 → 0.0.7
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/bundle-yaml.js +87 -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 +182 -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/paket-version.js +25 -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 +11 -3
- package/dist/cli/hilfe.js +197 -0
- package/dist/cli/main.js +255 -7
- package/dist/gui/beobachter.js +108 -0
- package/dist/gui/login-pfad.js +120 -0
- package/dist/gui/main.js +462 -19
- package/dist/gui/mcp-ansicht.js +57 -0
- package/dist/gui/oeffnen.js +1 -1
- package/dist/gui/preload.cjs +20 -1
- package/dist/gui/renderer/index.html +108 -7
- package/dist/gui/renderer/renderer.js +1066 -142
- package/dist/gui/renderer/styles.css +85 -2
- package/dist/kern/anwendungsfaelle.js +624 -12
- package/dist/kern/modell.js +221 -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/dist/ports/zustand.js +15 -1
- package/package.json +59 -2
|
@@ -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) {
|
|
@@ -280,10 +303,28 @@
|
|
|
280
303
|
var ZIELE = [];
|
|
281
304
|
var SKILLS = [];
|
|
282
305
|
var MARKETPLACES = [];
|
|
306
|
+
// Bundles aller bekannten Quellen (E69), entdeckt beim Quellen-Scan: kein eigener Katalog, keine
|
|
307
|
+
// eigene Ansicht. Grundlage der Bundle-Sektion im Dialog „Skills hinzufügen" (F68).
|
|
308
|
+
var BUNDLES = [];
|
|
309
|
+
// Definitionen, die sich nicht lesen ließen: genannt, damit eine kaputte Datei nicht stumm fehlt.
|
|
310
|
+
var BUNDLES_KAPUTT = [];
|
|
283
311
|
var QUELLEN = [];
|
|
284
312
|
var HARNESSE_ALLE = [];
|
|
285
313
|
var AKTIVE_HARNESSE = [];
|
|
286
314
|
var HARNESSE_NICHT_INSTALLIERT = [];
|
|
315
|
+
// MCP-Katalog (E61-Folgerunde): je Eintrag { eintrag, eingehaengt, fehlendeVariablen }, dazu
|
|
316
|
+
// Einträge in Harness-Konfigurationen, die nicht im Katalog stehen (nur genannt). Seit F46 nur
|
|
317
|
+
// noch Grundlage für den MCP-Abschnitt der Ziel-Karten (mcpZeilenFuerZiel), die MCP-Ansicht selbst
|
|
318
|
+
// zeigt keine Fundorte mehr.
|
|
319
|
+
var MCP_KATALOG = [];
|
|
320
|
+
var MCP_FREMD = [];
|
|
321
|
+
// Nicht-Katalog-Einträge aus geteilten, nur lesbaren Fundorten (E61-Nachtrag), etwa der
|
|
322
|
+
// .mcp.json einer Projektwurzel.
|
|
323
|
+
var MCP_FREMD_GETEILT = [];
|
|
324
|
+
// Alle Orte, an denen grundsätzlich ein MCP-Server hängen könnte (E61-GUI-Nachtrag), roh aus dem
|
|
325
|
+
// Kern (F42, E16): Grundlage für den MCP-Abschnitt und den „MCP hinzufügen …"-Knopf an den
|
|
326
|
+
// Ziel-Karten — nie am Harness-Namen entschieden, sondern an dieser Liste.
|
|
327
|
+
var MCP_PLAETZE = [];
|
|
287
328
|
var quellenFilter = 'alle';
|
|
288
329
|
var zielFilter = 'alle';
|
|
289
330
|
// Standard-Ref je Quelle (E60), aus dem Katalog abgeleitet (SKILLS trägt ihn schon je Skill): ein
|
|
@@ -340,9 +381,18 @@
|
|
|
340
381
|
|
|
341
382
|
/* ---------------- Laden ---------------- */
|
|
342
383
|
|
|
343
|
-
/**
|
|
344
|
-
|
|
345
|
-
|
|
384
|
+
/**
|
|
385
|
+
* Home-Verzeichnis und Plattform einmal beim Start holen (nur für die Anzeige, siehe oben).
|
|
386
|
+
* Danach stehen die fest formulierten Pfade im Bild in der Schreibweise der Plattform (E68).
|
|
387
|
+
*/
|
|
388
|
+
async function ladeUmgebung() {
|
|
389
|
+
var werte = await Promise.all([
|
|
390
|
+
rufe(function () { return window.hmgr.home(); }),
|
|
391
|
+
rufe(function () { return window.hmgr.plattform(); }),
|
|
392
|
+
]);
|
|
393
|
+
HOME = werte[0];
|
|
394
|
+
TRENNER = plattformTrenner(werte[1]);
|
|
395
|
+
$('mcpkatalogpfad').textContent = pfadSchreibweise('~/.hmgr/mcp.yaml');
|
|
346
396
|
}
|
|
347
397
|
|
|
348
398
|
async function ladeAlles() {
|
|
@@ -357,6 +407,9 @@
|
|
|
357
407
|
rufe(function () { return window.hmgr.harnessTypen(); }),
|
|
358
408
|
rufe(function () { return window.hmgr.aktiveHarnesses(); }),
|
|
359
409
|
rufe(function () { return window.hmgr.nichtVorhandeneHarnesses(); }),
|
|
410
|
+
rufe(function () { return window.hmgr.mcpUebersicht(); }),
|
|
411
|
+
rufe(function () { return window.hmgr.mcpPlaetze(); }),
|
|
412
|
+
rufe(function () { return window.hmgr.bundles(); }),
|
|
360
413
|
]);
|
|
361
414
|
ZIELE = ergebnisse[0];
|
|
362
415
|
SKILLS = ergebnisse[1];
|
|
@@ -367,6 +420,13 @@
|
|
|
367
420
|
HARNESSE_ALLE = ergebnisse[4];
|
|
368
421
|
AKTIVE_HARNESSE = ergebnisse[5];
|
|
369
422
|
HARNESSE_NICHT_INSTALLIERT = ergebnisse[6];
|
|
423
|
+
MCP_KATALOG = ergebnisse[7].katalog;
|
|
424
|
+
MCP_FREMD = ergebnisse[7].unbekannt;
|
|
425
|
+
MCP_FREMD_GETEILT = ergebnisse[7].unbekannteGeteilteFundorte;
|
|
426
|
+
MCP_PLAETZE = ergebnisse[8];
|
|
427
|
+
BUNDLES = ergebnisse[9].bundles;
|
|
428
|
+
BUNDLES.sort(function (a, b) { return a.name.localeCompare(b.name, 'de') || a.quelleText.localeCompare(b.quelleText, 'de'); });
|
|
429
|
+
BUNDLES_KAPUTT = ergebnisse[9].kaputte;
|
|
370
430
|
STANDARD_REF = {};
|
|
371
431
|
SKILLS.forEach(function (s) { if (STANDARD_REF[s.quelleText] === undefined) STANDARD_REF[s.quelleText] = s.ref; });
|
|
372
432
|
|
|
@@ -375,6 +435,7 @@
|
|
|
375
435
|
baueQuellenfilter();
|
|
376
436
|
baueMarketplaces();
|
|
377
437
|
baueEinstellungen();
|
|
438
|
+
baueMcp();
|
|
378
439
|
aktualisiereKopf();
|
|
379
440
|
}
|
|
380
441
|
|
|
@@ -425,7 +486,7 @@
|
|
|
425
486
|
|
|
426
487
|
/* ---------------- Navigation ---------------- */
|
|
427
488
|
|
|
428
|
-
var ANSICHTEN = ['ziele', 'katalog', 'update', 'plugins'];
|
|
489
|
+
var ANSICHTEN = ['ziele', 'katalog', 'update', 'plugins', 'mcp'];
|
|
429
490
|
function zeigeAnsicht(name) {
|
|
430
491
|
document.querySelectorAll('.plakette').forEach(function (a) {
|
|
431
492
|
if (a.dataset.ansicht === name) a.setAttribute('aria-current', 'page');
|
|
@@ -600,6 +661,110 @@
|
|
|
600
661
|
return kz;
|
|
601
662
|
}
|
|
602
663
|
|
|
664
|
+
/** Defekte Links (ohne Quelle) zuerst, sie brauchen Aufmerksamkeit (E45), sonst alphabetisch. */
|
|
665
|
+
function sortiereEintraege(eintraege) {
|
|
666
|
+
return eintraege.slice().sort(function (a, b) {
|
|
667
|
+
var da = a.art === 'link-ohne-quelle', db = b.art === 'link-ohne-quelle';
|
|
668
|
+
if (da !== db) return da ? -1 : 1;
|
|
669
|
+
var na = a.art === 'link' ? a.link.name : a.name;
|
|
670
|
+
var nb = b.art === 'link' ? b.link.name : b.name;
|
|
671
|
+
return na.localeCompare(nb, 'de');
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/** Eine Zeile der Link-Tabelle. `bundle` gesetzt heißt: die Zeile steht in einer Bundle-Gruppe
|
|
676
|
+
(E69), das Entfernen zerbricht dort das Bundle und der Dialog sagt es. */
|
|
677
|
+
function linkZeile(ei, z, bundle) {
|
|
678
|
+
var name = ei.art === 'link' ? ei.link.name : ei.name;
|
|
679
|
+
var li = e('li');
|
|
680
|
+
|
|
681
|
+
if (ei.art === 'link') li.appendChild(repoIcon(ei.link.checkout.quelle));
|
|
682
|
+
else li.appendChild(repoIcon({ art: 'lokal', pfad: ei.checkoutPfad }));
|
|
683
|
+
|
|
684
|
+
li.appendChild(e('span', 'name', name));
|
|
685
|
+
|
|
686
|
+
// Die Beschreibung aus der SKILL.md (E39): die Frage, wegen der die Zeile gelesen wird,
|
|
687
|
+
// deshalb die breiteste Spalte. Ein Link ohne bekannte Quelle kennt seinen Skill nicht.
|
|
688
|
+
li.appendChild(wasZelle(ei.art === 'link' ? ei.link.skill : null));
|
|
689
|
+
|
|
690
|
+
// Quelle: der Name, anklickbar, dahinter nur, was vom Normalfall abweicht — ein Ref, das
|
|
691
|
+
// vom Standard-Ref der Quelle abweicht, und der Grund „über Zeiger" (E57). „Manuell" (der
|
|
692
|
+
// Normalfall) entfällt ersatzlos, ein Plugin-Grund steht stattdessen in der Aktionsspalte.
|
|
693
|
+
var qz = e('span', 'quelle');
|
|
694
|
+
if (ei.art === 'link') {
|
|
695
|
+
var einschraenkung = z.einschraenkungen[name];
|
|
696
|
+
if (einschraenkung) qz.appendChild(document.createTextNode(einschraenkung + ' → '));
|
|
697
|
+
qz.appendChild(quelleLinkElement(ei.link.checkout.quelle));
|
|
698
|
+
var standardRef = STANDARD_REF[quelleKurz(ei.link.checkout.quelle)];
|
|
699
|
+
if (ei.link.checkout.ref && standardRef && ei.link.checkout.ref !== standardRef) {
|
|
700
|
+
qz.appendChild(e('span', 'ref', '@' + ei.link.checkout.ref));
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
qz.appendChild(e('span', 'mono', pfadKurz(ei.checkoutPfad)));
|
|
704
|
+
}
|
|
705
|
+
var grund = z.gruende[name];
|
|
706
|
+
if (grund && grund.art === 'zeiger') qz.appendChild(e('span', 'grundmarke', 'über Zeiger'));
|
|
707
|
+
li.appendChild(qz);
|
|
708
|
+
|
|
709
|
+
// Aus einem Plugin verlinkt: kein Entfernen-Knopf, das geht nur über das Plugin — der
|
|
710
|
+
// Grund erklärt hier zugleich, warum der Knopf fehlt.
|
|
711
|
+
if (grund && grund.art === 'plugin') {
|
|
712
|
+
li.appendChild(e('span', 'kein-weg', 'nur über Plugin „' + grund.plugin.split('@')[0] + '“'));
|
|
713
|
+
} else {
|
|
714
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
715
|
+
weg.type = 'button';
|
|
716
|
+
weg.setAttribute('aria-label', 'Link auf ' + name + ' entfernen');
|
|
717
|
+
weg.addEventListener('click', function () { frageEntfernen(name, z.ziel, bundle ? bundle.name : null); });
|
|
718
|
+
li.appendChild(weg);
|
|
719
|
+
}
|
|
720
|
+
return li;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/** Ein installiertes Bundle als Gruppe über seinen Skill-Zeilen (E69, F68): Kopf mit Name, Quelle
|
|
724
|
+
und „Entfernen" für das Ganze, darunter die Zeilen seiner Mitglieder. */
|
|
725
|
+
function bundleGruppe(b, z, eintraege) {
|
|
726
|
+
var gruppe = e('div', 'bundlegruppe');
|
|
727
|
+
var kopf = e('div', 'bundlekopf');
|
|
728
|
+
var titel = e('span', 'bundletitel');
|
|
729
|
+
titel.appendChild(e('span', 'etikett', 'Bundle'));
|
|
730
|
+
titel.appendChild(e('b', 'bundlename', b.name));
|
|
731
|
+
kopf.appendChild(titel);
|
|
732
|
+
var quelleEl = e('span', 'mono bundlequelle', b.quelleName);
|
|
733
|
+
quelleEl.title = b.quelleText;
|
|
734
|
+
kopf.appendChild(quelleEl);
|
|
735
|
+
kopf.appendChild(e('span', 'bundlezahl', zahlwort(b.skills.length, 'Skill', 'Skills')));
|
|
736
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
737
|
+
weg.type = 'button';
|
|
738
|
+
weg.setAttribute('aria-label', 'Bundle ' + b.name + ' aus ' + zielTitel(z) + ' entfernen');
|
|
739
|
+
weg.addEventListener('click', function () { frageBundleEntfernen(b, z.ziel); });
|
|
740
|
+
kopf.appendChild(weg);
|
|
741
|
+
gruppe.appendChild(kopf);
|
|
742
|
+
var ul = e('ul', 'linkliste');
|
|
743
|
+
eintraege.forEach(function (ei) { ul.appendChild(linkZeile(ei, z, b)); });
|
|
744
|
+
gruppe.appendChild(ul);
|
|
745
|
+
return gruppe;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** Ein Bundle als einzelner Streifen (Chip „Alle" einer Verzeichnis-Karte, E69/E53): Harness-
|
|
749
|
+
Zeichen, Name, seine Mitglieder als Text, Entfernen. Ohne eigene Tabelle. */
|
|
750
|
+
function bundleStreifen(b, z) {
|
|
751
|
+
var streifen = e('div', 'bundlegruppe');
|
|
752
|
+
var kopf = e('div', 'bundlekopf');
|
|
753
|
+
var titel = e('span', 'bundletitel');
|
|
754
|
+
titel.appendChild(harnessZeichen(z.ziel.harness));
|
|
755
|
+
titel.appendChild(e('span', 'etikett', 'Bundle'));
|
|
756
|
+
titel.appendChild(e('b', 'bundlename', b.name));
|
|
757
|
+
kopf.appendChild(titel);
|
|
758
|
+
kopf.appendChild(e('span', 'bundlemitglieder', b.skills.join(', ')));
|
|
759
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
760
|
+
weg.type = 'button';
|
|
761
|
+
weg.setAttribute('aria-label', 'Bundle ' + b.name + ' aus ' + zielTitel(z) + ' entfernen');
|
|
762
|
+
weg.addEventListener('click', function () { frageBundleEntfernen(b, z.ziel); });
|
|
763
|
+
kopf.appendChild(weg);
|
|
764
|
+
streifen.appendChild(kopf);
|
|
765
|
+
return streifen;
|
|
766
|
+
}
|
|
767
|
+
|
|
603
768
|
function linkTeil(z) {
|
|
604
769
|
var teil = document.createDocumentFragment();
|
|
605
770
|
var eigene = z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; });
|
|
@@ -628,11 +793,12 @@
|
|
|
628
793
|
}
|
|
629
794
|
}
|
|
630
795
|
|
|
796
|
+
var fussContainer;
|
|
631
797
|
if (eigene.length === 0) {
|
|
632
798
|
var leer = e('div', 'leerzeile');
|
|
633
799
|
leer.appendChild(e('p', 'ohne', 'Hier liegt noch nichts.'));
|
|
634
|
-
leer.appendChild(skillsKnopf(z));
|
|
635
800
|
teil.appendChild(leer);
|
|
801
|
+
fussContainer = leer;
|
|
636
802
|
} else {
|
|
637
803
|
// Fünf Spalten (E60): Icon, Skill, Beschreibung (die breiteste, sie beantwortet die Frage,
|
|
638
804
|
// wegen der die Tabelle gelesen wird), Quelle, Aktion.
|
|
@@ -644,76 +810,48 @@
|
|
|
644
810
|
kopf.appendChild(e('span'));
|
|
645
811
|
teil.appendChild(kopf);
|
|
646
812
|
|
|
647
|
-
|
|
648
|
-
//
|
|
649
|
-
|
|
813
|
+
// Bundles zuerst (E69): sie stehen als Gruppe über den Einzel-Zeilen, ihre Mitglieder liegen
|
|
814
|
+
// in der Gruppe statt in der allgemeinen Liste. Was zum Bundle gehört, sagt der Kern.
|
|
815
|
+
var bundles = (z.bundles || []).filter(function (b) { return b.skills.length > 0; });
|
|
816
|
+
var bundleVon = {};
|
|
817
|
+
bundles.forEach(function (b) { b.skills.forEach(function (n) { bundleVon[n] = b; }); });
|
|
818
|
+
bundles
|
|
650
819
|
.slice()
|
|
651
|
-
.sort(function (a, b) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
})
|
|
658
|
-
.forEach(function (ei) {
|
|
659
|
-
var name = ei.art === 'link' ? ei.link.name : ei.name;
|
|
660
|
-
var li = e('li');
|
|
661
|
-
|
|
662
|
-
if (ei.art === 'link') li.appendChild(repoIcon(ei.link.checkout.quelle));
|
|
663
|
-
else li.appendChild(repoIcon({ art: 'lokal', pfad: ei.checkoutPfad }));
|
|
664
|
-
|
|
665
|
-
li.appendChild(e('span', 'name', name));
|
|
666
|
-
|
|
667
|
-
// Die Beschreibung aus der SKILL.md (E39): die Frage, wegen der die Zeile gelesen wird,
|
|
668
|
-
// deshalb die breiteste Spalte. Ein Link ohne bekannte Quelle kennt seinen Skill nicht.
|
|
669
|
-
var beschreibung = ei.art === 'link' ? ei.link.skill.beschreibung : undefined;
|
|
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
|
-
}
|
|
677
|
-
|
|
678
|
-
// Quelle: der Name, anklickbar, dahinter nur, was vom Normalfall abweicht — ein Ref, das
|
|
679
|
-
// vom Standard-Ref der Quelle abweicht, und der Grund „über Zeiger" (E57). „Manuell" (der
|
|
680
|
-
// Normalfall) entfällt ersatzlos, ein Plugin-Grund steht stattdessen in der Aktionsspalte.
|
|
681
|
-
var qz = e('span', 'quelle');
|
|
682
|
-
if (ei.art === 'link') {
|
|
683
|
-
var einschraenkung = z.einschraenkungen[name];
|
|
684
|
-
if (einschraenkung) qz.appendChild(document.createTextNode(einschraenkung + ' → '));
|
|
685
|
-
qz.appendChild(quelleLinkElement(ei.link.checkout.quelle));
|
|
686
|
-
var standardRef = STANDARD_REF[quelleKurz(ei.link.checkout.quelle)];
|
|
687
|
-
if (ei.link.checkout.ref && standardRef && ei.link.checkout.ref !== standardRef) {
|
|
688
|
-
qz.appendChild(e('span', 'ref', '@' + ei.link.checkout.ref));
|
|
689
|
-
}
|
|
690
|
-
} else {
|
|
691
|
-
qz.appendChild(e('span', 'mono', pfadKurz(ei.checkoutPfad)));
|
|
692
|
-
}
|
|
693
|
-
var grund = z.gruende[name];
|
|
694
|
-
if (grund && grund.art === 'zeiger') qz.appendChild(e('span', 'grundmarke', 'über Zeiger'));
|
|
695
|
-
li.appendChild(qz);
|
|
696
|
-
|
|
697
|
-
// Aus einem Plugin verlinkt: kein Entfernen-Knopf, das geht nur über das Plugin — der
|
|
698
|
-
// Grund erklärt hier zugleich, warum der Knopf fehlt.
|
|
699
|
-
if (grund && grund.art === 'plugin') {
|
|
700
|
-
li.appendChild(e('span', 'kein-weg', 'nur über Plugin „' + grund.plugin.split('@')[0] + '“'));
|
|
701
|
-
} else {
|
|
702
|
-
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
703
|
-
weg.type = 'button';
|
|
704
|
-
weg.setAttribute('aria-label', 'Link auf ' + name + ' entfernen');
|
|
705
|
-
weg.addEventListener('click', function () { frageEntfernen(name, z.ziel); });
|
|
706
|
-
li.appendChild(weg);
|
|
707
|
-
}
|
|
708
|
-
ul.appendChild(li);
|
|
820
|
+
.sort(function (a, b) { return a.name.localeCompare(b.name, 'de'); })
|
|
821
|
+
.forEach(function (b) {
|
|
822
|
+
var mitglieder = eigene.filter(function (ei) {
|
|
823
|
+
return b.skills.indexOf(ei.art === 'link' ? ei.link.name : ei.name) > -1;
|
|
824
|
+
});
|
|
825
|
+
teil.appendChild(bundleGruppe(b, z, sortiereEintraege(mitglieder)));
|
|
709
826
|
});
|
|
710
|
-
|
|
827
|
+
|
|
828
|
+
var ul = e('ul', 'linkliste');
|
|
829
|
+
sortiereEintraege(
|
|
830
|
+
eigene.filter(function (ei) { return !bundleVon[ei.art === 'link' ? ei.link.name : ei.name]; }),
|
|
831
|
+
).forEach(function (ei) { ul.appendChild(linkZeile(ei, z, null)); });
|
|
832
|
+
if (ul.childNodes.length > 0) teil.appendChild(ul);
|
|
833
|
+
|
|
834
|
+
// MCP-Abschnitt (E61-GUI-Nachtrag): unter der Skill-Link-Tabelle, vor dem Kartenfuß mit den
|
|
835
|
+
// Knöpfen, Gegenrichtung zur MCP-Ansicht.
|
|
836
|
+
var mcpAbschnMitTabelle = mcpAbschnitt(z.ziel);
|
|
837
|
+
if (mcpAbschnMitTabelle) teil.appendChild(mcpAbschnMitTabelle);
|
|
711
838
|
|
|
712
839
|
var fuss = e('div', 'zielfuss');
|
|
713
|
-
fuss.appendChild(skillsKnopf(z));
|
|
714
840
|
teil.appendChild(fuss);
|
|
841
|
+
fussContainer = fuss;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Ohne eigene Links (leer-Fall) steht der MCP-Abschnitt hinter der Leerzeile, die dort schon den
|
|
845
|
+
// Kartenfuß bildet (Nachricht + Knöpfe in einer Zeile, wie überall sonst im leeren Zustand).
|
|
846
|
+
if (eigene.length === 0) {
|
|
847
|
+
var mcpAbschnOhneTabelle = mcpAbschnitt(z.ziel);
|
|
848
|
+
if (mcpAbschnOhneTabelle) teil.appendChild(mcpAbschnOhneTabelle);
|
|
715
849
|
}
|
|
716
850
|
|
|
851
|
+
fussContainer.appendChild(skillsKnopf(z));
|
|
852
|
+
var mcpKnopf = mcpHinzufuegenKnopf(z.ziel);
|
|
853
|
+
if (mcpKnopf) fussContainer.appendChild(mcpKnopf);
|
|
854
|
+
|
|
717
855
|
return teil;
|
|
718
856
|
}
|
|
719
857
|
|
|
@@ -739,6 +877,137 @@
|
|
|
739
877
|
return knopf;
|
|
740
878
|
}
|
|
741
879
|
|
|
880
|
+
/* ---------------- MCP an der Ziel-Karte (E61-GUI-Nachtrag) ---------------- */
|
|
881
|
+
/* Gegenrichtung zur MCP-Ansicht (E47-Muster): ob ein Harness an einem Ort MCP kann, entscheidet
|
|
882
|
+
nie der Harness-Name (E16), sondern einzig, ob MCP_PLAETZE (roh aus dem Kern, F42) diesen Ort
|
|
883
|
+
nennt. */
|
|
884
|
+
function mcpKannAn(ziel) {
|
|
885
|
+
var zk = zielKey(ziel);
|
|
886
|
+
return MCP_PLAETZE.some(function (p) { return zielKey(p) === zk; });
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/** Die MCP-Zeilen eines Ziels, aus den drei Quellen der MCP-Ansicht zusammengesucht: eingehängte
|
|
890
|
+
Katalog-Einträge (mit Entfernen-Knopf), ihre Fundorte in der geteilten .mcp.json des Projekts
|
|
891
|
+
(E61-Nachtrag, seit E64 mit Übernehmen-Knopf) und Einträge, die dort hängen, aber nicht im
|
|
892
|
+
Katalog stehen (stiller Vermerk). */
|
|
893
|
+
function mcpZeilenFuerZiel(ziel) {
|
|
894
|
+
var zk = zielKey(ziel);
|
|
895
|
+
var zeilen = [];
|
|
896
|
+
MCP_KATALOG.forEach(function (k) {
|
|
897
|
+
if (k.eingehaengt.some(function (z) { return zielKey(z) === zk; })) {
|
|
898
|
+
zeilen.push({ art: 'katalog', name: k.eintrag.name, beschreibung: k.eintrag.beschreibung, eintrag: k.eintrag });
|
|
899
|
+
}
|
|
900
|
+
k.geteilteFundorte.forEach(function (g) {
|
|
901
|
+
if (zielKey(g.ziel) === zk)
|
|
902
|
+
zeilen.push({ art: 'geteilt', name: k.eintrag.name, beschreibung: k.eintrag.beschreibung, datei: g.datei });
|
|
903
|
+
});
|
|
904
|
+
});
|
|
905
|
+
MCP_FREMD.forEach(function (x) { if (zielKey(x.ziel) === zk) zeilen.push({ art: 'fremd', name: x.name }); });
|
|
906
|
+
// Ein Eintrag aus der geteilten .mcp.json, den der Katalog nicht kennt: „nicht im Katalog" allein
|
|
907
|
+
// läse sich wie eine Einladung, ihn in die mcp.yaml zu schreiben. Wo er wirklich steht, gehört
|
|
908
|
+
// daneben — und übernehmen lässt er sich auch ohne Katalog-Eintrag (E64, der Block kommt aus der
|
|
909
|
+
// Datei).
|
|
910
|
+
MCP_FREMD_GETEILT.forEach(function (x) {
|
|
911
|
+
if (zielKey(x.ziel) === zk) zeilen.push({ art: 'fremd-geteilt', name: x.name, datei: x.datei });
|
|
912
|
+
});
|
|
913
|
+
zeilen.sort(function (a, b) { return a.name.localeCompare(b.name, 'de'); });
|
|
914
|
+
return zeilen;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/** Der MCP-Abschnitt unter der Skill-Link-Tabelle einer Ziel-Karte (E61-GUI-Nachtrag): nur, wenn
|
|
918
|
+
dort etwas eingehängt ist oder der „hinzufügen"-Knopf ohnehin sichtbar wäre (mcpKannAn). */
|
|
919
|
+
function mcpAbschnitt(ziel) {
|
|
920
|
+
var zeilen = mcpZeilenFuerZiel(ziel);
|
|
921
|
+
if (zeilen.length === 0 && !mcpKannAn(ziel)) return null;
|
|
922
|
+
var abschnitt = e('div', 'mcpkarteabschnitt');
|
|
923
|
+
abschnitt.appendChild(e('span', 'etikett', 'MCP'));
|
|
924
|
+
if (zeilen.length === 0) {
|
|
925
|
+
abschnitt.appendChild(e('p', 'ohne mcpkarteleer', 'Noch nichts eingehängt.'));
|
|
926
|
+
return abschnitt;
|
|
927
|
+
}
|
|
928
|
+
var ul = e('ul', 'mcpkartezeilen');
|
|
929
|
+
zeilen.forEach(function (row) {
|
|
930
|
+
var li = e('li');
|
|
931
|
+
li.appendChild(e('span', 'name', row.name));
|
|
932
|
+
if (row.art === 'katalog') {
|
|
933
|
+
li.appendChild(e('span', 'was', row.beschreibung || ''));
|
|
934
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
935
|
+
weg.type = 'button';
|
|
936
|
+
weg.setAttribute('aria-label', row.name + ' aus ' + zielTitel({ ziel: ziel }) + ' aushängen');
|
|
937
|
+
weg.addEventListener('click', function () { frageMcpAus(row.eintrag, ziel); });
|
|
938
|
+
li.appendChild(weg);
|
|
939
|
+
} else if (row.art === 'geteilt' || row.art === 'fremd-geteilt') {
|
|
940
|
+
// E64: der Vermerk nennt die Bedeutung („steht im Projekt, nicht bei dir"), nicht mehr bloß
|
|
941
|
+
// den Dateinamen — und daneben steht der Weg, das zu ändern.
|
|
942
|
+
li.appendChild(row.art === 'geteilt'
|
|
943
|
+
? e('span', 'was', row.beschreibung || '')
|
|
944
|
+
: e('span', 'was fehlt', 'nicht im Katalog'));
|
|
945
|
+
li.appendChild(e('span', 'geteiltvermerk', 'in der .mcp.json des Projekts'));
|
|
946
|
+
var her = e('button', 'knopf leise klein', 'Übernehmen …');
|
|
947
|
+
her.type = 'button';
|
|
948
|
+
her.setAttribute('aria-label', row.name + ' aus der .mcp.json des Projekts in deine private Konfiguration übernehmen');
|
|
949
|
+
her.addEventListener('click', function () { frageMcpUebernahme(row.name, ziel, row.datei); });
|
|
950
|
+
li.appendChild(her);
|
|
951
|
+
} else {
|
|
952
|
+
li.appendChild(e('span', 'was fehlt', 'nicht im Katalog'));
|
|
953
|
+
li.appendChild(e('span'));
|
|
954
|
+
}
|
|
955
|
+
ul.appendChild(li);
|
|
956
|
+
});
|
|
957
|
+
abschnitt.appendChild(ul);
|
|
958
|
+
return abschnitt;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/** Knopf „MCP hinzufügen …" im Kartenfuß neben „Skills hinzufügen" (E61-GUI-Nachtrag, Gegenrichtung
|
|
962
|
+
zur MCP-Ansicht): nur, wo der Harness an diesem Ort MCP kann (mcpKannAn, E16). */
|
|
963
|
+
function mcpHinzufuegenKnopf(ziel) {
|
|
964
|
+
if (!mcpKannAn(ziel)) return null;
|
|
965
|
+
var knopf = e('button', 'knopf leise klein mitsymbol', 'MCP hinzufügen …');
|
|
966
|
+
knopf.type = 'button';
|
|
967
|
+
knopf.insertBefore(harnessZeichen(ziel.harness), knopf.firstChild);
|
|
968
|
+
knopf.setAttribute('aria-label', 'MCP-Server zu ' + zielTitel({ ziel: ziel }) + ' hinzufügen (' + harnessName(ziel.harness) + ')');
|
|
969
|
+
knopf.addEventListener('click', function () { oeffneMcpHinzufuegen(ziel); });
|
|
970
|
+
return knopf;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/** Eigene Links eines Verzeichnisses über alle Harnesses gebündelt: Schlüssel Name+Quelle,
|
|
974
|
+
hängt ein Name je Harness aus verschiedener Quelle, gibt es je Quelle eine eigene Zeile
|
|
975
|
+
(E53 Pkt. 2). Einzige Zählregel für Skills: der Körper zeilt daraus, die Kartenköpfe zählen
|
|
976
|
+
dieselbe Struktur (E71). Ein einzelnes Ziel kommt als einelementige Liste.
|
|
977
|
+
Fremde Einträge bleiben draußen, defekte Links (ohne Quelle) zählen mit. */
|
|
978
|
+
function buendleEigene(teile) {
|
|
979
|
+
var zeilen = {};
|
|
980
|
+
var reihenfolge = [];
|
|
981
|
+
teile.forEach(function (z) {
|
|
982
|
+
z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; }).forEach(function (ei) {
|
|
983
|
+
var defekt = ei.art === 'link-ohne-quelle';
|
|
984
|
+
var name = defekt ? ei.name : ei.link.name;
|
|
985
|
+
var quelleSchluessel = defekt ? ('defekt\u0000' + ei.checkoutPfad)
|
|
986
|
+
: (quelleKurz(ei.link.checkout.quelle) + '@' + ei.link.checkout.ref);
|
|
987
|
+
// NUL als Trenner, kein Leerzeichen: Skill-Namen dürfen Leerzeichen enthalten, Quellen-Namen
|
|
988
|
+
// seit E59 auch, ein lokaler Pfad ohnehin — „a b“ + „x“ und „a“ + „b x“ ergäben sonst
|
|
989
|
+
// denselben Schlüssel. Immer als Escape geschrieben, nie als rohes Byte im Quelltext.
|
|
990
|
+
var key = name + '\u0000' + quelleSchluessel;
|
|
991
|
+
if (!zeilen[key]) {
|
|
992
|
+
zeilen[key] = {
|
|
993
|
+
name: name,
|
|
994
|
+
defekt: defekt,
|
|
995
|
+
icon: defekt ? { art: 'lokal', pfad: ei.checkoutPfad } : ei.link.checkout.quelle,
|
|
996
|
+
checkoutPfad: defekt ? ei.checkoutPfad : null,
|
|
997
|
+
quelle: defekt ? null : ei.link.checkout.quelle,
|
|
998
|
+
ref: defekt ? null : ei.link.checkout.ref,
|
|
999
|
+
einschraenkung: defekt ? null : z.einschraenkungen[name],
|
|
1000
|
+
skill: defekt ? null : ei.link.skill,
|
|
1001
|
+
teile: [],
|
|
1002
|
+
};
|
|
1003
|
+
reihenfolge.push(key);
|
|
1004
|
+
}
|
|
1005
|
+
zeilen[key].teile.push(z);
|
|
1006
|
+
});
|
|
1007
|
+
});
|
|
1008
|
+
return { zeilen: zeilen, reihenfolge: reihenfolge };
|
|
1009
|
+
}
|
|
1010
|
+
|
|
742
1011
|
function zielKarte(z) {
|
|
743
1012
|
var karte = e('div', 'ziel');
|
|
744
1013
|
var kopf = e('div', 'zielkopf');
|
|
@@ -762,8 +1031,10 @@
|
|
|
762
1031
|
warnspan.setAttribute('aria-label', 'braucht Aufmerksamkeit');
|
|
763
1032
|
kopf.appendChild(warnspan);
|
|
764
1033
|
}
|
|
765
|
-
|
|
766
|
-
|
|
1034
|
+
// Der Kopf zählt Skills, nicht Einträge (E71), über dieselbe Bündelung, aus der der Körper zeilt.
|
|
1035
|
+
// Bei einem Harness fällt die Zahl mit der Zahl eigener Einträge zusammen, die Regel bleibt eine.
|
|
1036
|
+
var zahl = buendleEigene([z]).reihenfolge.length;
|
|
1037
|
+
kopf.appendChild(e('span', 'zahl', zahl + (zahl === 1 ? ' Skill' : ' Skills')));
|
|
767
1038
|
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
768
1039
|
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
769
1040
|
pfeil.setAttribute('class', 'akkordeonpfeil');
|
|
@@ -826,46 +1097,47 @@
|
|
|
826
1097
|
if (beachteteAlle.length > 0) teil.appendChild(fremdKasten(beachteteAlle, true));
|
|
827
1098
|
if (ignorierteAlle.length > 0) teil.appendChild(ignoriertZeile(ignorierteAlle, true));
|
|
828
1099
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
var
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
// seit E59 auch, ein lokaler Pfad ohnehin — „a b“ + „x“ und „a“ + „b x“ ergäben sonst
|
|
841
|
-
// denselben Schlüssel. Immer als Escape geschrieben, nie als rohes Byte im Quelltext.
|
|
842
|
-
var key = name + '\u0000' + quelleSchluessel;
|
|
843
|
-
if (!zeilen[key]) {
|
|
844
|
-
zeilen[key] = {
|
|
845
|
-
name: name,
|
|
846
|
-
defekt: defekt,
|
|
847
|
-
icon: defekt ? { art: 'lokal', pfad: ei.checkoutPfad } : ei.link.checkout.quelle,
|
|
848
|
-
checkoutPfad: defekt ? ei.checkoutPfad : null,
|
|
849
|
-
quelle: defekt ? null : ei.link.checkout.quelle,
|
|
850
|
-
ref: defekt ? null : ei.link.checkout.ref,
|
|
851
|
-
einschraenkung: defekt ? null : z.einschraenkungen[name],
|
|
852
|
-
beschreibung: defekt ? undefined : ei.link.skill.beschreibung,
|
|
853
|
-
teile: [],
|
|
854
|
-
};
|
|
855
|
-
reihenfolge.push(key);
|
|
856
|
-
}
|
|
857
|
-
zeilen[key].teile.push(z);
|
|
858
|
-
});
|
|
1100
|
+
var gebuendelt = buendleEigene(teile);
|
|
1101
|
+
var zeilen = gebuendelt.zeilen;
|
|
1102
|
+
var reihenfolge = gebuendelt.reihenfolge;
|
|
1103
|
+
|
|
1104
|
+
// MCPs hängen am Verzeichnis, nicht am Harness-Chip (E61-GUI-Nachtrag): der Abschnitt unter
|
|
1105
|
+
// „Alle" zeigt jedes Ziel, das an genau diesem Pfad MCP kann — dieselbe Fähigkeitsfrage wie unter
|
|
1106
|
+
// dem Harness-Chip, allein über den Ort aus MCP_PLAETZE gestellt. Kein Harness-Name im Filter
|
|
1107
|
+
// (E16): heute liefert das nur Claude Code, weil Hermes keine Projektebene für MCP kennt; käme
|
|
1108
|
+
// ein Harness mit Projektebene dazu, stünde er hier von selbst und fiele nicht stumm weg.
|
|
1109
|
+
var verzMcpZiele = MCP_PLAETZE.filter(function (p) {
|
|
1110
|
+
return p.ort.art === 'arbeitsverzeichnis' && p.ort.pfad === pfad;
|
|
859
1111
|
});
|
|
860
1112
|
|
|
861
1113
|
if (reihenfolge.length === 0) {
|
|
862
1114
|
var leer = e('div', 'leerzeile');
|
|
863
1115
|
leer.appendChild(e('p', 'ohne', 'Hier liegt noch nichts.'));
|
|
864
1116
|
leer.appendChild(alleSkillsKnopf(pfad, teile));
|
|
1117
|
+
verzMcpZiele.forEach(function (mz) {
|
|
1118
|
+
var mcpKnopfLeer = mcpHinzufuegenKnopf(mz);
|
|
1119
|
+
if (mcpKnopfLeer) leer.appendChild(mcpKnopfLeer);
|
|
1120
|
+
});
|
|
865
1121
|
teil.appendChild(leer);
|
|
1122
|
+
verzMcpZiele.forEach(function (mz) {
|
|
1123
|
+
var mcpAbschnLeer = mcpAbschnitt(mz);
|
|
1124
|
+
if (mcpAbschnLeer) teil.appendChild(mcpAbschnLeer);
|
|
1125
|
+
});
|
|
866
1126
|
return teil;
|
|
867
1127
|
}
|
|
868
1128
|
|
|
1129
|
+
// Bundles unter „Alle" (E69): die Zeilen bleiben nach Name und Quelle gebündelt (E53), ein
|
|
1130
|
+
// Bundle gehört aber zu genau einem Harness — deshalb hier ein Streifen je Bundle mit seinem
|
|
1131
|
+
// Harness-Zeichen statt einer Umgruppierung der Tabelle. Unter dem Harness-Chip zeigt linkTeil
|
|
1132
|
+
// die volle Gruppe über den Zeilen.
|
|
1133
|
+
teile.forEach(function (z) {
|
|
1134
|
+
(z.bundles || [])
|
|
1135
|
+
.filter(function (b) { return b.skills.length > 0; })
|
|
1136
|
+
.slice()
|
|
1137
|
+
.sort(function (a, b) { return a.name.localeCompare(b.name, 'de'); })
|
|
1138
|
+
.forEach(function (b) { teil.appendChild(bundleStreifen(b, z)); });
|
|
1139
|
+
});
|
|
1140
|
+
|
|
869
1141
|
var kopf = e('div', 'linkkopf');
|
|
870
1142
|
kopf.appendChild(e('span'));
|
|
871
1143
|
kopf.appendChild(e('span', 'etikett', 'Skill'));
|
|
@@ -896,13 +1168,7 @@
|
|
|
896
1168
|
nameSpan.appendChild(document.createTextNode(row.name));
|
|
897
1169
|
li.appendChild(nameSpan);
|
|
898
1170
|
|
|
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
|
-
}
|
|
1171
|
+
li.appendChild(wasZelle(row.skill));
|
|
906
1172
|
|
|
907
1173
|
var qz = e('span', 'quelle');
|
|
908
1174
|
if (row.defekt) {
|
|
@@ -949,8 +1215,19 @@
|
|
|
949
1215
|
});
|
|
950
1216
|
teil.appendChild(ul);
|
|
951
1217
|
|
|
1218
|
+
// MCP-Abschnitt (E61-GUI-Nachtrag): auf Kartenebene unter „Alle", wie unter dem Harness-Chip,
|
|
1219
|
+
// der an diesem Pfad MCP kann.
|
|
1220
|
+
verzMcpZiele.forEach(function (mz) {
|
|
1221
|
+
var mcpAbschnAlle = mcpAbschnitt(mz);
|
|
1222
|
+
if (mcpAbschnAlle) teil.appendChild(mcpAbschnAlle);
|
|
1223
|
+
});
|
|
1224
|
+
|
|
952
1225
|
var fuss = e('div', 'zielfuss');
|
|
953
1226
|
fuss.appendChild(alleSkillsKnopf(pfad, teile));
|
|
1227
|
+
verzMcpZiele.forEach(function (mz) {
|
|
1228
|
+
var mcpKnopfAlle = mcpHinzufuegenKnopf(mz);
|
|
1229
|
+
if (mcpKnopfAlle) fuss.appendChild(mcpKnopfAlle);
|
|
1230
|
+
});
|
|
954
1231
|
teil.appendChild(fuss);
|
|
955
1232
|
|
|
956
1233
|
return teil;
|
|
@@ -996,10 +1273,10 @@
|
|
|
996
1273
|
warnspan.setAttribute('aria-label', 'braucht Aufmerksamkeit');
|
|
997
1274
|
kopf.appendChild(warnspan);
|
|
998
1275
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
kopf.appendChild(e('span', 'zahl', gesamtZahl + (gesamtZahl === 1 ? '
|
|
1276
|
+
// Skills statt Einträge (E71): eine Summe über die Harness-Abschnitte zählte jeden Skill so oft,
|
|
1277
|
+
// wie ihn Harnesses am selben Verzeichnis führen — der Körper bündelt sie zu einer Zeile.
|
|
1278
|
+
var gesamtZahl = buendleEigene(teile).reihenfolge.length;
|
|
1279
|
+
kopf.appendChild(e('span', 'zahl', gesamtZahl + (gesamtZahl === 1 ? ' Skill' : ' Skills')));
|
|
1003
1280
|
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1004
1281
|
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
1005
1282
|
pfeil.setAttribute('class', 'akkordeonpfeil');
|
|
@@ -1048,7 +1325,10 @@
|
|
|
1048
1325
|
// gehört zum gewählten Harness, nicht zur Karte: er steht rechts in der Chip-Leiste, relativ
|
|
1049
1326
|
// zum Verzeichnis, mit der Inaktiv-Marke daneben.
|
|
1050
1327
|
if (gewaehlt) {
|
|
1051
|
-
|
|
1328
|
+
// Nur die Anzeige: liegt der gelesene Ordner unterhalb des Verzeichnisses, steht er
|
|
1329
|
+
// relativ, sonst gekürzt. Beide Trenner, sonst greift das unter Windows nie (E68).
|
|
1330
|
+
var unterhalb = gewaehlt.verzeichnis.indexOf(pfad + '/') === 0
|
|
1331
|
+
|| gewaehlt.verzeichnis.indexOf(pfad + '\\') === 0;
|
|
1052
1332
|
chipleiste.appendChild(e('span', 'kartenordner mono',
|
|
1053
1333
|
unterhalb ? gewaehlt.verzeichnis.slice(pfad.length + 1) : pfadKurz(gewaehlt.verzeichnis)));
|
|
1054
1334
|
if (!harnessAktiv(aktivChip)) chipleiste.appendChild(e('span', 'ruhend', 'Harness inaktiv'));
|
|
@@ -1146,10 +1426,24 @@
|
|
|
1146
1426
|
// entfernenKontext.eintraege: [{harness, ort}], immer eines bei frageEntfernen (Einzel-Ziel),
|
|
1147
1427
|
// mehrere bei frageEntfernenAlle (E53 Pkt. 2, „Alle"-Zeile über mehrere Harnesses).
|
|
1148
1428
|
var entfernenKontext = null;
|
|
1149
|
-
|
|
1150
|
-
|
|
1429
|
+
/** `bundleName` gesetzt heißt: der Link gehört zu einem Bundle, das Entfernen zerbricht es (E69). */
|
|
1430
|
+
function frageEntfernen(name, ziel, bundleName) {
|
|
1431
|
+
entfernenKontext = { art: 'link', name: name, eintraege: [{ harness: ziel.harness, ort: ziel.ort }] };
|
|
1151
1432
|
$('dx-titel').textContent = 'Link auf ' + name + ' entfernen?';
|
|
1152
|
-
|
|
1433
|
+
var text = 'Der Link in ' + zielTitel({ ziel: ziel }) + ' verschwindet. Der Checkout bleibt liegen, bis kein Link mehr darauf zeigt.';
|
|
1434
|
+
if (bundleName) {
|
|
1435
|
+
text += ' Damit zerbricht das Bundle „' + bundleName + '“ hier: seine übrigen Links bleiben liegen, aber nicht mehr als Bundle.';
|
|
1436
|
+
}
|
|
1437
|
+
$('dx-text').textContent = text;
|
|
1438
|
+
zeige($('d-entfernen'));
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
/** Entfernen des ganzen Bundles an einem Ziel (E69): alle seine Links auf einmal. */
|
|
1442
|
+
function frageBundleEntfernen(b, ziel) {
|
|
1443
|
+
entfernenKontext = { art: 'bundle', bundle: b.name, ziel: ziel };
|
|
1444
|
+
$('dx-titel').textContent = 'Bundle ' + b.name + ' entfernen?';
|
|
1445
|
+
$('dx-text').textContent = zahlwort(b.skills.length, 'Link', 'Links') + ' aus ' + zielTitel({ ziel: ziel })
|
|
1446
|
+
+ ' verschwinden: ' + b.skills.join(', ') + '. Die Checkouts bleiben liegen, bis kein Link mehr darauf zeigt.';
|
|
1153
1447
|
zeige($('d-entfernen'));
|
|
1154
1448
|
}
|
|
1155
1449
|
/** Entfernen einer „Alle"-Zeile (E53 Pkt. 2): entfernbareZiele sind die Ziele (voller z.ziel), in
|
|
@@ -1157,6 +1451,7 @@
|
|
|
1157
1451
|
und liegen bleibt. Der Dialog nennt beides. */
|
|
1158
1452
|
function frageEntfernenAlle(name, pfad, entfernbareZiele, nichtEntfernbareHarnesse) {
|
|
1159
1453
|
entfernenKontext = {
|
|
1454
|
+
art: 'link',
|
|
1160
1455
|
name: name,
|
|
1161
1456
|
eintraege: entfernbareZiele.map(function (z) { return { harness: z.ziel.harness, ort: z.ziel.ort }; }),
|
|
1162
1457
|
};
|
|
@@ -1173,6 +1468,19 @@
|
|
|
1173
1468
|
nachDialog($('d-entfernen'), async function (wert) {
|
|
1174
1469
|
if (wert !== 'ja' || entfernenKontext === null) return;
|
|
1175
1470
|
var kontext = entfernenKontext;
|
|
1471
|
+
// Bundle als Ganzes (E69): ein Aufruf, der Kern nimmt alle Links des Bundles weg.
|
|
1472
|
+
if (kontext.art === 'bundle') {
|
|
1473
|
+
var wegB = await rufe(function () {
|
|
1474
|
+
return window.hmgr.bundleEntfernen({
|
|
1475
|
+
bundle: kontext.bundle,
|
|
1476
|
+
harness: kontext.ziel.harness,
|
|
1477
|
+
ort: kontext.ziel.ort,
|
|
1478
|
+
});
|
|
1479
|
+
});
|
|
1480
|
+
await ladeAlles();
|
|
1481
|
+
melde('Bundle ' + kontext.bundle + ' entfernt, ' + zahlwort(wegB.entfernt.length, 'Link', 'Links') + ' weniger.');
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1176
1484
|
// Ein Fehlschlag in einem Harness hält die übrigen nicht auf: rufe hat ihn schon gemeldet, die
|
|
1177
1485
|
// Schleife läuft weiter, und die Liste wird danach in jedem Fall neu geladen. Sonst stünde nach
|
|
1178
1486
|
// einem Fehler die halb erledigte Arbeit unsichtbar in einer veralteten Liste.
|
|
@@ -1215,7 +1523,7 @@
|
|
|
1215
1523
|
function frageLoeschen(name, ziel, verzeichnis) {
|
|
1216
1524
|
loeschenKontext = { name: name, ziel: ziel };
|
|
1217
1525
|
$('dl-titel').textContent = '„' + name + '“ löschen?';
|
|
1218
|
-
$('dl-text').textContent = 'hmgr löscht ' + pfadKurz(verzeichnis) +
|
|
1526
|
+
$('dl-text').textContent = 'hmgr löscht ' + pfadKurz(verzeichnis) + TRENNER + name
|
|
1219
1527
|
+ ' unwiderruflich von der Platte, ein Ordner mit allem Inhalt oder ein Link. Das lässt sich nicht rückgängig machen.';
|
|
1220
1528
|
zeige($('d-loeschen'));
|
|
1221
1529
|
}
|
|
@@ -1400,7 +1708,7 @@
|
|
|
1400
1708
|
chip.addEventListener('click', function () { setzeQuellenfilter(s.quelleSchluessel); });
|
|
1401
1709
|
|
|
1402
1710
|
// Quellen-Chip-Paar (E60): links filtert der Name, rechts öffnet das Pfeilsymbol die Quelle
|
|
1403
|
-
// (Repo-Seite im Browser, lokale Arbeitskopie im
|
|
1711
|
+
// (Repo-Seite im Browser, lokale Arbeitskopie im Dateimanager). Kein <a> im <button>, deshalb
|
|
1404
1712
|
// stehen sie nebeneinander in einer gemeinsamen Hülle.
|
|
1405
1713
|
var quelle = QUELLEN.filter(function (q) { return q.text === s.quelleText; })[0];
|
|
1406
1714
|
if (quelle) {
|
|
@@ -1411,7 +1719,7 @@
|
|
|
1411
1719
|
if (lokal) oeffnen.type = 'button'; else oeffnen.href = '#';
|
|
1412
1720
|
oeffnen.className = 'chipoeffnen';
|
|
1413
1721
|
oeffnen.title = quelle.text;
|
|
1414
|
-
oeffnen.setAttribute('aria-label', s.quelleName + ' ' + (lokal ? 'im
|
|
1722
|
+
oeffnen.setAttribute('aria-label', s.quelleName + ' ' + (lokal ? 'im Dateimanager öffnen' : 'im Browser öffnen'));
|
|
1415
1723
|
oeffnen.appendChild(externSvg());
|
|
1416
1724
|
oeffnen.addEventListener('click', function (ev) {
|
|
1417
1725
|
ev.preventDefault();
|
|
@@ -1455,6 +1763,24 @@
|
|
|
1455
1763
|
return box;
|
|
1456
1764
|
}
|
|
1457
1765
|
|
|
1766
|
+
/** Beschreibungs-Zelle der Ziel-Tabellen: die Beschreibung (E39) und darunter die immer
|
|
1767
|
+
sichtbaren Frontmatter-Marken (E55), derselbe Helfer wie im Katalog. Ohne Skill (Link ohne
|
|
1768
|
+
Quelle) bleibt nur die Fehlt-Zeile. */
|
|
1769
|
+
function wasZelle(skill) {
|
|
1770
|
+
var feld = e('span', 'wasfeld');
|
|
1771
|
+
var beschreibung = skill ? skill.beschreibung : undefined;
|
|
1772
|
+
if (beschreibung) {
|
|
1773
|
+
var wasEl = e('span', 'was', beschreibung);
|
|
1774
|
+
if (beschreibung.length > 88) wasEl.title = beschreibung;
|
|
1775
|
+
feld.appendChild(wasEl);
|
|
1776
|
+
} else {
|
|
1777
|
+
feld.appendChild(e('span', 'was fehlt', 'keine Beschreibung in der SKILL.md'));
|
|
1778
|
+
}
|
|
1779
|
+
var marken = skill ? skillMarkenBox(skill) : null;
|
|
1780
|
+
if (marken) feld.appendChild(marken);
|
|
1781
|
+
return feld;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1458
1784
|
/** Zeilen des Aufklappbereichs einer Skill-Zeile aus dem Frontmatter (E55), nur wenn vorhanden. */
|
|
1459
1785
|
function skillDetails(skill) {
|
|
1460
1786
|
var zeilen = [];
|
|
@@ -1465,6 +1791,14 @@
|
|
|
1465
1791
|
return zeilen;
|
|
1466
1792
|
}
|
|
1467
1793
|
|
|
1794
|
+
/** ISO-Zeitpunkt als deutsches Datum (E70). Ungültiges oder fehlendes Datum ergibt null. */
|
|
1795
|
+
function datumDeutsch(iso) {
|
|
1796
|
+
if (!iso) return null;
|
|
1797
|
+
var d = new Date(iso);
|
|
1798
|
+
if (isNaN(d.getTime())) return null;
|
|
1799
|
+
return d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1468
1802
|
function baueSkills() {
|
|
1469
1803
|
var liste = $('katalog');
|
|
1470
1804
|
liste.textContent = '';
|
|
@@ -1488,12 +1822,17 @@
|
|
|
1488
1822
|
// title. Fehlt die Quelle in der Liste (Katalog und Quellen kämen auseinander), bleibt es
|
|
1489
1823
|
// beim reinen Text — angeschrieben ist die Zeile so oder so.
|
|
1490
1824
|
var herkunft = e('p', 'herkunft mono');
|
|
1825
|
+
// Das Änderungsdatum steht hinter dem Ref (E70, F70=O1): der jüngste Commit, der den
|
|
1826
|
+
// Skill-Ordner berührt. Nicht der Pull-Zeitpunkt — der steht als „Zuletzt geprüft" in der
|
|
1827
|
+
// Update-Ansicht und wäre für alle Skills einer Quelle derselbe.
|
|
1828
|
+
var geaendert = datumDeutsch(skill.geaendert);
|
|
1829
|
+
var nachRef = ' · Ref ' + skill.ref + (geaendert ? ' · geändert ' + geaendert : '');
|
|
1491
1830
|
if (herkunftQuelle) {
|
|
1492
1831
|
herkunft.appendChild(quelleLinkElement(herkunftQuelle));
|
|
1493
|
-
herkunft.appendChild(e('b', null,
|
|
1832
|
+
herkunft.appendChild(e('b', null, nachRef));
|
|
1494
1833
|
} else {
|
|
1495
1834
|
herkunft.title = skill.quelleText;
|
|
1496
|
-
herkunft.appendChild(e('b', null, skill.quelleName +
|
|
1835
|
+
herkunft.appendChild(e('b', null, skill.quelleName + nachRef));
|
|
1497
1836
|
}
|
|
1498
1837
|
links.appendChild(herkunft);
|
|
1499
1838
|
|
|
@@ -1629,7 +1968,7 @@
|
|
|
1629
1968
|
li.appendChild(e('span', r.ok ? 'ok' : 'aus', r.ok ? 'Link' : 'Fehler'));
|
|
1630
1969
|
var s = e('span');
|
|
1631
1970
|
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) +
|
|
1971
|
+
s.appendChild(e('span', 'mono', r.ok ? pfadKurz(r.ziel.verzeichnis) + TRENNER + skill.name : r.fehler));
|
|
1633
1972
|
li.appendChild(s);
|
|
1634
1973
|
ul.appendChild(li);
|
|
1635
1974
|
});
|
|
@@ -1642,7 +1981,8 @@
|
|
|
1642
1981
|
Skill nur aus, wenn er in ALLEN beteiligten Harnesses hängt, und verlinkt je Harness, wo er fehlt. */
|
|
1643
1982
|
var skillsKontext = null;
|
|
1644
1983
|
function dskBilanzAktualisieren() {
|
|
1645
|
-
var n = $('dsk-liste').querySelectorAll('input:checked').length
|
|
1984
|
+
var n = $('dsk-liste').querySelectorAll('input:checked').length
|
|
1985
|
+
+ $('dsk-bundles').querySelectorAll('input:checked').length;
|
|
1646
1986
|
$('dsk-bilanz').textContent = zahlwort(n, 'ausgewählt', 'ausgewählt');
|
|
1647
1987
|
}
|
|
1648
1988
|
function filtereDskListe() {
|
|
@@ -1651,6 +1991,60 @@
|
|
|
1651
1991
|
var skill = SKILLS[+label.dataset.index];
|
|
1652
1992
|
label.hidden = !!suche && skill.name.toLowerCase().indexOf(suche) === -1;
|
|
1653
1993
|
});
|
|
1994
|
+
// Die Suche gilt auch für Bundles (E69): sonst bliebe die Sektion bei jeder Eingabe voll stehen.
|
|
1995
|
+
document.querySelectorAll('#dsk-bundles .wahlzeile').forEach(function (label) {
|
|
1996
|
+
var bundle = BUNDLES[+label.dataset.index];
|
|
1997
|
+
label.hidden = !!suche && bundle.name.toLowerCase().indexOf(suche) === -1;
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
/**
|
|
2002
|
+
* Bundle-Sektion des Dialogs (E69, F68): je Bundle eine Wahlzeile mit Beschreibung, Zahl seiner
|
|
2003
|
+
* Mitglieder und Herkunft. `installiertIn(bundle)` sagt, in welchen der angesteuerten Ziele es
|
|
2004
|
+
* schon hängt — hängt es überall, ist die Zeile ausgegraut, wie bei einem Skill.
|
|
2005
|
+
*/
|
|
2006
|
+
function fuelleBundleSektion(installiertIn, zieleZahl) {
|
|
2007
|
+
var box = $('dsk-bundles');
|
|
2008
|
+
box.textContent = '';
|
|
2009
|
+
BUNDLES.forEach(function (bundle, i) {
|
|
2010
|
+
var schon = installiertIn(bundle);
|
|
2011
|
+
var ueberall = schon >= zieleZahl;
|
|
2012
|
+
var label = e('label', 'wahlzeile quellfarbe');
|
|
2013
|
+
label.dataset.index = i;
|
|
2014
|
+
var idx = quellenIndex(bundle.quelleText);
|
|
2015
|
+
label.style.borderLeftColor = 'var(--q' + (idx + 1) + ')';
|
|
2016
|
+
var input = document.createElement('input');
|
|
2017
|
+
input.type = 'checkbox';
|
|
2018
|
+
input.value = String(i);
|
|
2019
|
+
input.disabled = ueberall;
|
|
2020
|
+
input.addEventListener('change', dskBilanzAktualisieren);
|
|
2021
|
+
label.appendChild(input);
|
|
2022
|
+
var txt = e('span');
|
|
2023
|
+
txt.appendChild(e('b', null, bundle.name));
|
|
2024
|
+
txt.appendChild(e('span', 'notiz', bundle.beschreibung));
|
|
2025
|
+
var herkunft = e('span', 'mono', zahlwort(bundle.mitglieder, 'Skill', 'Skills')
|
|
2026
|
+
+ ' aus ' + zahlwort(bundle.quellen.length, 'Quelle', 'Quellen') + ' · ' + bundle.quelleName);
|
|
2027
|
+
herkunft.title = bundle.quellen.join('\n');
|
|
2028
|
+
txt.appendChild(herkunft);
|
|
2029
|
+
if (ueberall) txt.appendChild(e('span', 'notiz', zieleZahl > 1 ? 'Hängt schon überall.' : 'Hängt hier schon.'));
|
|
2030
|
+
else if (schon > 0) txt.appendChild(e('span', 'notiz', 'Hängt schon in ' + schon + ' von ' + zieleZahl + ' Harnesses.'));
|
|
2031
|
+
label.appendChild(txt);
|
|
2032
|
+
box.appendChild(label);
|
|
2033
|
+
});
|
|
2034
|
+
$('dsk-bundlebox').hidden = BUNDLES.length === 0 && BUNDLES_KAPUTT.length === 0;
|
|
2035
|
+
// Eine Definition, die sich nicht lesen ließ, wird genannt statt verschluckt (E69).
|
|
2036
|
+
var fehlerZeile = $('dsk-bundlefehler');
|
|
2037
|
+
fehlerZeile.hidden = BUNDLES_KAPUTT.length === 0;
|
|
2038
|
+
fehlerZeile.textContent = BUNDLES_KAPUTT.map(function (k) {
|
|
2039
|
+
return k.quelleName + ': ' + k.datei + ' — ' + k.fehler;
|
|
2040
|
+
}).join('\n');
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
/** Wie oft ein Bundle in den angesteuerten Zielen schon installiert ist (Name plus Quelle, E69). */
|
|
2044
|
+
function bundleHaengtIn(teile, bundle) {
|
|
2045
|
+
return teile.filter(function (z) {
|
|
2046
|
+
return (z.bundles || []).some(function (b) { return b.name === bundle.name && b.quelleText === bundle.quelleText; });
|
|
2047
|
+
}).length;
|
|
1654
2048
|
}
|
|
1655
2049
|
$('dsk-suche').addEventListener('input', filtereDskListe);
|
|
1656
2050
|
|
|
@@ -1685,6 +2079,7 @@
|
|
|
1685
2079
|
label.appendChild(txt);
|
|
1686
2080
|
box.appendChild(label);
|
|
1687
2081
|
});
|
|
2082
|
+
fuelleBundleSektion(function (bundle) { return bundleHaengtIn([z], bundle); }, 1);
|
|
1688
2083
|
dskBilanzAktualisieren();
|
|
1689
2084
|
zeige($('d-skills'));
|
|
1690
2085
|
$('dsk-suche').focus();
|
|
@@ -1729,6 +2124,7 @@
|
|
|
1729
2124
|
label.appendChild(txt);
|
|
1730
2125
|
box.appendChild(label);
|
|
1731
2126
|
});
|
|
2127
|
+
fuelleBundleSektion(function (bundle) { return bundleHaengtIn(teile, bundle); }, teile.length);
|
|
1732
2128
|
dskBilanzAktualisieren();
|
|
1733
2129
|
zeige($('d-skills'));
|
|
1734
2130
|
$('dsk-suche').focus();
|
|
@@ -1736,7 +2132,8 @@
|
|
|
1736
2132
|
|
|
1737
2133
|
$('d-skills').addEventListener('submit', function (ev) {
|
|
1738
2134
|
if (ev.submitter && ev.submitter.value === 'los') {
|
|
1739
|
-
var gewaehlt = Array.from($('dsk-liste').querySelectorAll('input:checked'))
|
|
2135
|
+
var gewaehlt = Array.from($('dsk-liste').querySelectorAll('input:checked'))
|
|
2136
|
+
.concat(Array.from($('dsk-bundles').querySelectorAll('input:checked')));
|
|
1740
2137
|
if (gewaehlt.length === 0) ev.preventDefault();
|
|
1741
2138
|
}
|
|
1742
2139
|
});
|
|
@@ -1745,6 +2142,7 @@
|
|
|
1745
2142
|
if (wert !== 'los' || skillsKontext === null) return;
|
|
1746
2143
|
var kontext = skillsKontext;
|
|
1747
2144
|
var indizes = Array.from($('dsk-liste').querySelectorAll('input:checked')).map(function (i) { return +i.value; });
|
|
2145
|
+
var bundleIndizes = Array.from($('dsk-bundles').querySelectorAll('input:checked')).map(function (i) { return +i.value; });
|
|
1748
2146
|
|
|
1749
2147
|
if (kontext.modus === 'einzeln') {
|
|
1750
2148
|
var z = kontext.z;
|
|
@@ -1754,6 +2152,26 @@
|
|
|
1754
2152
|
// Der zweite gehört daher als Fehler in die Bilanz, sonst meldet der Dialog Links, die es nicht gibt.
|
|
1755
2153
|
var vergeben = [];
|
|
1756
2154
|
var hinweiseZiel = [];
|
|
2155
|
+
// Bundles zuerst (E69): sie wirken als Ganzes und bringen ihre Mitglieder mit, ein danach
|
|
2156
|
+
// einzeln gewählter Skill gleichen Namens würde den eben gelegten Link stumm ersetzen.
|
|
2157
|
+
for (var bi = 0; bi < bundleIndizes.length; bi++) {
|
|
2158
|
+
var bundle = BUNDLES[bundleIndizes[bi]];
|
|
2159
|
+
try {
|
|
2160
|
+
var erg = await rufe(function () {
|
|
2161
|
+
return window.hmgr.bundleInstallieren({
|
|
2162
|
+
bundle: bundle.name,
|
|
2163
|
+
harness: z.ziel.harness,
|
|
2164
|
+
ort: z.ziel.ort,
|
|
2165
|
+
quelle: bundle.quelleText,
|
|
2166
|
+
});
|
|
2167
|
+
});
|
|
2168
|
+
hinweiseZiel.push(erg.hinweis);
|
|
2169
|
+
erg.gelegt.concat(erg.uebernommen).forEach(function (n) { vergeben.push(n); });
|
|
2170
|
+
ergebnisZeilen.push({ ok: true, bundle: bundle, erg: erg });
|
|
2171
|
+
} catch (fehler) {
|
|
2172
|
+
ergebnisZeilen.push({ ok: false, bundle: bundle, fehler: fehler.message });
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
1757
2175
|
for (var n = 0; n < indizes.length; n++) {
|
|
1758
2176
|
var skill = SKILLS[indizes[n]];
|
|
1759
2177
|
if (vergeben.indexOf(skill.name) > -1) {
|
|
@@ -1779,18 +2197,30 @@
|
|
|
1779
2197
|
// Arbeitsverzeichnis ist das die gebündelte Verzeichnis-Karte, nicht das Ziel allein (E49).
|
|
1780
2198
|
ZIEL_OFFEN[kartenKey(z.ziel)] = true;
|
|
1781
2199
|
await ladeAlles();
|
|
1782
|
-
|
|
2200
|
+
// Ein Bundle zählt als eines, egal wie viele Links es mitbringt: die Zeile darunter nennt sie.
|
|
2201
|
+
var erfolge = ergebnisZeilen.filter(function (r) { return r.ok && !r.bundle; }).length;
|
|
2202
|
+
var erfolgeBundle = ergebnisZeilen.filter(function (r) { return r.ok && r.bundle; }).length;
|
|
1783
2203
|
$('de-titel').textContent = 'Verlinkt';
|
|
1784
|
-
$('de-text').textContent = zahlwort(
|
|
2204
|
+
$('de-text').textContent = (erfolgeBundle > 0 ? zahlwort(erfolgeBundle, 'Bundle', 'Bundles') + ' und ' : '')
|
|
2205
|
+
+ zahlwort(erfolge, 'Skill hängt', 'Skills hängen') + ' jetzt in ' + zielTitel(z) + '.';
|
|
1785
2206
|
setzeErfolgHinweise(hinweiseZiel);
|
|
1786
2207
|
var ul = $('de-liste');
|
|
1787
2208
|
ul.textContent = '';
|
|
1788
2209
|
ergebnisZeilen.forEach(function (r) {
|
|
1789
2210
|
var li = e('li');
|
|
1790
|
-
li.appendChild(e('span', r.ok ? 'ok' : 'aus', r.ok ? 'Link' : 'Fehler'));
|
|
2211
|
+
li.appendChild(e('span', r.ok ? 'ok' : 'aus', r.ok ? (r.bundle ? 'Bundle' : 'Link') : 'Fehler'));
|
|
1791
2212
|
var s = e('span');
|
|
1792
|
-
s.appendChild(e('b', null, r.skill.name));
|
|
2213
|
+
s.appendChild(e('b', null, r.bundle ? r.bundle.name : r.skill.name));
|
|
1793
2214
|
if (!r.ok) s.appendChild(e('span', 'mono', r.fehler));
|
|
2215
|
+
else if (r.bundle) {
|
|
2216
|
+
var teileText = r.erg.gelegt.concat(r.erg.uebernommen).join(', ');
|
|
2217
|
+
if (r.erg.neueQuellen.length > 0) {
|
|
2218
|
+
// Neue Quellen (F66) gehören genannt: hmgr hat sie beim Installieren selbst angelegt.
|
|
2219
|
+
teileText += ' · neue ' + zahlwort(r.erg.neueQuellen.length, 'Quelle', 'Quellen') + ': '
|
|
2220
|
+
+ r.erg.neueQuellen.join(', ');
|
|
2221
|
+
}
|
|
2222
|
+
s.appendChild(e('span', 'mono', teileText));
|
|
2223
|
+
}
|
|
1794
2224
|
li.appendChild(s);
|
|
1795
2225
|
ul.appendChild(li);
|
|
1796
2226
|
});
|
|
@@ -1809,6 +2239,32 @@
|
|
|
1809
2239
|
// den eben gelegten Link sonst stumm.
|
|
1810
2240
|
var vergebenJeHarness = {};
|
|
1811
2241
|
var hinweiseAlle = [];
|
|
2242
|
+
// Bundles zuerst, je Harness, in dem sie noch nicht hängen (E69): ein Bundle gehört zu genau
|
|
2243
|
+
// einem Ziel, unter „Alle" also einmal je beteiligtem Harness.
|
|
2244
|
+
for (var bj = 0; bj < bundleIndizes.length; bj++) {
|
|
2245
|
+
var bundleAlle = BUNDLES[bundleIndizes[bj]];
|
|
2246
|
+
for (var bt = 0; bt < teile.length; bt++) {
|
|
2247
|
+
var zb = teile[bt];
|
|
2248
|
+
if (bundleHaengtIn([zb], bundleAlle) > 0) continue;
|
|
2249
|
+
try {
|
|
2250
|
+
var ergAlle = await rufe(function () {
|
|
2251
|
+
return window.hmgr.bundleInstallieren({
|
|
2252
|
+
bundle: bundleAlle.name,
|
|
2253
|
+
harness: zb.ziel.harness,
|
|
2254
|
+
ort: zb.ziel.ort,
|
|
2255
|
+
quelle: bundleAlle.quelleText,
|
|
2256
|
+
});
|
|
2257
|
+
});
|
|
2258
|
+
hinweiseAlle.push(ergAlle.hinweis);
|
|
2259
|
+
ergAlle.gelegt.concat(ergAlle.uebernommen).forEach(function (n) {
|
|
2260
|
+
vergebenJeHarness[zb.ziel.harness + '\u0000' + n] = true;
|
|
2261
|
+
});
|
|
2262
|
+
zeilenErgebnis.push({ ok: true, bundle: bundleAlle, harness: zb.ziel.harness, erg: ergAlle });
|
|
2263
|
+
} catch (fehler) {
|
|
2264
|
+
zeilenErgebnis.push({ ok: false, bundle: bundleAlle, harness: zb.ziel.harness, fehler: fehler.message });
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
1812
2268
|
for (var m = 0; m < indizes.length; m++) {
|
|
1813
2269
|
var sk = SKILLS[indizes[m]];
|
|
1814
2270
|
for (var t = 0; t < teile.length; t++) {
|
|
@@ -1891,15 +2347,34 @@
|
|
|
1891
2347
|
if (c.aktualisierung.ergebnis === 'aktualisiert') quellen[quelleKurz(c.checkout.quelle)] = true;
|
|
1892
2348
|
});
|
|
1893
2349
|
var neue = Object.keys(quellen).length;
|
|
2350
|
+
// Skill-Ebene (E70): Namen über alle Checkouts, doppelte zählen einmal. Damit sagt der
|
|
2351
|
+
// Standknopf „5 Skills aktualisiert" statt nur „1 Quelle mit Neuem".
|
|
2352
|
+
var geaenderteSkills = {};
|
|
2353
|
+
ergebnis.checkouts.forEach(function (c) {
|
|
2354
|
+
(c.geaenderteSkills || []).forEach(function (name) { geaenderteSkills[name] = true; });
|
|
2355
|
+
});
|
|
2356
|
+
var skillZahl = Object.keys(geaenderteSkills).length;
|
|
2357
|
+
// Ein nachgezogenes Bundle ist Neues wie ein neuer Commit (E69, F64=O1): sonst bliebe der
|
|
2358
|
+
// Standknopf still, obwohl sich am Ziel gerade Links geändert haben.
|
|
2359
|
+
var bundlesNeu = (ergebnis.bundles || []).filter(function (b) { return b.ergebnis === 'aktualisiert'; }).length;
|
|
1894
2360
|
var fehlgeschlagen = ergebnis.checkouts.filter(function (c) { return c.aktualisierung.ergebnis === 'fehlgeschlagen'; }).length;
|
|
1895
2361
|
var gesamt = ergebnis.checkouts.length;
|
|
1896
2362
|
standFehlerZustand = fehlgeschlagen === 0 ? 'keine' : fehlgeschlagen === gesamt ? 'alle' : 'gemischt';
|
|
1897
2363
|
// Von Hand läuft es in der Update-Ansicht, der Bericht ist schon sichtbar: nichts anzukündigen.
|
|
1898
|
-
if (neue === 0 || !automatisch || !$('ansicht-update').hidden) return standRuhig();
|
|
2364
|
+
if ((neue === 0 && bundlesNeu === 0) || !automatisch || !$('ansicht-update').hidden) return standRuhig();
|
|
1899
2365
|
var p = $('standtext');
|
|
1900
2366
|
p.textContent = '';
|
|
1901
|
-
|
|
1902
|
-
|
|
2367
|
+
// Ist die Skill-Zahl bekannt, sagt sie mehr als die Zahl der Quellen (E70). Sonst bleibt es
|
|
2368
|
+
// beim alten Signal: eine Quelle kann Neues haben, ohne dass ein Skill davon betroffen ist.
|
|
2369
|
+
if (skillZahl > 0) {
|
|
2370
|
+
p.appendChild(e('b', null, zahlwort(skillZahl, 'Skill', 'Skills')));
|
|
2371
|
+
p.appendChild(document.createTextNode(' aktualisiert'));
|
|
2372
|
+
} else {
|
|
2373
|
+
p.appendChild(e('b', null, neue > 0
|
|
2374
|
+
? zahlwort(neue, 'Quelle', 'Quellen')
|
|
2375
|
+
: zahlwort(bundlesNeu, 'Bundle', 'Bundles')));
|
|
2376
|
+
p.appendChild(document.createTextNode(' mit Neuem'));
|
|
2377
|
+
}
|
|
1903
2378
|
$('standknopf').classList.add('neu');
|
|
1904
2379
|
$('standknopf').textContent = 'Ansehen';
|
|
1905
2380
|
}
|
|
@@ -1939,6 +2414,35 @@
|
|
|
1939
2414
|
});
|
|
1940
2415
|
window.hmgr.onAktualisierenAnstossen(function () { aktualisierenLauf(true); });
|
|
1941
2416
|
|
|
2417
|
+
// Stilles Neuladen bei Änderungen außerhalb der GUI (E62, Fokus-Wechsel und Datei-Watcher): nur
|
|
2418
|
+
// ladeAlles, keine Meldung, kein Spinner-Zwang. Nachrutsch-sicher: höchstens ein Durchlauf läuft,
|
|
2419
|
+
// höchstens ein weiterer ist vorgemerkt, ein laufender Durchlauf wird nie von einem zweiten
|
|
2420
|
+
// überholt. Fehler landen wie sonst über rufe() in der Meldungsleiste, kein stiller Absturz.
|
|
2421
|
+
var stillerReloadLaeuft = false;
|
|
2422
|
+
var stillerReloadVorgemerkt = false;
|
|
2423
|
+
async function stillerReload() {
|
|
2424
|
+
// Läuft gerade ein Aktualisieren-Lauf, wird nicht parallel geladen: er endet ohnehin mit
|
|
2425
|
+
// ladeAlles und nimmt die Änderung mit. Sonst bauten zwei Läufe gleichzeitig die Ansicht auf.
|
|
2426
|
+
if ($('updatestart').disabled) return;
|
|
2427
|
+
if (stillerReloadLaeuft) {
|
|
2428
|
+
stillerReloadVorgemerkt = true;
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
stillerReloadLaeuft = true;
|
|
2432
|
+
try {
|
|
2433
|
+
await ladeAlles();
|
|
2434
|
+
} catch (fehler) {
|
|
2435
|
+
// rufe() hat den Fehler schon gemeldet, hier bleibt nur: den Durchlauf nicht wiederholen.
|
|
2436
|
+
} finally {
|
|
2437
|
+
stillerReloadLaeuft = false;
|
|
2438
|
+
if (stillerReloadVorgemerkt) {
|
|
2439
|
+
stillerReloadVorgemerkt = false;
|
|
2440
|
+
void stillerReload();
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
window.hmgr.onNeuLadenAnstossen(function () { void stillerReload(); });
|
|
2445
|
+
|
|
1942
2446
|
function baueBericht(ergebnis) {
|
|
1943
2447
|
var box = $('bericht');
|
|
1944
2448
|
box.textContent = '';
|
|
@@ -1957,7 +2461,11 @@
|
|
|
1957
2461
|
: a.ergebnis === 'fehlgeschlagen' ? 'fehlgeschlagen (' + a.fehler + ')'
|
|
1958
2462
|
: 'übersprungen (lokale Änderung)';
|
|
1959
2463
|
li.appendChild(e('span', 'marke-' + art, text));
|
|
1960
|
-
|
|
2464
|
+
// Skill-Ebene an der Checkout-Zeile (E70): sonst steht hier nur, dass die Quelle neue
|
|
2465
|
+
// Commits hat, und wer sein eigenes Push sucht, findet es nicht wieder.
|
|
2466
|
+
var skills = c.geaenderteSkills || [];
|
|
2467
|
+
li.appendChild(quelleSpan('pfad', c.checkout.quelle, ' @ ' + c.checkout.ref
|
|
2468
|
+
+ (skills.length > 0 ? ' · ' + zahlwort(skills.length, 'Skill', 'Skills') + ': ' + skills.join(', ') : '')));
|
|
1961
2469
|
cul.appendChild(li);
|
|
1962
2470
|
});
|
|
1963
2471
|
checkoutBlock.appendChild(cul);
|
|
@@ -1977,6 +2485,33 @@
|
|
|
1977
2485
|
box.appendChild(pluginBlock);
|
|
1978
2486
|
}
|
|
1979
2487
|
|
|
2488
|
+
// Bundles im selben Lauf (E69, F64=O1): eine geänderte Definition wird beim Aktualisieren
|
|
2489
|
+
// nachgezogen, eine Zeile je Bundle im Stil der Checkout-Zeilen darüber.
|
|
2490
|
+
var bundleEintraege = ergebnis.bundles || [];
|
|
2491
|
+
if (bundleEintraege.length > 0) {
|
|
2492
|
+
var bundleBlock = e('div', 'block');
|
|
2493
|
+
bundleBlock.appendChild(e('span', 'etikett', 'Bundles'));
|
|
2494
|
+
var bul = e('ul');
|
|
2495
|
+
bundleEintraege.forEach(function (b) {
|
|
2496
|
+
var li = e('li');
|
|
2497
|
+
var text = b.ergebnis === 'aktualisiert' ? 'aktualisiert'
|
|
2498
|
+
: b.ergebnis === 'unveraendert' ? 'unverändert'
|
|
2499
|
+
: 'fehlgeschlagen (' + (b.fehler || '').split('\n')[0] + ')';
|
|
2500
|
+
li.appendChild(e('span', b.ergebnis === 'fehlgeschlagen' ? 'marke-aus' : 'marke-ok', text));
|
|
2501
|
+
// Was sich geändert hat, steht hinter dem Namen; ein Fehlschlag sagt, dass nichts geschah.
|
|
2502
|
+
var teile = [];
|
|
2503
|
+
if (b.gelegt.length > 0) teile.push('+ ' + b.gelegt.join(', '));
|
|
2504
|
+
if (b.entfernt.length > 0) teile.push('− ' + b.entfernt.join(', '));
|
|
2505
|
+
if (b.neueQuellen.length > 0) teile.push('neue Quellen: ' + b.neueQuellen.length);
|
|
2506
|
+
if (b.ergebnis === 'fehlgeschlagen') teile.push('bleibt auf dem alten Stand');
|
|
2507
|
+
li.appendChild(e('span', 'pfad', b.bundle + ' · ' + harnessName(b.ziel.harness) + ' · ' + ortName(b.ziel.ort)
|
|
2508
|
+
+ (teile.length > 0 ? ' · ' + teile.join(' · ') : '')));
|
|
2509
|
+
bul.appendChild(li);
|
|
2510
|
+
});
|
|
2511
|
+
bundleBlock.appendChild(bul);
|
|
2512
|
+
box.appendChild(bundleBlock);
|
|
2513
|
+
}
|
|
2514
|
+
|
|
1980
2515
|
if (ergebnis.aufraeumen.verwaiste.length > 0) {
|
|
1981
2516
|
var raeumBlock = e('div', 'block entfernt');
|
|
1982
2517
|
raeumBlock.appendChild(e('span', 'etikett', 'Verwaiste Checkouts'));
|
|
@@ -2030,6 +2565,391 @@
|
|
|
2030
2565
|
});
|
|
2031
2566
|
}
|
|
2032
2567
|
|
|
2568
|
+
/* ================= MCP (E61-Folgerunde, F46: reiner Katalog) ================= */
|
|
2569
|
+
/* Der Katalog aus ~/.hmgr/mcp.yaml wird hier nicht bearbeitet, hmgr liest ihn nur: kein Anlegen,
|
|
2570
|
+
kein Ändern, nur Einhängen (entwurf/gui.html). Abweichung vom Entwurf (F43): kein Erfolgsdialog
|
|
2571
|
+
und kein Wirkungs-Vermerk an der Orts-Zeile, Erfolg läuft über die bestehende Meldungsleiste
|
|
2572
|
+
(melde()), bei Claude Code mit dem Hinweis des Kerns zur nächsten Sitzung angehängt, bei Hermes
|
|
2573
|
+
ohne (der Kern liefert den Hinweis nur für Claude Code).
|
|
2574
|
+
F46 („die Ziele gehören nicht in die MCP, sondern die MCP in die Ziele"): wo ein Server hängt und
|
|
2575
|
+
das Aushängen zeigen seither nur noch die Ziel-Karten (mcpAbschnitt weiter unten), diese Ansicht
|
|
2576
|
+
bleibt reiner Katalog — Name, Beschreibung, Homepage, Konfigblock, F39-Warnzeile, Notizen,
|
|
2577
|
+
Einhängen. mcpOrtDatei/-Scope/-Spur bleiben hier stehen, sie tragen weiterhin den Einhängen-
|
|
2578
|
+
Dialog (baueMcpWahl) und den Aushängen-Text (frageMcpAus), beide von den Ziel-Karten geteilt. */
|
|
2579
|
+
|
|
2580
|
+
/* Wohin der Eintrag geschrieben wird, rein zur Anzeige: Claude Code führt Projekt- wie globale
|
|
2581
|
+
Server in ~/.claude.json (die geteilte .mcp.json der Projektwurzel bleibt unangetastet, E57),
|
|
2582
|
+
Hermes je Ort in seiner config.yaml. */
|
|
2583
|
+
function mcpOrtDatei(ziel) {
|
|
2584
|
+
if (ziel.harness === 'hermes')
|
|
2585
|
+
return ziel.ort.art === 'profil'
|
|
2586
|
+
? pfadSchreibweise('~/.hermes/profiles/' + ziel.ort.name + '/config.yaml')
|
|
2587
|
+
: pfadSchreibweise('~/.hermes/config.yaml');
|
|
2588
|
+
return pfadSchreibweise('~/.claude.json');
|
|
2589
|
+
}
|
|
2590
|
+
function mcpOrtScope(ziel) {
|
|
2591
|
+
if (ziel.harness !== 'claude-code') return null;
|
|
2592
|
+
return ziel.ort.art === 'arbeitsverzeichnis' ? 'Scope local' : 'Scope user';
|
|
2593
|
+
}
|
|
2594
|
+
/** Was in der Ort-Zeile hinter dem Namen steht: die Datei mit dem Scope. Anders als im Entwurf
|
|
2595
|
+
auch beim Arbeitsverzeichnis, denn der Ortsname der App trägt den Pfad schon (`ortName`); im
|
|
2596
|
+
Entwurf hieß er nur „Arbeitsverzeichnis", weshalb der Pfad dort hier stand. Jede Angabe
|
|
2597
|
+
einmal: Pfad im Namen, Datei und Scope daneben. */
|
|
2598
|
+
function mcpOrtSpur(ziel) {
|
|
2599
|
+
var scope = mcpOrtScope(ziel);
|
|
2600
|
+
return mcpOrtDatei(ziel) + (scope ? ' · ' + scope : '');
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
function mcpKonfigArt(konfig) { return 'command' in konfig ? 'stdio' : 'HTTP'; }
|
|
2604
|
+
/** Der Konfigblock im Wortlaut, so wie er in der mcp.yaml steht: die ${VAR}-Verweise darin bleiben
|
|
2605
|
+
sichtbar, nur sie sind der Grund für die Warnzeile darunter. Nie ein Klartext-Secret (F41). */
|
|
2606
|
+
function mcpKonfigWortlaut(konfig) {
|
|
2607
|
+
if ('command' in konfig) {
|
|
2608
|
+
var text = [konfig.command].concat(konfig.args || []).join(' ');
|
|
2609
|
+
Object.keys(konfig.env || {}).forEach(function (k) { text += ' · ' + k + '=' + konfig.env[k]; });
|
|
2610
|
+
return text;
|
|
2611
|
+
}
|
|
2612
|
+
var url = konfig.url;
|
|
2613
|
+
Object.keys(konfig.headers || {}).forEach(function (k) { url += ' · ' + k + ': ' + konfig.headers[k]; });
|
|
2614
|
+
return url;
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
/** Die Homepage eines Servers, dasselbe Muster wie eine Quelle (E60): der Name bleibt ruhig, das
|
|
2618
|
+
Signalorange trägt nur das Pfeilsymbol. Öffnet über denselben Kanal wie eine Quelle (E60): eine
|
|
2619
|
+
http(s)-Adresse geht in den Browser. */
|
|
2620
|
+
function mcpHeimLink(url) {
|
|
2621
|
+
var a = document.createElement('a');
|
|
2622
|
+
a.className = 'qlink';
|
|
2623
|
+
a.href = '#';
|
|
2624
|
+
a.title = url;
|
|
2625
|
+
a.setAttribute('aria-label', url + ' im Browser öffnen');
|
|
2626
|
+
a.appendChild(document.createTextNode(url));
|
|
2627
|
+
a.appendChild(externSvg());
|
|
2628
|
+
a.addEventListener('click', function (ev) {
|
|
2629
|
+
ev.preventDefault();
|
|
2630
|
+
window.hmgr.quelleOeffnen(url).catch(function () {});
|
|
2631
|
+
});
|
|
2632
|
+
return a;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
/** F39: die Prüf-Funktion ist dieselbe wie beim tatsächlichen Einhängen, hier aber nur gegen die
|
|
2636
|
+
Prozess-Umgebung von hmgr (der Main-Prozess liefert `fehlendeVariablen` schon fertig mit der
|
|
2637
|
+
Übersicht, ohne den ortsbezogenen .env-Abgleich, der bleibt dem Einhängen selbst vorbehalten
|
|
2638
|
+
und kommt dann als `warnung` zurück). Wiederverwendet die bestehende .ruhezeile-Optik. */
|
|
2639
|
+
function mcpWarnZeile(fehlend) {
|
|
2640
|
+
var mehrzahl = fehlend.length > 1;
|
|
2641
|
+
var namen = fehlend.map(function (v) { return '${' + v + '}'; }).join(', ');
|
|
2642
|
+
var rz = e('div', 'ruhezeile');
|
|
2643
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
2644
|
+
svg.setAttribute('viewBox', '0 0 16 16');
|
|
2645
|
+
svg.setAttribute('stroke-width', '1.6');
|
|
2646
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
2647
|
+
svg.innerHTML = '<circle cx="8" cy="8" r="6.5"/><path d="M8 4.5v4M8 10.5v.5"/>';
|
|
2648
|
+
rz.appendChild(svg);
|
|
2649
|
+
rz.appendChild(e('p', null, namen + (mehrzahl ? ' sind' : ' ist') + ' in der Umgebung von hmgr nicht gesetzt. Ob '
|
|
2650
|
+
+ (mehrzahl ? 'die Variablen' : 'die Variable') + ' beim Start des Harness gesetzt ' + (mehrzahl ? 'sind' : 'ist')
|
|
2651
|
+
+ ', kann hmgr nicht wissen.'));
|
|
2652
|
+
return rz;
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
/** Notizen sind das Langsame am Eintrag (entwurf/gui.html): zugeklappt, öffnen sich nur, wenn
|
|
2656
|
+
jemand sie sucht. Der Wortlaut bleibt, wie er in der mcp.yaml steht, Zeilenumbrüche
|
|
2657
|
+
eingeschlossen (weiss-raum: pre-line in styles.css). */
|
|
2658
|
+
function mcpNotizenBlock(text) {
|
|
2659
|
+
var d = document.createElement('details');
|
|
2660
|
+
d.className = 'notizen';
|
|
2661
|
+
var s = document.createElement('summary');
|
|
2662
|
+
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
2663
|
+
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
2664
|
+
pfeil.setAttribute('class', 'keil');
|
|
2665
|
+
pfeil.setAttribute('fill', 'none');
|
|
2666
|
+
pfeil.setAttribute('stroke', 'currentColor');
|
|
2667
|
+
pfeil.setAttribute('stroke-width', '1.7');
|
|
2668
|
+
pfeil.setAttribute('stroke-linejoin', 'round');
|
|
2669
|
+
pfeil.setAttribute('aria-hidden', 'true');
|
|
2670
|
+
pfeil.innerHTML = '<path d="M4.3 2.5 7.8 6 4.3 9.5"/>';
|
|
2671
|
+
s.appendChild(pfeil);
|
|
2672
|
+
s.appendChild(document.createTextNode('Notizen'));
|
|
2673
|
+
d.appendChild(s);
|
|
2674
|
+
d.appendChild(e('p', null, text));
|
|
2675
|
+
return d;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
function mcpKarte(k) {
|
|
2679
|
+
var eintrag = k.eintrag;
|
|
2680
|
+
var kEl = e('div', 'mcp');
|
|
2681
|
+
|
|
2682
|
+
var kopf = e('div', 'mcpkopf');
|
|
2683
|
+
kopf.appendChild(e('b', null, eintrag.name));
|
|
2684
|
+
if (eintrag.homepage) kopf.appendChild(mcpHeimLink(eintrag.homepage));
|
|
2685
|
+
var ein = e('button', 'knopf leise klein', 'Einhängen …');
|
|
2686
|
+
ein.type = 'button';
|
|
2687
|
+
ein.setAttribute('aria-label', eintrag.name + ' einhängen');
|
|
2688
|
+
ein.addEventListener('click', function () { oeffneMcpEinhaengen(k); });
|
|
2689
|
+
kopf.appendChild(ein);
|
|
2690
|
+
kEl.appendChild(kopf);
|
|
2691
|
+
|
|
2692
|
+
kEl.appendChild(e('p', 'was', eintrag.beschreibung));
|
|
2693
|
+
|
|
2694
|
+
var konf = e('div', 'kette mcpkonfig');
|
|
2695
|
+
konf.appendChild(e('span', 'glied', mcpKonfigArt(eintrag.konfig)));
|
|
2696
|
+
konf.appendChild(e('span', 'mono', mcpKonfigWortlaut(eintrag.konfig)));
|
|
2697
|
+
kEl.appendChild(konf);
|
|
2698
|
+
|
|
2699
|
+
if (k.fehlendeVariablen.length > 0) kEl.appendChild(mcpWarnZeile(k.fehlendeVariablen));
|
|
2700
|
+
if (eintrag.notizen) kEl.appendChild(mcpNotizenBlock(eintrag.notizen));
|
|
2701
|
+
|
|
2702
|
+
return kEl;
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
/** Ohne Katalog steht hier kein leerer Kasten, sondern der Weg zur Datei: anlegen lässt sich ein
|
|
2706
|
+
Eintrag nur dort, hmgr schreibt sie nie (E61). */
|
|
2707
|
+
function mcpEinladung() {
|
|
2708
|
+
var kasten = e('div', 'startfeld mcpstart');
|
|
2709
|
+
var p1 = e('p');
|
|
2710
|
+
p1.appendChild(document.createTextNode('Noch kein Server im Katalog. hmgr liest ihn aus '));
|
|
2711
|
+
p1.appendChild(e('span', 'mono', pfadSchreibweise('~/.hmgr/mcp.yaml')));
|
|
2712
|
+
p1.appendChild(document.createTextNode(' und schreibt die Datei nie.'));
|
|
2713
|
+
kasten.appendChild(p1);
|
|
2714
|
+
var p2 = e('p');
|
|
2715
|
+
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 '));
|
|
2716
|
+
p2.appendChild(e('span', 'mono', 'hmgr help mcp'));
|
|
2717
|
+
p2.appendChild(document.createTextNode('.'));
|
|
2718
|
+
kasten.appendChild(p2);
|
|
2719
|
+
return kasten;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
function baueMcp() {
|
|
2723
|
+
var w = $('mcpliste');
|
|
2724
|
+
w.textContent = '';
|
|
2725
|
+
if (MCP_KATALOG.length === 0) w.appendChild(mcpEinladung());
|
|
2726
|
+
else MCP_KATALOG.forEach(function (k) { w.appendChild(mcpKarte(k)); });
|
|
2727
|
+
$('navzahlmcp').textContent = MCP_KATALOG.length;
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
/* ---------------- Einhängen (F42) ---------------- */
|
|
2731
|
+
|
|
2732
|
+
var mcpLaufend = null;
|
|
2733
|
+
var mcpPlaetzeAuswahl = [];
|
|
2734
|
+
// Sperre gegen Doppel-Anstoß (wie beim Hinzufügen einer Quelle, E54): zwischen Klick und Dialog
|
|
2735
|
+
// liegt ein IPC-Aufruf. Ein zweiter Klick in dieser Lücke riefe showModal() an einem schon
|
|
2736
|
+
// offenen Dialog auf, und das ist ein Fehler, kein zweiter Dialog.
|
|
2737
|
+
var mcpWahlOeffnet = false;
|
|
2738
|
+
|
|
2739
|
+
async function oeffneMcpEinhaengen(k) {
|
|
2740
|
+
if (mcpWahlOeffnet || $('d-mcp').open) return;
|
|
2741
|
+
mcpWahlOeffnet = true;
|
|
2742
|
+
try {
|
|
2743
|
+
await baueMcpWahl(k);
|
|
2744
|
+
} finally {
|
|
2745
|
+
mcpWahlOeffnet = false;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
async function baueMcpWahl(k) {
|
|
2750
|
+
mcpLaufend = k;
|
|
2751
|
+
$('dm-name').textContent = k.eintrag.name;
|
|
2752
|
+
$('dm-text').textContent = 'Ein Ort je Einhängen. agents kennt keine MCP-Server und steht deshalb nicht zur Wahl.';
|
|
2753
|
+
var box = $('dm-orte');
|
|
2754
|
+
box.textContent = '';
|
|
2755
|
+
// Die Plätze kommen aus dem Kern (F42, E16): die Fähigkeit hängt am Adapter, nicht am
|
|
2756
|
+
// Harness-Namen, hier wird nicht selbst entschieden, wer welche Ebene kennt.
|
|
2757
|
+
var plaetze = await rufe(function () { return window.hmgr.mcpPlaetze(); });
|
|
2758
|
+
mcpPlaetzeAuswahl = plaetze;
|
|
2759
|
+
plaetze.forEach(function (ziel, i) {
|
|
2760
|
+
var schon = k.eingehaengt.some(function (z) { return zielKey(z) === zielKey(ziel); });
|
|
2761
|
+
var aktiv = harnessAktiv(ziel.harness);
|
|
2762
|
+
var label = e('label', 'wahlzeile');
|
|
2763
|
+
var input = document.createElement('input');
|
|
2764
|
+
input.type = 'radio';
|
|
2765
|
+
input.name = 'mcport';
|
|
2766
|
+
input.value = String(i);
|
|
2767
|
+
input.disabled = schon || !aktiv;
|
|
2768
|
+
label.appendChild(input);
|
|
2769
|
+
|
|
2770
|
+
var txt = e('span');
|
|
2771
|
+
txt.appendChild(beschriftet(e('b', null, zielTitel({ ziel: ziel })), ziel.harness));
|
|
2772
|
+
txt.appendChild(e('span', 'mono', mcpOrtSpur(ziel)));
|
|
2773
|
+
|
|
2774
|
+
if (schon) {
|
|
2775
|
+
// F46: die Orte-Liste am Katalog-Eintrag gibt es nicht mehr, der Weg zum Aushängen führt
|
|
2776
|
+
// über den Entfernen-Knopf im MCP-Abschnitt der Ziel-Karte.
|
|
2777
|
+
txt.appendChild(e('span', 'notiz', 'Hängt hier schon. Zum Aushängen den Entfernen-Knopf am MCP-Abschnitt der Ziel-Karte benutzen.'));
|
|
2778
|
+
} else if (!aktiv) {
|
|
2779
|
+
txt.appendChild(e('span', 'notiz', harnessName(ziel.harness) + ' ist inaktiv. Schalt den Harness in den Einstellungen wieder ein, um hier einzuhängen.'));
|
|
2780
|
+
} else if (ziel.ort.art === 'arbeitsverzeichnis') {
|
|
2781
|
+
// E64: die Datei bleibt beim Einhängen unangetastet, aber nicht mehr grundsätzlich — der
|
|
2782
|
+
// Übernehmen-Knopf an der Ziel-Karte fasst sie auf ausdrücklichen Auftrag an.
|
|
2783
|
+
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.'));
|
|
2784
|
+
} else if (ziel.harness === 'hermes' && ziel.ort.art === 'home') {
|
|
2785
|
+
txt.appendChild(e('span', 'notiz', 'Hermes kennt keine Projektebene für MCP-Server, hier geht nur global.'));
|
|
2786
|
+
}
|
|
2787
|
+
label.appendChild(txt);
|
|
2788
|
+
box.appendChild(label);
|
|
2789
|
+
});
|
|
2790
|
+
zeige($('d-mcp'));
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
$('d-mcp').addEventListener('submit', function (ev) {
|
|
2794
|
+
if (ev.submitter && ev.submitter.value === 'los') {
|
|
2795
|
+
var gewaehlt = $('dm-orte').querySelector('input:checked');
|
|
2796
|
+
if (!gewaehlt) ev.preventDefault();
|
|
2797
|
+
}
|
|
2798
|
+
});
|
|
2799
|
+
|
|
2800
|
+
nachDialog($('d-mcp'), async function (wert) {
|
|
2801
|
+
var k = mcpLaufend;
|
|
2802
|
+
mcpLaufend = null;
|
|
2803
|
+
if (wert !== 'los' || !k) return;
|
|
2804
|
+
var gewaehlt = $('dm-orte').querySelector('input:checked');
|
|
2805
|
+
if (!gewaehlt) return;
|
|
2806
|
+
var ziel = mcpPlaetzeAuswahl[+gewaehlt.value];
|
|
2807
|
+
var ergebnis = await rufe(function () {
|
|
2808
|
+
return window.hmgr.mcpEinhaengen({ name: k.eintrag.name, harness: ziel.harness, ort: ziel.ort });
|
|
2809
|
+
});
|
|
2810
|
+
await ladeAlles();
|
|
2811
|
+
// Erfolg über die Meldungsleiste (F43): kein Erfolgsdialog, kein Vermerk an der Orts-Zeile. Der
|
|
2812
|
+
// Kern liefert den Hinweis zur Wirkung nur bei Claude Code, Hermes lädt selbst nach.
|
|
2813
|
+
var teile = [k.eintrag.name + ' hängt jetzt in ' + zielTitel({ ziel: ziel }) + '.'];
|
|
2814
|
+
if (ergebnis.hinweis) teile.push(ergebnis.hinweis);
|
|
2815
|
+
if (ergebnis.warnung) teile.push(ergebnis.warnung);
|
|
2816
|
+
melde(teile.join('\n'));
|
|
2817
|
+
});
|
|
2818
|
+
|
|
2819
|
+
/* ---------------- Aushängen ---------------- */
|
|
2820
|
+
|
|
2821
|
+
var mcpAusKontext = null;
|
|
2822
|
+
function frageMcpAus(eintrag, ziel) {
|
|
2823
|
+
mcpAusKontext = { eintrag: eintrag, ziel: ziel };
|
|
2824
|
+
$('dma-titel').textContent = eintrag.name + ' aus ' + zielTitel({ ziel: ziel }) + ' aushängen?';
|
|
2825
|
+
$('dma-text').textContent = 'Der Eintrag verschwindet aus ' + mcpOrtDatei(ziel)
|
|
2826
|
+
+ '. Der Katalog-Eintrag bleibt, du kannst ihn jederzeit wieder einhängen.';
|
|
2827
|
+
zeige($('d-mcpaus'));
|
|
2828
|
+
}
|
|
2829
|
+
nachDialog($('d-mcpaus'), async function (wert) {
|
|
2830
|
+
var kontext = mcpAusKontext;
|
|
2831
|
+
mcpAusKontext = null;
|
|
2832
|
+
if (wert !== 'ja' || !kontext) return;
|
|
2833
|
+
var ergebnis = await rufe(function () {
|
|
2834
|
+
return window.hmgr.mcpAushaengen({ name: kontext.eintrag.name, harness: kontext.ziel.harness, ort: kontext.ziel.ort });
|
|
2835
|
+
});
|
|
2836
|
+
await ladeAlles();
|
|
2837
|
+
var teile = [kontext.eintrag.name + ' aus ' + zielTitel({ ziel: kontext.ziel }) + ' ausgehängt.'];
|
|
2838
|
+
if (ergebnis.hinweis) teile.push(ergebnis.hinweis);
|
|
2839
|
+
melde(teile.join('\n'));
|
|
2840
|
+
});
|
|
2841
|
+
|
|
2842
|
+
/* ---------------- Übernehmen aus der geteilten .mcp.json (E64) ---------------- */
|
|
2843
|
+
/* Der einzige Weg, auf dem hmgr die geteilte Datei anfasst, deshalb eine ausdrückliche Rückfrage:
|
|
2844
|
+
der Eintrag wandert in die private Konfiguration und verschwindet aus der Datei — für alle, die
|
|
2845
|
+
sie teilen. Der Name muss nicht im Katalog stehen, übernommen wird der Block aus der Datei. */
|
|
2846
|
+
var mcpUebernahmeKontext = null;
|
|
2847
|
+
function frageMcpUebernahme(name, ziel, datei) {
|
|
2848
|
+
mcpUebernahmeKontext = { name: name, ziel: ziel };
|
|
2849
|
+
$('dmu-titel').textContent = name + ' übernehmen?';
|
|
2850
|
+
$('dmu-text').textContent = name + ' wird in deine private Konfiguration für '
|
|
2851
|
+
+ zielTitel({ ziel: ziel }) + ' übernommen und aus ' + (datei || '.mcp.json')
|
|
2852
|
+
+ ' entfernt. Ist die Datei mit dem Team geteilt, verlieren die anderen den Eintrag, sobald die '
|
|
2853
|
+
+ 'Änderung committet ist.';
|
|
2854
|
+
zeige($('d-mcpueb'));
|
|
2855
|
+
}
|
|
2856
|
+
nachDialog($('d-mcpueb'), async function (wert) {
|
|
2857
|
+
var kontext = mcpUebernahmeKontext;
|
|
2858
|
+
mcpUebernahmeKontext = null;
|
|
2859
|
+
if (wert !== 'ja' || !kontext) return;
|
|
2860
|
+
var ergebnis = await rufe(function () {
|
|
2861
|
+
return window.hmgr.mcpUebernehmen({ name: kontext.name, harness: kontext.ziel.harness, ort: kontext.ziel.ort });
|
|
2862
|
+
});
|
|
2863
|
+
await ladeAlles();
|
|
2864
|
+
var teile = [kontext.name + ' gehört jetzt zu deiner privaten Konfiguration für '
|
|
2865
|
+
+ zielTitel({ ziel: kontext.ziel }) + ' und steht nicht mehr in ' + ergebnis.datei + '.'];
|
|
2866
|
+
if (ergebnis.hinweis) teile.push(ergebnis.hinweis);
|
|
2867
|
+
melde(teile.join('\n'));
|
|
2868
|
+
});
|
|
2869
|
+
|
|
2870
|
+
/* ---------------- MCP hinzufügen (E61-GUI-Nachtrag) ---------------- */
|
|
2871
|
+
/* Gegenrichtung zu oeffneMcpEinhaengen: von der Ziel-Karte aus, Mehrfachauswahl über den ganzen
|
|
2872
|
+
Katalog, dasselbe Dialogmuster wie „Skills hinzufügen" (E47) statt der Ortswahl von d-mcp. */
|
|
2873
|
+
var mcpHinzufuegenKontext = null;
|
|
2874
|
+
function dmhBilanzAktualisieren() {
|
|
2875
|
+
var n = $('dmh-liste').querySelectorAll('input:checked').length;
|
|
2876
|
+
$('dmh-bilanz').textContent = zahlwort(n, 'ausgewählt', 'ausgewählt');
|
|
2877
|
+
}
|
|
2878
|
+
function oeffneMcpHinzufuegen(ziel) {
|
|
2879
|
+
mcpHinzufuegenKontext = { ziel: ziel };
|
|
2880
|
+
$('dmh-ziel').textContent = zielTitel({ ziel: ziel });
|
|
2881
|
+
var box = $('dmh-liste');
|
|
2882
|
+
box.textContent = '';
|
|
2883
|
+
// Ohne Katalog stünde hier ein leerer Kasten mit einem Knopf, der nichts tut: hmgr legt
|
|
2884
|
+
// MCP-Einträge nie selbst an (E61), also nennt der Dialog denselben Weg wie die MCP-Ansicht.
|
|
2885
|
+
if (MCP_KATALOG.length === 0) {
|
|
2886
|
+
box.appendChild(e('p', 'ohne', 'Noch kein Server im Katalog. hmgr liest ihn aus '
|
|
2887
|
+
+ pfadSchreibweise('~/.hmgr/mcp.yaml') + ' und schreibt die Datei nie.'));
|
|
2888
|
+
}
|
|
2889
|
+
MCP_KATALOG.forEach(function (k, i) {
|
|
2890
|
+
var schon = k.eingehaengt.some(function (z) { return zielKey(z) === zielKey(ziel); });
|
|
2891
|
+
var label = e('label', 'wahlzeile');
|
|
2892
|
+
var input = document.createElement('input');
|
|
2893
|
+
input.type = 'checkbox';
|
|
2894
|
+
input.value = String(i);
|
|
2895
|
+
input.disabled = schon;
|
|
2896
|
+
input.addEventListener('change', dmhBilanzAktualisieren);
|
|
2897
|
+
label.appendChild(input);
|
|
2898
|
+
var txt = e('span');
|
|
2899
|
+
txt.appendChild(e('b', null, k.eintrag.name));
|
|
2900
|
+
if (k.eintrag.beschreibung) txt.appendChild(e('span', 'mono', k.eintrag.beschreibung));
|
|
2901
|
+
if (schon) txt.appendChild(e('span', 'notiz', 'Hängt hier schon.'));
|
|
2902
|
+
label.appendChild(txt);
|
|
2903
|
+
box.appendChild(label);
|
|
2904
|
+
});
|
|
2905
|
+
dmhBilanzAktualisieren();
|
|
2906
|
+
zeige($('d-mcp-hinzufuegen'));
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
$('d-mcp-hinzufuegen').addEventListener('submit', function (ev) {
|
|
2910
|
+
if (ev.submitter && ev.submitter.value === 'los') {
|
|
2911
|
+
var gewaehlt = Array.from($('dmh-liste').querySelectorAll('input:checked'));
|
|
2912
|
+
if (gewaehlt.length === 0) ev.preventDefault();
|
|
2913
|
+
}
|
|
2914
|
+
});
|
|
2915
|
+
|
|
2916
|
+
nachDialog($('d-mcp-hinzufuegen'), async function (wert) {
|
|
2917
|
+
if (wert !== 'los' || mcpHinzufuegenKontext === null) return;
|
|
2918
|
+
var ziel = mcpHinzufuegenKontext.ziel;
|
|
2919
|
+
var indizes = Array.from($('dmh-liste').querySelectorAll('input:checked')).map(function (i) { return +i.value; });
|
|
2920
|
+
var erfolge = 0;
|
|
2921
|
+
var hinweise = [];
|
|
2922
|
+
var fehlschlaege = [];
|
|
2923
|
+
for (var n = 0; n < indizes.length; n++) {
|
|
2924
|
+
var k = MCP_KATALOG[indizes[n]];
|
|
2925
|
+
try {
|
|
2926
|
+
var ergebnis = await rufe(function () {
|
|
2927
|
+
return window.hmgr.mcpEinhaengen({ name: k.eintrag.name, harness: ziel.harness, ort: ziel.ort });
|
|
2928
|
+
});
|
|
2929
|
+
erfolge++;
|
|
2930
|
+
if (ergebnis.hinweis && hinweise.indexOf(ergebnis.hinweis) < 0) hinweise.push(ergebnis.hinweis);
|
|
2931
|
+
// F39-Warnungen gesammelt im Erfolgs-Toast statt je Einhängen einzeln zu melden.
|
|
2932
|
+
if (ergebnis.warnung) hinweise.push(k.eintrag.name + ': ' + ergebnis.warnung);
|
|
2933
|
+
} catch (fehler) {
|
|
2934
|
+
// rufe() hat den Fehler zwar schon in die Meldungsleiste gesetzt, die Schluss-Meldung unten
|
|
2935
|
+
// überschreibt sie aber gleich wieder. Ein Teilerfolg muss beide Seiten nennen, sonst behauptet
|
|
2936
|
+
// der Toast einen Lauf ohne Fehler. Die übrigen Einträge laufen weiter.
|
|
2937
|
+
fehlschlaege.push(k.eintrag.name + ': ' + fehler.message);
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
ZIEL_OFFEN[kartenKey(ziel)] = true;
|
|
2941
|
+
await ladeAlles();
|
|
2942
|
+
var zeilen = [erfolge > 0
|
|
2943
|
+
? zahlwort(erfolge, 'MCP-Server hängt', 'MCP-Server hängen') + ' jetzt in ' + zielTitel({ ziel: ziel }) + '.'
|
|
2944
|
+
: 'Nichts eingehängt in ' + zielTitel({ ziel: ziel }) + '.'];
|
|
2945
|
+
hinweise.forEach(function (h) { zeilen.push(h); });
|
|
2946
|
+
if (fehlschlaege.length > 0) {
|
|
2947
|
+
zeilen.push(zahlwort(fehlschlaege.length, 'Eintrag ließ', 'Einträge ließen') + ' sich nicht einhängen:');
|
|
2948
|
+
fehlschlaege.forEach(function (f) { zeilen.push(f); });
|
|
2949
|
+
}
|
|
2950
|
+
melde(zeilen.join('\n'));
|
|
2951
|
+
});
|
|
2952
|
+
|
|
2033
2953
|
/* ================= EINSTELLUNGEN ================= */
|
|
2034
2954
|
|
|
2035
2955
|
/** Ziele eines Harness-Typs mit mindestens einem eigenen Link oder liegen gebliebener
|
|
@@ -2559,7 +3479,11 @@
|
|
|
2559
3479
|
|
|
2560
3480
|
// Die Fragen kommen fertig formuliert aus dem Kern; für die Anzeige werden Home-Pfade darin
|
|
2561
3481
|
// wie überall sonst auf ~ gekürzt (nur Anzeige, die Antwort transportiert keinen Text zurück).
|
|
2562
|
-
|
|
3482
|
+
/** Wie pfadKurz, aber mitten im Fließtext einer Rückfrage — und ebenso mit beiden Trennern (E68). */
|
|
3483
|
+
function frageKurz(text) {
|
|
3484
|
+
if (!HOME) return text;
|
|
3485
|
+
return text.split(HOME + '/').join('~/').split(HOME + '\\').join('~\\');
|
|
3486
|
+
}
|
|
2563
3487
|
window.hmgr.onRueckfrage(function (nachricht) {
|
|
2564
3488
|
if (nachricht.art === 'bestaetigen') {
|
|
2565
3489
|
$('db-frage').textContent = frageKurz(nachricht.frage);
|
|
@@ -2607,5 +3531,5 @@
|
|
|
2607
3531
|
|
|
2608
3532
|
/* ---------------- Start ---------------- */
|
|
2609
3533
|
|
|
2610
|
-
|
|
3534
|
+
ladeUmgebung().then(ladeAlles).catch(function (fehler) { melde(fehler.message || String(fehler)); });
|
|
2611
3535
|
})();
|