hmgr 0.0.54 → 0.0.56

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.
@@ -56,6 +56,7 @@ function texteLesen(wert, feld) {
56
56
  return e;
57
57
  });
58
58
  }
59
+ /** `null` für ein Plugin, das hmgr aus dieser Quelle nicht bedienen kann (fremde oder fehlende `source`). */
59
60
  function pluginLesen(roh, dateien) {
60
61
  if (typeof roh !== 'object' || roh === null || Array.isArray(roh))
61
62
  throw new MarketplaceUnbekannteForm('Ein Eintrag unter „plugins" ist kein Objekt');
@@ -64,10 +65,11 @@ function pluginLesen(roh, dateien) {
64
65
  if (typeof name !== 'string' || name.length === 0)
65
66
  throw new MarketplaceUnbekannteForm('Ein Plugin hat keinen Namen');
66
67
  const quelle = eintrag['source'];
67
- // Die Doku kennt nur eine Pfadangabe. Fremde Quellen ({ source: "github", … }) und ein
68
- // fehlendes Feld lehnt hmgr ab, statt einen Ordner zu raten.
68
+ // Die Doku kennt als Pfad nur einen Text. Fremde Quellen ({ source: "npm" | "github" | "url", … })
69
+ // und ein fehlendes Feld kann hmgr aus dieser Quelle nicht bedienen. Es lässt genau diesen Eintrag
70
+ // weg, statt die ganze Datei zu verwerfen und damit alle übrigen Plugins mitzunehmen.
69
71
  if (typeof quelle !== 'string')
70
- throw new MarketplaceUnbekannteForm(`Plugin „${name}": „source" ist kein Pfad in dieser Quelle`);
72
+ return null;
71
73
  const wurzel = normalisieren(quelle);
72
74
  // Mit „skills" gilt genau diese Liste, sonst der Ordner <source>/skills (HARNESS-FAKTEN:
73
75
  // mehrere Plugins teilen sich so einen gemeinsamen skills-Ordner).
@@ -95,5 +97,5 @@ export function marketplaceLesen(text, dateien) {
95
97
  const plugins = wurzel['plugins'];
96
98
  if (!Array.isArray(plugins))
97
99
  throw new MarketplaceUnbekannteForm('kein „plugins" als Liste');
98
- return { name, plugins: plugins.map((p) => pluginLesen(p, dateien)) };
100
+ return { name, plugins: plugins.map((p) => pluginLesen(p, dateien)).filter((p) => p !== null) };
99
101
  }
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "fassung": 1,
3
3
  "versionen": {
4
+ "0.0.56": [
5
+ "Eine Quelle, deren Plugin als npm-Paket oder aus einem fremden Repo kommt, lässt die Oberfläche nicht mehr leer. hmgr überspringt solche Plugins und zeigt alles andere wie gewohnt."
6
+ ],
7
+ "0.0.55": [
8
+ "Auch die Dialoge „Verlinken“ und „MCP hinzufügen“ haben jetzt ein Suchfeld. Du kannst über mehrere Suchen hinweg ankreuzen, unten steht, wie viele Einträge ausgewählt sind."
9
+ ],
4
10
  "0.0.54": [
5
11
  "Der Dialog „Einhängen“ eines MCP-Servers hat jetzt ein Suchfeld. Tippen filtert die Orte nach Name und Pfad, so findest du ein Arbeitsverzeichnis ohne langes Scrollen."
6
12
  ],
package/dist/gui/main.js CHANGED
@@ -943,12 +943,64 @@ function fensterOeffnen() {
943
943
  // Verlinken-Dialog „Wohin?" (E76): er bietet nur Ziele aktiver Harnesses an. Der Text
944
944
  // der Wahlliste sagt, welche das sind; ist gar keines aktiv, steht dort der Leerhinweis.
945
945
  var dzZieleText = null;
946
+ // E123: Suchfeld im Verlinken-Dialog (Mehrfachauswahl: Haken bleiben beim Ausblenden).
947
+ var dzSucheVorhanden = false;
948
+ var dzSucheLeerBeimOeffnen = false;
949
+ var dzSucheFokus = false;
950
+ var dzSucheNurTreffer = false;
951
+ var dzSucheAndereVersteckt = false;
952
+ var dzSucheAbgehakt = false;
953
+ var dzSucheBilanz = null;
954
+ var dzSucheLeerhinweis = null;
955
+ var dzSucheLeerhinweisWeg = false;
956
+ var dzSucheEnterOffen = false;
957
+ var dzSucheFehler = null;
946
958
  var dzKnopf = Array.from(document.querySelectorAll('#katalog .zeile button'))
947
959
  .find((b) => b.textContent === 'Verlinken');
948
960
  if (dzKnopf) {
949
961
  dzKnopf.click();
950
962
  dzZieleText = document.getElementById('dz-ziele')?.textContent
951
963
  ?.replace(/\\s+/g, ' ').trim() ?? null;
964
+ try {
965
+ var dzSuche = document.getElementById('dz-suche');
966
+ dzSucheVorhanden = dzSuche !== null;
967
+ dzSucheLeerBeimOeffnen = dzSuche?.value === '';
968
+ dzSucheFokus = document.activeElement?.id === 'dz-suche';
969
+ var dzSetze = (wert) => {
970
+ dzSuche.value = wert;
971
+ dzSuche.dispatchEvent(new Event('input', { bubbles: true }));
972
+ };
973
+ var dzZeilen = () => Array.from(document.querySelectorAll('#dz-ziele .wahlzeile'));
974
+ dzSetze('researcher');
975
+ var dzSichtbar = dzZeilen().filter((z) => !z.hidden);
976
+ dzSucheNurTreffer = dzSichtbar.length > 0
977
+ && dzSichtbar.every((z) => (z.textContent || '').indexOf('researcher') > -1);
978
+ dzSucheAndereVersteckt = dzZeilen().some((z) => z.hidden);
979
+ dzSetze('');
980
+ var dzFrei = dzZeilen().find((z) => z.querySelector('input')?.disabled === false
981
+ && (z.textContent || '').indexOf('researcher') < 0);
982
+ var dzFreiBox = dzFrei?.querySelector('input');
983
+ if (dzFreiBox) {
984
+ dzFreiBox.checked = true;
985
+ dzFreiBox.dispatchEvent(new Event('change', { bubbles: true }));
986
+ dzSetze('researcher');
987
+ dzSucheBilanz = document.getElementById('dz-bilanz')?.textContent ?? null;
988
+ dzSucheAbgehakt = dzFrei.hidden === true && dzFreiBox.checked === true;
989
+ dzSetze('');
990
+ dzFreiBox.checked = false;
991
+ dzFreiBox.dispatchEvent(new Event('change', { bubbles: true }));
992
+ }
993
+ dzSetze('zzzkeintreffer');
994
+ dzSucheLeerhinweis = document.querySelector('#dz-ziele .suchhinweis')?.textContent ?? null;
995
+ dzSetze('');
996
+ dzSucheLeerhinweisWeg = document.querySelector('#dz-ziele .suchhinweis') === null
997
+ && dzZeilen().every((z) => !z.hidden);
998
+ dzSuche.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
999
+ await new Promise((r) => setTimeout(r, 100));
1000
+ dzSucheEnterOffen = document.getElementById('d-ziel')?.open === true;
1001
+ } catch (fehler) {
1002
+ dzSucheFehler = String(fehler);
1003
+ }
952
1004
  document.getElementById('d-ziel')?.close();
953
1005
  }
954
1006
  // Bundle-Gruppe an der Claude-Code-Home-Karte (E69, E114): Kopf mit Name, Quelle, Zählung
@@ -1231,10 +1283,58 @@ function fensterOeffnen() {
1231
1283
  var dMcpHinzuOffenNachOeffnen = false;
1232
1284
  var dMcpHinzuOffenNachAbbrechen = true;
1233
1285
  var dmhListeText = null;
1286
+ // E123: Suchfeld im Dialog „MCP hinzufügen".
1287
+ var dmhSucheVorhanden = false;
1288
+ var dmhSucheBlockHidden = null;
1289
+ var dmhSucheLeerBeimOeffnen = false;
1290
+ var dmhSucheFokus = false;
1291
+ var dmhSucheNurTreffer = false;
1292
+ var dmhSucheAndereVersteckt = false;
1293
+ var dmhSucheAbgehakt = false;
1294
+ var dmhSucheBilanz = null;
1295
+ var dmhSucheLeerhinweis = null;
1296
+ var dmhSucheEnterOffen = false;
1234
1297
  try {
1235
1298
  mcpHinzuKnopfAn(hermesKarte)?.click();
1236
1299
  dMcpHinzuOffenNachOeffnen = document.getElementById('d-mcp-hinzufuegen')?.open === true;
1237
1300
  dmhListeText = document.getElementById('dmh-liste')?.textContent?.replace(/\\s+/g, ' ').trim() ?? null;
1301
+ var dmhSuche = document.getElementById('dmh-suche');
1302
+ dmhSucheVorhanden = dmhSuche !== null;
1303
+ dmhSucheBlockHidden = document.getElementById('dmh-suchblock')?.hidden ?? null;
1304
+ dmhSucheLeerBeimOeffnen = dmhSuche?.value === '';
1305
+ dmhSucheFokus = document.activeElement?.id === 'dmh-suche';
1306
+ var dmhSetze = (wert) => {
1307
+ dmhSuche.value = wert;
1308
+ dmhSuche.dispatchEvent(new Event('input', { bubbles: true }));
1309
+ };
1310
+ var dmhZeilen = () => Array.from(document.querySelectorAll('#dmh-liste .wahlzeile'));
1311
+ if (dmhZeilen().length > 0) {
1312
+ dmhSetze('mail');
1313
+ var dmhSichtbar = dmhZeilen().filter((z) => !z.hidden);
1314
+ dmhSucheNurTreffer = dmhSichtbar.length > 0
1315
+ && dmhSichtbar.every((z) => (z.textContent || '').indexOf('mail') > -1);
1316
+ dmhSucheAndereVersteckt = dmhZeilen().some((z) => z.hidden);
1317
+ dmhSetze('');
1318
+ var dmhFrei = dmhZeilen().find((z) => z.querySelector('input')?.disabled === false
1319
+ && (z.textContent || '').indexOf('mail') < 0);
1320
+ var dmhFreiBox = dmhFrei?.querySelector('input');
1321
+ if (dmhFreiBox) {
1322
+ dmhFreiBox.checked = true;
1323
+ dmhFreiBox.dispatchEvent(new Event('change', { bubbles: true }));
1324
+ dmhSetze('mail');
1325
+ dmhSucheBilanz = document.getElementById('dmh-bilanz')?.textContent ?? null;
1326
+ dmhSucheAbgehakt = dmhFrei.hidden === true && dmhFreiBox.checked === true;
1327
+ dmhSetze('');
1328
+ dmhFreiBox.checked = false;
1329
+ dmhFreiBox.dispatchEvent(new Event('change', { bubbles: true }));
1330
+ }
1331
+ dmhSetze('zzzkeintreffer');
1332
+ dmhSucheLeerhinweis = document.querySelector('#dmh-liste .suchhinweis')?.textContent ?? null;
1333
+ dmhSetze('');
1334
+ dmhSuche.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
1335
+ await new Promise((r) => setTimeout(r, 100));
1336
+ dmhSucheEnterOffen = document.getElementById('d-mcp-hinzufuegen')?.open === true;
1337
+ }
1238
1338
  document.querySelector('#d-mcp-hinzufuegen button[value="abbruch"]')?.click();
1239
1339
  dMcpHinzuOffenNachAbbrechen = document.getElementById('d-mcp-hinzufuegen')?.open === true;
1240
1340
  } catch (fehler) {
@@ -1362,6 +1462,17 @@ function fensterOeffnen() {
1362
1462
  dskBundleZuVorher,
1363
1463
  dskBundleKoerperText,
1364
1464
  dzZieleText,
1465
+ dzSucheVorhanden,
1466
+ dzSucheLeerBeimOeffnen,
1467
+ dzSucheFokus,
1468
+ dzSucheNurTreffer,
1469
+ dzSucheAndereVersteckt,
1470
+ dzSucheAbgehakt,
1471
+ dzSucheBilanz,
1472
+ dzSucheLeerhinweis,
1473
+ dzSucheLeerhinweisWeg,
1474
+ dzSucheEnterOffen,
1475
+ dzSucheFehler,
1365
1476
  bundleGruppeText,
1366
1477
  bundleGruppeZeilen,
1367
1478
  bundleNichtVerlinktText,
@@ -1460,6 +1571,16 @@ function fensterOeffnen() {
1460
1571
  dMcpHinzuOffenNachOeffnen,
1461
1572
  dMcpHinzuOffenNachAbbrechen,
1462
1573
  dmhListeText,
1574
+ dmhSucheVorhanden,
1575
+ dmhSucheBlockHidden,
1576
+ dmhSucheLeerBeimOeffnen,
1577
+ dmhSucheFokus,
1578
+ dmhSucheNurTreffer,
1579
+ dmhSucheAndereVersteckt,
1580
+ dmhSucheAbgehakt,
1581
+ dmhSucheBilanz,
1582
+ dmhSucheLeerhinweis,
1583
+ dmhSucheEnterOffen,
1463
1584
  dmhFehler,
1464
1585
  selbstupdateVorhanden,
1465
1586
  selbstupdateVerborgen,
@@ -214,8 +214,15 @@
214
214
  <h3>Wohin soll <span id="dz-skill">der Skill</span>?</h3>
215
215
  <p>Kreuz jedes Ziel an, in dem der Skill auftauchen soll.</p>
216
216
  </div>
217
- <div class="dkoerper"><div class="wahl" id="dz-ziele"></div></div>
217
+ <div class="dkoerper">
218
+ <div class="suche">
219
+ <svg viewBox="0 0 16 16" stroke-width="1.6" aria-hidden="true"><circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5 14.5 14.5"/></svg>
220
+ <input type="search" id="dz-suche" placeholder="Ziel oder Pfad suchen" aria-label="Ziele durchsuchen" spellcheck="false" autocomplete="off">
221
+ </div>
222
+ <div class="wahl" id="dz-ziele"></div>
223
+ </div>
218
224
  <div class="dfuss">
225
+ <span class="links mono" id="dz-bilanz">0 ausgewählt</span>
219
226
  <button class="knopf leise" value="abbruch">Abbrechen</button>
220
227
  <button class="knopf signal" value="los" id="dz-los">Verlinken</button>
221
228
  </div>
@@ -595,6 +602,10 @@
595
602
  <p>Kreuz jeden Server an, der hier eingehängt werden soll. Schon Eingehängtes ist ausgegraut.</p>
596
603
  </div>
597
604
  <div class="dkoerper">
605
+ <div class="suche" id="dmh-suchblock">
606
+ <svg viewBox="0 0 16 16" stroke-width="1.6" aria-hidden="true"><circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5 14.5 14.5"/></svg>
607
+ <input type="search" id="dmh-suche" placeholder="Server suchen" aria-label="Server durchsuchen" spellcheck="false" autocomplete="off">
608
+ </div>
598
609
  <div class="wahl" id="dmh-liste"></div>
599
610
  </div>
600
611
  <div class="dfuss">
@@ -2447,6 +2447,7 @@
2447
2447
  $('dz-skill').textContent = skill.name;
2448
2448
  var box = $('dz-ziele');
2449
2449
  box.textContent = '';
2450
+ $('dz-suche').value = '';
2450
2451
  // E76: Ziele inaktiver Harnesses stehen gar nicht erst zur Wahl — der Kern lehnte den Link
2451
2452
  // ohnehin ab, und seit E74 zeigt keine Ansicht sie. Dieselbe Regel wie die Zielliste.
2452
2453
  var waehlbar = ZIELE.filter(zielSichtbar);
@@ -2460,10 +2461,12 @@
2460
2461
  return n === skill.name;
2461
2462
  });
2462
2463
  var label = e('label', 'wahlzeile');
2464
+ label.dataset.index = String(ZIELE.indexOf(z));
2463
2465
  var input = document.createElement('input');
2464
2466
  input.type = 'checkbox';
2465
2467
  input.value = String(ZIELE.indexOf(z));
2466
2468
  input.disabled = schon;
2469
+ input.addEventListener('change', dzBilanzAktualisieren);
2467
2470
  label.appendChild(input);
2468
2471
  var txt = e('span');
2469
2472
  txt.appendChild(beschriftet(e('b', null, zielTitel(z)), z.ziel.harness));
@@ -2472,9 +2475,42 @@
2472
2475
  label.appendChild(txt);
2473
2476
  box.appendChild(label);
2474
2477
  });
2478
+ dzBilanzAktualisieren();
2475
2479
  zeige($('d-ziel'));
2480
+ $('dz-suche').focus();
2476
2481
  }
2477
2482
 
2483
+ function dzBilanzAktualisieren() {
2484
+ var n = $('dz-ziele').querySelectorAll('input:checked').length;
2485
+ $('dz-bilanz').textContent = zahlwort(n, 'ausgewählt', 'ausgewählt');
2486
+ }
2487
+
2488
+ /** E123: filtert die Wahlzeilen des Verlinken-Dialogs live. Mehrfachauswahl: angehakte Zeilen
2489
+ bleiben auch ausgeblendet angehakt, damit man über mehrere Suchen hinweg sammeln kann. */
2490
+ function filtereDzZiele() {
2491
+ var begriff = $('dz-suche').value.trim();
2492
+ var suche = suchform(begriff);
2493
+ var box = $('dz-ziele');
2494
+ var gezeigt = 0;
2495
+ box.querySelectorAll('.wahlzeile').forEach(function (label) {
2496
+ var z = ZIELE[+label.dataset.index];
2497
+ var passt = true;
2498
+ if (suche) {
2499
+ var text = suchform(zielTitel(z) + ' ' + z.verzeichnis + ' ' + pfadKurz(z.verzeichnis));
2500
+ passt = text.indexOf(suche) > -1;
2501
+ }
2502
+ label.hidden = !passt;
2503
+ if (passt) gezeigt++;
2504
+ });
2505
+ var alt = box.querySelector('.suchhinweis');
2506
+ if (alt) alt.remove();
2507
+ if (suche && gezeigt === 0) {
2508
+ box.appendChild(e('p', 'leerhinweis suchhinweis', 'Kein Ziel passt zu „' + begriff + '".'));
2509
+ }
2510
+ }
2511
+ $('dz-suche').addEventListener('input', filtereDzZiele);
2512
+ enterBestaetigt($('dz-suche'), $('dz-los'));
2513
+
2478
2514
  $('d-ziel').addEventListener('submit', function (ev) {
2479
2515
  if (ev.submitter && ev.submitter.value === 'los') {
2480
2516
  var gewaehlt = Array.from($('dz-ziele').querySelectorAll('input:checked'));
@@ -3870,6 +3906,8 @@
3870
3906
  $('dmh-ziel').textContent = zielTitel({ ziel: ziel });
3871
3907
  var box = $('dmh-liste');
3872
3908
  box.textContent = '';
3909
+ $('dmh-suche').value = '';
3910
+ $('dmh-suchblock').hidden = MCP_KATALOG.length === 0;
3873
3911
  // Ohne Katalog stünde hier ein leerer Kasten mit einem Knopf, der nichts tut: hmgr legt
3874
3912
  // MCP-Einträge nie selbst an (E61), also nennt der Dialog denselben Weg wie die MCP-Ansicht.
3875
3913
  if (MCP_KATALOG.length === 0) {
@@ -3885,6 +3923,7 @@
3885
3923
  var i = p.i;
3886
3924
  var schon = k.eingehaengt.some(function (z) { return zielKey(z) === zielKey(ziel); });
3887
3925
  var label = e('label', 'wahlzeile');
3926
+ label.dataset.index = String(i);
3888
3927
  var input = document.createElement('input');
3889
3928
  input.type = 'checkbox';
3890
3929
  input.value = String(i);
@@ -3900,7 +3939,33 @@
3900
3939
  });
3901
3940
  dmhBilanzAktualisieren();
3902
3941
  zeige($('d-mcp-hinzufuegen'));
3942
+ if (!$('dmh-suchblock').hidden) $('dmh-suche').focus();
3943
+ }
3944
+
3945
+ /** E123: filtert die Server im Dialog „MCP hinzufügen" nach Name und Beschreibung. Angehakte
3946
+ Zeilen bleiben auch ausgeblendet angehakt (Mehrfachauswahl). */
3947
+ function filtereDmhListe() {
3948
+ var begriff = $('dmh-suche').value.trim();
3949
+ var suche = suchform(begriff);
3950
+ var box = $('dmh-liste');
3951
+ var gezeigt = 0;
3952
+ box.querySelectorAll('.wahlzeile').forEach(function (label) {
3953
+ var k = MCP_KATALOG[+label.dataset.index];
3954
+ var passt = true;
3955
+ if (suche) {
3956
+ passt = suchform(k.eintrag.name + ' ' + (k.eintrag.beschreibung || '')).indexOf(suche) > -1;
3957
+ }
3958
+ label.hidden = !passt;
3959
+ if (passt) gezeigt++;
3960
+ });
3961
+ var alt = box.querySelector('.suchhinweis');
3962
+ if (alt) alt.remove();
3963
+ if (suche && gezeigt === 0) {
3964
+ box.appendChild(e('p', 'leerhinweis suchhinweis', 'Kein Server passt zu „' + begriff + '".'));
3965
+ }
3903
3966
  }
3967
+ $('dmh-suche').addEventListener('input', filtereDmhListe);
3968
+ enterBestaetigt($('dmh-suche'), $('dmh-los'));
3904
3969
 
3905
3970
  $('d-mcp-hinzufuegen').addEventListener('submit', function (ev) {
3906
3971
  if (ev.submitter && ev.submitter.value === 'los') {
@@ -920,7 +920,8 @@ export class Anwendungsfaelle {
920
920
  const zustand = await this.p.zustand.lesen();
921
921
  const ergebnis = [];
922
922
  for (const quelle of zustand.quellen) {
923
- const gelesen = await this.marketplaceLesen(quelle);
923
+ // Eine unlesbare marketplace.json einer Quelle darf nicht die ganze Liste kippen (E124).
924
+ const gelesen = await this.marketplaceLesen(quelle).catch(() => null);
924
925
  if (gelesen === null)
925
926
  continue;
926
927
  ergebnis.push({ quelle, name: gelesen.name, plugins: gelesen.plugins });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hmgr",
3
- "version": "0.0.54",
3
+ "version": "0.0.56",
4
4
  "description": "Harness Manager: Skills und Plugins für AI-Harnesses verwalten",
5
5
  "license": "MIT",
6
6
  "author": "Tim Kaufmann",