hmgr 0.0.52 → 0.0.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "fassung": 1,
3
3
  "versionen": {
4
+ "0.0.53": [
5
+ "MCP-Server stehen an der Ordnerkarte jetzt in derselben Tabelle wie die Skills, mit dem Zeichen des Harness davor. Ein Tooltip erklärt, warum ein Server für Hermes fehlt: Hermes liest MCP-Server nur im Home oder in einem Profil, nicht pro Ordner. `hmgr mcp list` sagt das am Ende ebenfalls."
6
+ ],
4
7
  "0.0.52": [
5
8
  "Liegt ein Skill in einem Repo in mehreren Fassungen, je Werkzeug eine (etwa `.claude/skills` und `.hermes/skills`), verlinkt hmgr jetzt für jeden Harness die passende Fassung. Gibt es keine passende, nennt die Meldung die Pfade, und `hmgr link … --pfad` wählt einen aus."
6
9
  ],
package/dist/cli/hilfe.js CHANGED
@@ -424,6 +424,10 @@ global (Scope user). Hermes kennt nur global, mcp_servers in seiner config.yaml
424
424
  mit --ort profil:<name>); ein Projekt-Ziel ist bei Hermes ein Fehler, es kennt keine Projektebene für
425
425
  MCPs. agents kennt MCP-Server gar nicht.
426
426
 
427
+ Hängt ein Eintrag an einem Arbeitsverzeichnis und ist ein aktiver Harness ohne Projektebene (Hermes) da,
428
+ endet mcp list mit einer Zeile, die sagt, dass dieser Harness den Eintrag dort nicht liest, und auf
429
+ --ort home oder --ort profil:<name> verweist.
430
+
427
431
  Bei claude-code führt der Scope local die Server je Git-Repo, nicht je Unterordner (E91). Ein
428
432
  Arbeitsverzeichnis, das in einem Repo liegt, ohne dessen Wurzel zu sein, bekommt deshalb kein mcp add,
429
433
  mcp remove und mcp migrate: die Meldung nennt den Wurzelordner, dort gehören die Server hin (ist er
package/dist/cli/main.js CHANGED
@@ -741,6 +741,28 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
741
741
  for (const f of [...uebersicht.unbekannteGeteilteFundorte].sort((a, b) => nachName(a.name, b.name)))
742
742
  console.log(` ${f.name} ${f.ziel.harness} ${beschreibeOrt(f.ziel.ort)} ${f.datei} · Scope ${f.scope}`);
743
743
  }
744
+ // E120: hängt etwas an einem Arbeitsverzeichnis, erklärt eine Zeile, warum ein aktiver Harness
745
+ // ohne Ordner-MCP (Hermes) es dort nicht hat. Abgeleitet aus den Ebenen der Fähigkeit (E16).
746
+ const anOrdner = [
747
+ ...uebersicht.katalog.flatMap((k) => [...k.eingehaengt, ...k.geteilteFundorte.map((g) => g.ziel)]),
748
+ ...uebersicht.unbekannt.map((u) => u.ziel),
749
+ ...uebersicht.unbekannteGeteilteFundorte.map((u) => u.ziel),
750
+ ].some((z) => z.ort.art === 'arbeitsverzeichnis');
751
+ if (anOrdner) {
752
+ const aktive = await anwendungsfaelle.aktiveHarnesses();
753
+ const anzeigeNamen = { 'claude-code': 'Claude Code', hermes: 'Hermes', agents: 'Agents' };
754
+ for (const { harness, ebenen } of anwendungsfaelle.mcpEbenen()) {
755
+ if (!aktive.includes(harness) || ebenen.includes('arbeitsverzeichnis'))
756
+ continue;
757
+ const home = ebenen.includes('home');
758
+ const profil = ebenen.includes('profil');
759
+ if (!home && !profil)
760
+ continue;
761
+ const wo = home && profil ? 'im Home oder in einem Profil' : home ? 'im Home' : 'in einem Profil';
762
+ const orte = [...(home ? ['--ort home'] : []), ...(profil ? ['--ort profil:<name>'] : [])].join(' oder ');
763
+ console.log(`${anzeigeNamen[harness] ?? harness} liest MCP-Server nicht pro Ordner, nur ${wo} (${orte}).`);
764
+ }
765
+ }
744
766
  return 0;
745
767
  }
746
768
  if (unterkommando !== 'add' && unterkommando !== 'remove' && unterkommando !== 'migrate')
package/dist/gui/main.js CHANGED
@@ -464,6 +464,7 @@ function registriereIpc() {
464
464
  }));
465
465
  // Plätze für den Einhängen-Dialog (F42): die Fähigkeit hängt am Adapter, nicht am Namen (E16),
466
466
  // deshalb kommt die Liste unverändert aus dem Kern statt aus einer Harness-Fallunterscheidung hier.
467
+ ipcMain.handle('hmgr:mcpEbenen', async () => auffangen(async () => anwendungsfaelle.mcpEbenen()));
467
468
  ipcMain.handle('hmgr:mcpPlaetze', async () => auffangen(async () => anwendungsfaelle.mcpPlaetze()));
468
469
  ipcMain.handle('hmgr:mcpEinhaengen', async (_event, args) => auffangen(async () => mcpWirkungSerialisieren(await anwendungsfaelle.mcpEinhaengen({ name: args.name, ziel: zielAusIpc(args.harness, args.ort) }))));
469
470
  ipcMain.handle('hmgr:mcpAushaengen', async (_event, args) => auffangen(async () => mcpWirkungSerialisieren(await anwendungsfaelle.mcpAushaengen({ name: args.name, ziel: zielAusIpc(args.harness, args.ort) }))));
@@ -997,7 +998,7 @@ function fensterOeffnen() {
997
998
  streifenHarnesse = streifen[0]?.querySelector('.zeilenzeichen')?.getAttribute('aria-label') ?? null;
998
999
  streifenText = streifen[0]?.querySelector('.bundlekopf')?.textContent?.replace(/\\s+/g, ' ').trim() ?? null;
999
1000
  alleGruppeZeilen = Array.from(streifen[0]?.querySelectorAll('.linkliste li .name') ?? []).map(nurName).join(',');
1000
- alleTabelleZeilen = Array.from(streifenKarte?.querySelectorAll('.linkliste li') ?? [])
1001
+ alleTabelleZeilen = Array.from(streifenKarte?.querySelectorAll('.linkliste:not(.mcpzeilen) li') ?? [])
1001
1002
  .filter((li) => li.closest('.bundlegruppe') === null && li.querySelector('.name'))
1002
1003
  .map((li) => nurName(li.querySelector('.name'))).join(',');
1003
1004
  Array.from(streifen[0]?.querySelectorAll('.bundlekopf button') ?? [])
@@ -1074,7 +1075,7 @@ function fensterOeffnen() {
1074
1075
  // Seit E64 stehen im MCP-Abschnitt zwei Knopfarten nebeneinander (Entfernen und
1075
1076
  // Übernehmen), deshalb wird hier nicht mehr der erste Knopf genommen, sondern der
1076
1077
  // Entfernen-Knopf an seinem aria-label erkannt.
1077
- var mcpZeilenKnoepfe = Array.from(document.querySelectorAll('#zielliste .mcpkartezeilen button'));
1078
+ var mcpZeilenKnoepfe = Array.from(document.querySelectorAll('#zielliste .mcpzeilen button'));
1078
1079
  try {
1079
1080
  var mcpAusKnopf = mcpZeilenKnoepfe.find(function (b) {
1080
1081
  return (b.getAttribute('aria-label') || '').indexOf('aushängen') > -1;
@@ -1126,13 +1127,20 @@ function fensterOeffnen() {
1126
1127
  var hermesKarte = karteMitKopftext('Hermes · Home');
1127
1128
  var hermesMcpText = hermesKarte?.querySelector('.mcpkarteabschnitt')?.textContent
1128
1129
  ?.replace(/\\s+/g, ' ').trim() ?? null;
1129
- var hermesMcpZeilenKnopfZahl = hermesKarte?.querySelectorAll('.mcpkartezeilen button').length ?? 0;
1130
+ var hermesMcpZeilenKnopfZahl = hermesKarte?.querySelectorAll('.mcpzeilen button').length ?? 0;
1130
1131
  var hermesMcpKnopfVorhanden = mcpHinzuKnopfAn(hermesKarte) !== undefined;
1131
1132
  var kundeXKarte = karteMitKopftext('kunde-x');
1132
1133
  var kundeXMcpText = kundeXKarte?.querySelector('.mcpkarteabschnitt')?.textContent
1133
1134
  ?.replace(/\\s+/g, ' ').trim() ?? null;
1134
- var kundeXMcpZeilenKnopfZahl = kundeXKarte?.querySelectorAll('.mcpkartezeilen button').length ?? 0;
1135
+ var kundeXMcpZeilenKnopfZahl = kundeXKarte?.querySelectorAll('.mcpzeilen button').length ?? 0;
1135
1136
  var kundeXMcpKnopfVorhanden = mcpHinzuKnopfAn(kundeXKarte) !== undefined;
1137
+ // E120: Tabellenkopf des MCP-Abschnitts und die Zeile mail (Katalog, unter „Alle").
1138
+ var kundeXMcpKopf = Array.from(kundeXKarte?.querySelectorAll('.mcpkarteabschnitt .linkkopf .etikett') ?? [])
1139
+ .map((x) => x.textContent).join('|');
1140
+ var kundeXMailZeile = Array.from(kundeXKarte?.querySelectorAll('.mcpzeilen li') ?? [])
1141
+ .find((li) => (li.querySelector('.name')?.textContent || '') === 'mail');
1142
+ var kundeXMailQuelle = kundeXMailZeile?.querySelector('.quelle')?.textContent ?? null;
1143
+ var kundeXMailZeichenTitel = kundeXMailZeile?.querySelector('.zeilenzeichen')?.getAttribute('title') ?? null;
1136
1144
  // Kartenkopf-Zahl (E71): der Kopf zählt Skills, gebündelt wie der Körper zeilt. An der
1137
1145
  // Verzeichnis-Karte kunde-x hängen dieselben zwei Skills an zwei Harnesses, eine Summe
1138
1146
  // über die Abschnitte ergäbe hier 4.
@@ -1373,6 +1381,9 @@ function fensterOeffnen() {
1373
1381
  kundeXMcpText,
1374
1382
  kundeXMcpZeilenKnopfZahl,
1375
1383
  kundeXMcpKnopfVorhanden,
1384
+ kundeXMcpKopf,
1385
+ kundeXMailQuelle,
1386
+ kundeXMailZeichenTitel,
1376
1387
  kundeXKopfZahl,
1377
1388
  hermesKopfZahl,
1378
1389
  hermesWarnzahl,
@@ -100,6 +100,7 @@ contextBridge.exposeInMainWorld('hmgr', {
100
100
  mcpUebersicht: () => ipcRenderer.invoke('hmgr:mcpUebersicht'),
101
101
  /** Plätze für den Einhängen-Dialog (F42): kommt aus dem Kern, hängt an der Fähigkeit (E16). */
102
102
  mcpPlaetze: () => ipcRenderer.invoke('hmgr:mcpPlaetze'),
103
+ mcpEbenen: () => ipcRenderer.invoke('hmgr:mcpEbenen'),
103
104
  mcpEinhaengen: (args) => ipcRenderer.invoke('hmgr:mcpEinhaengen', args),
104
105
  mcpAushaengen: (args) => ipcRenderer.invoke('hmgr:mcpAushaengen', args),
105
106
  /** Übernehmen aus der geteilten .mcp.json in den privaten Scope (E64), nach Bestätigung im Renderer. */
@@ -339,6 +339,8 @@
339
339
  // Kern (F42, E16): Grundlage für den MCP-Abschnitt und den „MCP hinzufügen …"-Knopf an den
340
340
  // Ziel-Karten — nie am Harness-Namen entschieden, sondern an dieser Liste.
341
341
  var MCP_PLAETZE = [];
342
+ // MCP-Ebenen je Harness (E120), aus der Fähigkeit am Adapter: Grundlage der Tooltip-Erklärung.
343
+ var MCP_EBENEN = [];
342
344
  var quellenFilter = 'alle';
343
345
  // Sortierung der Skills-Rubrik (E79, Richtung E81): hält für die Sitzung wie quellenFilter,
344
346
  // übersteht also ladeAlles. SKILLS selbst bleibt unsortiert nach Datum, damit li.dataset.index
@@ -473,6 +475,7 @@
473
475
  rufe(function () { return window.hmgr.mcpPlaetze(); }),
474
476
  rufe(function () { return window.hmgr.bundles(); }),
475
477
  rufe(function () { return window.hmgr.harnessHeime(); }),
478
+ rufe(function () { return window.hmgr.mcpEbenen(); }),
476
479
  ]);
477
480
  // Alter Stand der Verzeichnis-Pfade, vor dem Überschreiben unten (E84 Teil 1): zielKarten()
478
481
  // liest hier noch ZIELE/AKTIVE_HARNESSE von vor diesem Lauf. null heißt „kein Vergleich", der
@@ -495,6 +498,7 @@
495
498
  BUNDLES.sort(function (a, b) { return a.name.localeCompare(b.name, 'de') || a.quelleText.localeCompare(b.quelleText, 'de'); });
496
499
  BUNDLES_KAPUTT = ergebnisse[9].kaputte;
497
500
  HARNESS_HEIME = ergebnisse[10] || [];
501
+ MCP_EBENEN = ergebnisse[11] || [];
498
502
  STANDARD_REF = {};
499
503
  SKILLS.forEach(function (s) { if (STANDARD_REF[s.quelleText] === undefined) STANDARD_REF[s.quelleText] = s.ref; });
500
504
 
@@ -1112,54 +1116,115 @@
1112
1116
  return zeilen;
1113
1117
  }
1114
1118
 
1115
- /** Der MCP-Abschnitt unter der Skill-Link-Tabelle einer Ziel-Karte (E61-GUI-Nachtrag): nur, wenn
1116
- dort etwas eingehängt ist oder der „hinzufügen"-Knopf ohnehin sichtbar wäre (mcpKannAn). */
1117
- function mcpAbschnitt(ziel) {
1118
- var wurzel = mcpGitWurzel(ziel);
1119
- if (wurzel !== null) {
1120
- var gesperrt = e('div', 'mcpkarteabschnitt');
1121
- gesperrt.appendChild(e('span', 'etikett', 'MCP'));
1122
- gesperrt.appendChild(e('p', 'ohne mcpkarteleer', mcpWurzelNotiz(wurzel)));
1123
- return gesperrt;
1124
- }
1125
- var zeilen = mcpZeilenFuerZiel(ziel);
1126
- if (zeilen.length === 0 && !mcpKannAn(ziel)) return null;
1119
+ /** Quelle-Spalte und Beschreibung einer MCP-Zeile (E120, F142). */
1120
+ function mcpQuelleText(row) {
1121
+ if (row.art === 'katalog') return 'Katalog';
1122
+ if (row.art === 'fremd') return 'nicht im Katalog';
1123
+ return '.mcp.json des Projekts';
1124
+ }
1125
+
1126
+ /** Tooltip an den Harness-Zeichen einer MCP-Zeile unter „Alle" (E120): wo der Server hier hängt und,
1127
+ je weiterem aktivem Harness des Orts, warum nicht. Der Grund kommt aus den MCP-Ebenen der
1128
+ Fähigkeit (MCP_EBENEN, E16), nie aus dem Harness-Namen. Ein Harness, der den Server hier haben
1129
+ könnte (Ebene arbeitsverzeichnis), bekommt keinen Satz: ein Grund wäre geraten (etwa fehlende
1130
+ CLI, E103). */
1131
+ function mcpZeichenTitel(name, hatHarnesse, teile) {
1132
+ var saetze = ['Hier hängt ' + name + ' für ' + hatHarnesse.map(harnessName).join(', ') + '.'];
1133
+ var gesehen = {};
1134
+ teile.forEach(function (t) {
1135
+ var h = t.ziel.harness;
1136
+ if (gesehen[h] || hatHarnesse.indexOf(h) > -1 || !harnessAktiv(h)) return;
1137
+ gesehen[h] = true;
1138
+ var f = MCP_EBENEN.find(function (x) { return x.harness === h; });
1139
+ if (!f) return;
1140
+ if (f.ebenen.indexOf('arbeitsverzeichnis') > -1) return;
1141
+ if (f.ebenen.length === 0) {
1142
+ saetze.push(harnessName(h) + ' kennt keine MCP-Server.');
1143
+ } else {
1144
+ saetze.push(harnessName(h) + ' liest MCP-Server nicht pro Ordner, nur im Home'
1145
+ + (f.ebenen.indexOf('profil') > -1 ? ' oder in einem Profil' : '') + '.');
1146
+ }
1147
+ });
1148
+ return saetze.join(' ');
1149
+ }
1150
+
1151
+ /** Der MCP-Abschnitt einer Ziel-Karte (E61-GUI-Nachtrag, E120): eine Tabelle mit denselben Spalten
1152
+ wie die Skill-Tabelle darüber. `ziele` ist ein Ziel (Harness-Chip) oder mehrere (Chip „Alle",
1153
+ `teile` gesetzt: dann stehen die Harness-Zeichen vor dem Namen). Nur, wenn dort etwas
1154
+ eingehängt ist oder der „hinzufügen"-Knopf ohnehin sichtbar wäre (mcpKannAn). Sperrnotiz (E91)
1155
+ und „Noch nichts eingehängt." bleiben eine Zeile über die Breite. */
1156
+ function mcpAbschnitt(ziele, teile) {
1157
+ var alle = teile !== undefined;
1158
+ if (!Array.isArray(ziele)) ziele = [ziele];
1159
+ var notizen = [];
1160
+ var zeilen = [];
1161
+ var kannAn = false;
1162
+ ziele.forEach(function (ziel) {
1163
+ var wurzel = mcpGitWurzel(ziel);
1164
+ if (wurzel !== null) { notizen.push(mcpWurzelNotiz(wurzel)); return; }
1165
+ if (mcpKannAn(ziel)) kannAn = true;
1166
+ mcpZeilenFuerZiel(ziel).forEach(function (row) { row.ziel = ziel; zeilen.push(row); });
1167
+ });
1168
+ if (zeilen.length === 0 && notizen.length === 0 && !kannAn) return null;
1169
+ zeilen.sort(function (a, b) {
1170
+ return a.name.localeCompare(b.name, 'de') || harnessName(a.ziel.harness).localeCompare(harnessName(b.ziel.harness), 'de');
1171
+ });
1127
1172
  var abschnitt = e('div', 'mcpkarteabschnitt');
1128
- abschnitt.appendChild(e('span', 'etikett', 'MCP'));
1129
1173
  if (zeilen.length === 0) {
1130
- abschnitt.appendChild(e('p', 'ohne mcpkarteleer', 'Noch nichts eingehängt.'));
1174
+ abschnitt.appendChild(e('span', 'etikett', 'MCP'));
1175
+ if (notizen.length === 0) abschnitt.appendChild(e('p', 'ohne mcpkarteleer', 'Noch nichts eingehängt.'));
1176
+ notizen.forEach(function (n) { abschnitt.appendChild(e('p', 'ohne mcpkarteleer', n)); });
1131
1177
  return abschnitt;
1132
1178
  }
1133
- var ul = e('ul', 'mcpkartezeilen');
1179
+ var kopf = e('div', 'linkkopf');
1180
+ kopf.appendChild(e('span'));
1181
+ kopf.appendChild(e('span', 'etikett', 'MCP'));
1182
+ kopf.appendChild(e('span', 'etikett', 'Beschreibung'));
1183
+ kopf.appendChild(e('span', 'etikett', 'Quelle'));
1184
+ kopf.appendChild(e('span'));
1185
+ abschnitt.appendChild(kopf);
1186
+ var ul = e('ul', 'linkliste mcpzeilen');
1134
1187
  zeilen.forEach(function (row) {
1188
+ var ziel = row.ziel;
1135
1189
  var li = e('li');
1136
- li.appendChild(e('span', 'name', row.name));
1190
+ li.appendChild(e('span', 'repo'));
1191
+ var nameSpan = e('span', 'name');
1192
+ if (alle) {
1193
+ var hat = [];
1194
+ zeilen.forEach(function (r) {
1195
+ if (r.name === row.name && hat.indexOf(r.ziel.harness) === -1) hat.push(r.ziel.harness);
1196
+ });
1197
+ hat.sort(function (a, b) { return harnessName(a).localeCompare(harnessName(b), 'de'); });
1198
+ var zeichen = harnesseZeichenSammel([ziel.harness]);
1199
+ zeichen.title = mcpZeichenTitel(row.name, hat, teile);
1200
+ nameSpan.appendChild(zeichen);
1201
+ }
1202
+ nameSpan.appendChild(document.createTextNode(row.name));
1203
+ li.appendChild(nameSpan);
1204
+ var wasFeld = e('span', 'wasfeld');
1205
+ if (row.art === 'katalog' || row.art === 'geteilt') wasFeld.appendChild(e('span', 'was', row.beschreibung || ''));
1206
+ else if (row.art === 'fremd-geteilt') wasFeld.appendChild(e('span', 'was fehlt', 'nicht im Katalog'));
1207
+ li.appendChild(wasFeld);
1208
+ li.appendChild(e('span', 'quelle', mcpQuelleText(row)));
1137
1209
  if (row.art === 'katalog') {
1138
- li.appendChild(e('span', 'was', row.beschreibung || ''));
1139
1210
  var weg = e('button', 'knopf leise klein', 'Entfernen');
1140
1211
  weg.type = 'button';
1141
1212
  weg.setAttribute('aria-label', row.name + ' aus ' + zielTitel({ ziel: ziel }) + ' aushängen');
1142
1213
  weg.addEventListener('click', function () { frageMcpAus(row.eintrag, ziel); });
1143
1214
  li.appendChild(weg);
1144
1215
  } else if (row.art === 'geteilt' || row.art === 'fremd-geteilt') {
1145
- // E64: der Vermerk nennt die Bedeutung („steht im Projekt, nicht bei dir"), nicht mehr bloß
1146
- // den Dateinamen — und daneben steht der Weg, das zu ändern.
1147
- li.appendChild(row.art === 'geteilt'
1148
- ? e('span', 'was', row.beschreibung || '')
1149
- : e('span', 'was fehlt', 'nicht im Katalog'));
1150
- li.appendChild(e('span', 'geteiltvermerk', 'in der .mcp.json des Projekts'));
1151
1216
  var her = e('button', 'knopf leise klein', 'Übernehmen …');
1152
1217
  her.type = 'button';
1153
1218
  her.setAttribute('aria-label', row.name + ' aus der .mcp.json des Projekts in deine private Konfiguration übernehmen');
1154
1219
  her.addEventListener('click', function () { frageMcpUebernahme(row.name, ziel, row.datei); });
1155
1220
  li.appendChild(her);
1156
1221
  } else {
1157
- li.appendChild(e('span', 'was fehlt', 'nicht im Katalog'));
1158
1222
  li.appendChild(e('span'));
1159
1223
  }
1160
1224
  ul.appendChild(li);
1161
1225
  });
1162
1226
  abschnitt.appendChild(ul);
1227
+ notizen.forEach(function (n) { abschnitt.appendChild(e('p', 'ohne mcpkarteleer', n)); });
1163
1228
  return abschnitt;
1164
1229
  }
1165
1230
 
@@ -1357,10 +1422,8 @@
1357
1422
  if (mcpKnopfLeer) leer.appendChild(mcpKnopfLeer);
1358
1423
  });
1359
1424
  teil.appendChild(leer);
1360
- verzMcpZiele.forEach(function (mz) {
1361
- var mcpAbschnLeer = mcpAbschnitt(mz);
1362
- if (mcpAbschnLeer) teil.appendChild(mcpAbschnLeer);
1363
- });
1425
+ var mcpAbschnLeer = mcpAbschnitt(verzMcpZiele, teile);
1426
+ if (mcpAbschnLeer) teil.appendChild(mcpAbschnLeer);
1364
1427
  return teil;
1365
1428
  }
1366
1429
 
@@ -1494,10 +1557,8 @@
1494
1557
 
1495
1558
  // MCP-Abschnitt (E61-GUI-Nachtrag): auf Kartenebene unter „Alle", wie unter dem Harness-Chip,
1496
1559
  // der an diesem Pfad MCP kann.
1497
- verzMcpZiele.forEach(function (mz) {
1498
- var mcpAbschnAlle = mcpAbschnitt(mz);
1499
- if (mcpAbschnAlle) teil.appendChild(mcpAbschnAlle);
1500
- });
1560
+ var mcpAbschnAlle = mcpAbschnitt(verzMcpZiele, teile);
1561
+ if (mcpAbschnAlle) teil.appendChild(mcpAbschnAlle);
1501
1562
 
1502
1563
  var fuss = e('div', 'zielfuss');
1503
1564
  fuss.appendChild(alleSkillsKnopf(pfad, teile));
@@ -771,21 +771,11 @@ dialog::backdrop{background:rgba(9,45,37,.62)}
771
771
  }
772
772
 
773
773
  /* MCP an der Ziel-Karte (E61-GUI-Nachtrag), Gegenrichtung zur MCP-Ansicht (F46: die einzige Stelle,
774
- die noch zeigt, wo ein Server hängt): eigener Abschnitt unter der Skill-Link-Tabelle, Name,
775
- Beschreibung/Vermerk, Aktion rechtsbündig, flach in der Karte statt in einem eigenen Kasten. */
774
+ die noch zeigt, wo ein Server hängt): eigener Abschnitt unter der Skill-Link-Tabelle, seit E120 mit
775
+ derselben Tabelle (linkkopf/linkliste) wie die Skills; flach in der Karte statt in einem eigenen Kasten. */
776
776
  .mcpkarteabschnitt{margin-top:16px;padding-top:14px;border-top:1px solid var(--strich)}
777
777
  .mcpkarteabschnitt > .etikett{display:block;margin-bottom:10px}
778
778
  .mcpkarteleer{margin:0;color:var(--tinte-leise);font-style:italic}
779
- .mcpkartezeilen{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}
780
- .mcpkartezeilen li{display:flex;flex-wrap:wrap;align-items:center;gap:5px 14px}
781
- .mcpkartezeilen .name{font-family:var(--mono);font-size:16px;letter-spacing:-.012em;color:var(--tinte)}
782
- .mcpkartezeilen .was{font-size:16px;color:var(--tinte-weich)}
783
- .mcpkartezeilen .was.fehlt{color:var(--tinte-leise);font-style:italic}
784
- .mcpkartezeilen .knopf{margin-left:auto}
785
- .mcpkartezeilen .kein-weg{margin-left:auto;font-size:16px;color:var(--tinte-leise);text-align:right}
786
- /* Wo ein Eintrag steht (E64): kein margin-left:auto, sonst teilte er sich den freien Platz mit dem
787
- Übernehmen-Knopf und stünde in der Mitte statt beim Text. */
788
- .mcpkartezeilen .geteiltvermerk{font-size:16px;color:var(--tinte-leise)}
789
779
  /* Bundle-Gruppe an der Ziel-Karte (E69, F68): eine Einheit über ihren Skill-Zeilen, sichtbar
790
780
  zusammengefasst durch eine linke Kante statt durch einen eigenen Kasten — die Zeilen darin sind
791
781
  dieselbe Tabelle wie darunter und sollen mit ihr fluchten. */
@@ -578,6 +578,15 @@ export class Anwendungsfaelle {
578
578
  }
579
579
  return ziele;
580
580
  }
581
+ /**
582
+ * Die MCP-Ebenen je Harness-Typ (E120), aus der Fähigkeit am Adapter, nie am Namen (E16): eine
583
+ * leere Liste heißt, der Harness kennt MCP-Server gar nicht (agents). Grundlage für die Erklärung,
584
+ * warum ein Server an einem Ort für einen Harness fehlt (GUI-Tooltip, Hinweis von `mcp list`).
585
+ * Unabhängig von Installation und Aktivierung; das entscheidet der Aufrufer.
586
+ */
587
+ mcpEbenen() {
588
+ return this.p.harnesses.map((h) => ({ harness: h.name, ebenen: kannMcp(h) ? h.mcp.ebenen() : [] }));
589
+ }
581
590
  /**
582
591
  * Die Plätze für den Einhängen-Dialog der GUI (E61-Folgerunde, F42): dieselbe Liste wie in
583
592
  * `mcpUebersicht`, aber roh, ohne den Abgleich mit Katalog-Einträgen. Die Fähigkeit hängt am
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hmgr",
3
- "version": "0.0.52",
3
+ "version": "0.0.53",
4
4
  "description": "Harness Manager: Skills und Plugins für AI-Harnesses verwalten",
5
5
  "license": "MIT",
6
6
  "author": "Tim Kaufmann",