hmgr 0.0.44 → 0.0.46

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.
@@ -18,7 +18,8 @@ function deuten(roh) {
18
18
  aktiveHarnesses: (daten.aktiveHarnesses ?? ['claude-code']),
19
19
  // Fehlt das Feld (state.json vor E43), gilt: nichts ignoriert.
20
20
  ignorierteEintraege: (daten.ignorierteEintraege ?? []),
21
- // Fehlt das Feld (state.json vor E69), gilt: kein Bundle installiert.
21
+ // Fehlt das Feld (state.json vor E69), gilt: kein Bundle installiert. Ein Eintrag ohne
22
+ // `herausgenommen` (vor E114) hat keine herausgenommenen Mitglieder, das Feld bleibt dann weg.
22
23
  bundleInstallationen: (daten.bundleInstallationen ?? []),
23
24
  // Fehlt das Feld (state.json vor E78), gilt: jeder Harness hat nur sein Standard-Homedir.
24
25
  harnessHeime: (daten.harnessHeime ?? []),
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "fassung": 1,
3
3
  "versionen": {
4
+ "0.0.46": [
5
+ "Bundles lassen sich aufklappen und zeigen ihre Skills mit Beschreibung, auch schon im Dialog „Skills hinzufügen“. Entfernst du einen einzelnen Skill, bleibt das Bundle bestehen und zeigt, was fehlt; mit „Vervollständigen“ oder „Wieder verlinken“ holst du Skills zurück, und „Entfernen“ am Bundle räumt den Rest weg."
6
+ ],
7
+ "0.0.45": [
8
+ "MCP-Server und die Listen in den Einstellungen (Harnesses, zusätzliche Homedirs) erscheinen jetzt alphabetisch sortiert, ebenso in hmgr mcp list."
9
+ ],
4
10
  "0.0.44": [
5
11
  "Läuft gerade eine Aktualisierung, zeigt hmgr ein im Terminal hinzugefügtes Verzeichnis trotzdem sofort an, statt bis zum Ende der Aktualisierung zu warten."
6
12
  ],
package/dist/cli/hilfe.js CHANGED
@@ -315,9 +315,13 @@ Liegt am Ziel schon derselbe Skill aus derselben Quelle als hmgr-Link, wird er a
315
315
  übernommen: aus Bestand wird ein Bundle, ohne erst Links von Hand zu entfernen. Alles andere unter
316
316
  demselben Namen ist ein Konflikt und wird genannt.
317
317
 
318
- hmgr unlink <skill> auf ein einzelnes Mitglied zerbricht das Bundle in diesem Ziel: die übrigen
319
- Links bleiben als einzelne Links liegen, der Bundle-Verweis fällt weg. Das ist kein Fehler, hmgr
320
- sagt es nur.
318
+ hmgr unlink <skill> auf ein einzelnes Mitglied nimmt es aus dem Bundle heraus: nur dieser Link
319
+ verschwindet, das Bundle bleibt im Ziel und merkt sich den Skill als herausgenommen. sync und
320
+ update legen ihn nicht wieder an, neue Mitglieder der Definition ergänzen sie weiter. Zurück kommt
321
+ er mit hmgr link <skill> desselben Skills aus derselben Quelle oder mit bundle sync --complete, das
322
+ alle fehlenden Mitglieder verlinkt. remove nimmt die noch verlinkten Mitglieder weg, auch wenn
323
+ schon welche herausgenommen sind. hmgr status und bundle list zeigen die Lage je Ziel, etwa
324
+ „4/5 … fehlt: x".
321
325
 
322
326
  Den Abgleich macht schon hmgr update für alle installierten Bundles mit, es ist derselbe Vorgang
323
327
  und dieselbe Ausgabe (E69): eine geänderte Definition kommt also mit dem gewöhnlichen
@@ -335,11 +339,14 @@ Schalter (add, remove und sync):
335
339
  kennt nur hermes, wsl:<distro> nur claude-code unter Windows (E78)
336
340
  --composer <pfad> composer.json des Arbeitsverzeichnisses, wenn mehrere in Frage kommen
337
341
  --quelle <teil> nur bei add: Quelle eingrenzen, wenn den Namen mehrere Quellen führen
342
+ --complete nur bei sync: auch herausgenommene Mitglieder verlinken (Vervollständigen),
343
+ die Liste der herausgenommenen ist danach leer. Auch --vervollstaendigen
338
344
 
339
345
  Beispiele:
340
346
  hmgr bundle list
341
347
  hmgr bundle add wordpress --ort dir:.
342
348
  hmgr bundle sync
349
+ hmgr bundle sync wordpress --ort dir:. --complete
343
350
  hmgr bundle remove wordpress --ort dir:.
344
351
 
345
352
  Häufige Fälle:
package/dist/cli/main.js CHANGED
@@ -125,7 +125,7 @@ const SCHALTER = {
125
125
  unlink: { werte: ['harness', 'ort', 'composer'], flaggen: ['local', 'lokal'] },
126
126
  adopt: { werte: ['harness', 'ort', 'composer'], flaggen: ['local', 'lokal', 'probelauf'] },
127
127
  plugin: { werte: ['harness', 'ort', 'composer', 'quelle'], flaggen: ['local', 'lokal'] },
128
- bundle: { werte: ['harness', 'ort', 'composer', 'quelle'], flaggen: [] },
128
+ bundle: { werte: ['harness', 'ort', 'composer', 'quelle'], flaggen: ['complete', 'vervollstaendigen'] },
129
129
  mcp: { werte: ['harness', 'ort'], flaggen: [] },
130
130
  sync: { werte: [], flaggen: [] },
131
131
  update: { werte: ['quelle'], flaggen: [] },
@@ -160,7 +160,8 @@ const UNTERKOMMANDOS = {
160
160
  add: { woerter: 2, schalter: ['harness', 'ort', 'composer', 'quelle'] },
161
161
  remove: { woerter: 2, schalter: ['harness', 'ort', 'composer'] },
162
162
  // sync nimmt einen Bundle-Namen, aber keinen zwingend: ohne Namen gilt der ganze Bestand.
163
- sync: { woerter: 2, schalter: ['harness', 'ort', 'composer'] },
163
+ // --complete/--vervollstaendigen holt herausgenommene Mitglieder zurück (E114 L4).
164
+ sync: { woerter: 2, schalter: ['harness', 'ort', 'composer', 'complete', 'vervollstaendigen'] },
164
165
  list: { woerter: 1, schalter: [] },
165
166
  },
166
167
  mcp: {
@@ -491,9 +492,10 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
491
492
  if (kommando === 'unlink') {
492
493
  const ergebnis = await anwendungsfaelle.linkEntfernen({ skill, ziel, lokal });
493
494
  console.log(`Link ${skill} entfernt.`);
494
- // Zerbrochen (E69): die übrigen Links des Bundles bleiben als Einzel-Installationen liegen.
495
- for (const bundle of ergebnis.zerbrochen)
496
- console.log(`Bundle „${bundle}" ist damit zerbrochen, seine übrigen Links bleiben als einzelne Links liegen.`);
495
+ // Herausgenommen (E114 L2): das Bundle bleibt, der Abgleich legt den Skill nicht wieder an.
496
+ for (const bundle of ergebnis.herausgenommenAus)
497
+ console.log(`„${skill}" ist aus dem Bundle „${bundle}" herausgenommen, das Bundle bleibt. ` +
498
+ `Zurück mit hmgr link ${skill} oder hmgr bundle sync ${bundle} --complete.`);
497
499
  if (ergebnis.hinweis !== undefined)
498
500
  console.log(ergebnis.hinweis);
499
501
  return 0;
@@ -612,10 +614,15 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
612
614
  if (kommando === 'bundle') {
613
615
  const schalter = schalterLesen(argv.slice(1), 'bundle');
614
616
  unterkommandoPruefen('bundle', schalter);
615
- const { rest, werte } = schalter;
617
+ const { rest, werte, flaggen } = schalter;
616
618
  const unterkommando = rest[0];
617
619
  if (unterkommando === 'list') {
618
620
  const katalog = await anwendungsfaelle.bundleKatalog();
621
+ // Je Bundle die Ziele, in denen es liegt, mit seiner Lage (E114 L6). Nur nötig, wenn
622
+ // überhaupt ein Bundle installiert ist; der Ist-Zustand kostet sonst einen Scan aller Ziele.
623
+ const installiert = (await ports.zustand.lesen()).bundleInstallationen.length > 0
624
+ ? (await anwendungsfaelle.istZustand({ plugins: false })).flatMap((z) => z.bundles.map((b) => ({ ziel: z.ziel, bundle: b })))
625
+ : [];
619
626
  const leer = katalog.every((k) => k.bundles.length === 0 && k.kaputte.length === 0 && k.fehler === undefined);
620
627
  if (leer)
621
628
  console.log('Keine Quelle mit Bundles. hmgr help bundle zeigt das Format von bundles/<name>.yaml.');
@@ -631,6 +638,9 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
631
638
  console.log(` ${bundle.name} ${kappen(bundle.beschreibung)}`);
632
639
  for (const mitglied of bundle.mitglieder)
633
640
  console.log(` ${mitglied.pfad} aus ${quelleAlsText(mitglied.quelle)}`);
641
+ for (const { ziel, bundle: lage } of installiert)
642
+ if (lage.name === bundle.name && quelleSchluessel(lage.quelle) === quelleSchluessel(bundle.quelle))
643
+ console.log(` installiert ${ziel.harness} ${beschreibeOrt(ziel.ort)} ${bundleLage(lage)}`);
634
644
  }
635
645
  // Eine kaputte Definition verdeckt die übrigen nicht, sie wird eigens genannt (E69).
636
646
  for (const kaputt of eintrag.kaputte)
@@ -683,9 +693,11 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
683
693
  }
684
694
  // sync: ohne Ziel-Schalter der ganze Bestand, mit --ort/--harness nur dieses Ziel.
685
695
  const nurZiel = werte.has('harness') || werte.has('ort');
696
+ const vervollstaendigen = flaggen.has('complete') || flaggen.has('vervollstaendigen');
686
697
  const eintraege = await anwendungsfaelle.bundleAbgleich({
687
698
  ...(name === undefined ? {} : { bundle: name }),
688
699
  ...(nurZiel ? { ziel } : {}),
700
+ ...(vervollstaendigen ? { aufnehmen: 'alle' } : {}),
689
701
  });
690
702
  if (eintraege.length === 0)
691
703
  console.log('Kein Bundle installiert, nichts abzugleichen.');
@@ -702,7 +714,10 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
702
714
  const uebersicht = await anwendungsfaelle.mcpUebersicht();
703
715
  if (uebersicht.katalog.length === 0)
704
716
  console.log('Kein MCP im Katalog. hmgr help mcp zeigt das Format von ~/.hmgr/mcp.yaml.');
705
- for (const { eintrag, eingehaengt, geteilteFundorte } of uebersicht.katalog) {
717
+ // E113: alphabetisch nach Name (Kopie, die Lieferreihenfolge der Daten bleibt).
718
+ const nachName = (a, b) => a.localeCompare(b, 'de', { sensitivity: 'base' });
719
+ const katalogSortiert = [...uebersicht.katalog].sort((a, b) => nachName(a.eintrag.name, b.eintrag.name));
720
+ for (const { eintrag, eingehaengt, geteilteFundorte } of katalogSortiert) {
706
721
  console.log(`${eintrag.name} ${eintrag.beschreibung}`);
707
722
  if (eingehaengt.length === 0 && geteilteFundorte.length === 0)
708
723
  console.log(' nirgends eingehängt');
@@ -714,9 +729,9 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
714
729
  }
715
730
  if (uebersicht.unbekannt.length > 0 || uebersicht.unbekannteGeteilteFundorte.length > 0) {
716
731
  console.log('Nicht im Katalog, aber eingehängt (hmgr fasst sie nicht an):');
717
- for (const { ziel, name } of uebersicht.unbekannt)
732
+ for (const { ziel, name } of [...uebersicht.unbekannt].sort((a, b) => nachName(a.name, b.name)))
718
733
  console.log(` ${name} ${ziel.harness} ${beschreibeOrt(ziel.ort)}`);
719
- for (const f of uebersicht.unbekannteGeteilteFundorte)
734
+ for (const f of [...uebersicht.unbekannteGeteilteFundorte].sort((a, b) => nachName(a.name, b.name)))
720
735
  console.log(` ${f.name} ${f.ziel.harness} ${beschreibeOrt(f.ziel.ort)} ${f.datei} · Scope ${f.scope}`);
721
736
  }
722
737
  return 0;
@@ -885,7 +900,7 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
885
900
  }
886
901
  // Bundles des Ziels (E69), unter den Einträgen: welche Links als Einheit zusammengehören.
887
902
  for (const bundle of zielZustand.bundles)
888
- console.log(` Bundle ${bundle.name} (${quelleAlsText(bundle.quelle)}) ${bundle.skills.join(', ')}`);
903
+ console.log(` Bundle ${bundle.name} (${quelleAlsText(bundle.quelle)}) ${bundleLage(bundle)}`);
889
904
  if (zielZustand.plugins !== undefined)
890
905
  console.log(` Plugins: ${zielZustand.plugins.length === 0 ? 'keine' : zielZustand.plugins.join(', ')}`);
891
906
  }
@@ -1131,6 +1146,16 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
1131
1146
  }
1132
1147
  throw new Error(`Unbekanntes Kommando „${kommando}". hmgr --help zeigt alle Kommandos.`);
1133
1148
  }
1149
+ /**
1150
+ * Lage eines installierten Bundles in einem Ziel (E114 L6), für `status` und `bundle list`:
1151
+ * verlinkt von gesamt, die verlinkten Namen und, wenn etwas fehlt, was.
1152
+ */
1153
+ function bundleLage(bundle) {
1154
+ const gesamt = bundle.skills.length + bundle.nichtVerlinkt.length;
1155
+ const verlinkt = bundle.skills.length === 0 ? '' : ` ${bundle.skills.join(', ')}`;
1156
+ const fehlt = bundle.nichtVerlinkt.length === 0 ? '' : ` fehlt: ${bundle.nichtVerlinkt.join(', ')}`;
1157
+ return `${bundle.skills.length}/${gesamt}${verlinkt}${fehlt}`;
1158
+ }
1134
1159
  /**
1135
1160
  * Bericht über den Bundle-Abgleich (E69), gleich für `hmgr bundle sync` und `hmgr update`: je
1136
1161
  * Bundle eine Zeile mit seinem Ausgang, darunter, was sich geändert hat. „fehlgeschlagen" heißt
package/dist/gui/main.js CHANGED
@@ -174,6 +174,9 @@ function zielZustandSerialisieren(z) {
174
174
  bundles: z.bundles.map((b) => ({
175
175
  name: b.name,
176
176
  skills: b.skills,
177
+ // E114 L3: nicht verlinkte Mitglieder (herausgenommen oder von Hand gelöscht).
178
+ nichtVerlinkt: b.nichtVerlinkt,
179
+ herausgenommen: b.herausgenommen,
177
180
  quelleText: quelleAlsText(b.quelle),
178
181
  quelleName: quelleName(b.quelle),
179
182
  })),
@@ -234,8 +237,10 @@ function registriereIpc() {
234
237
  return ergebnis;
235
238
  }));
236
239
  // Bundles aller bekannten Quellen (E69): flach wie der Skill-Katalog, je Bundle seine Quelle und
237
- // die Zahl seiner Mitglieder. Kaputte Definitionen kommen als eigene Liste mit, damit die GUI sie
238
- // nennen kann, statt sie zu verschlucken.
240
+ // seine Mitglieder als Quelle plus Pfad (E114 L1). Name und Beschreibung eines Mitglieds sucht der
241
+ // Renderer im Skill-Katalog nach (`quelleSchluessel` + `pfad`): eine noch nicht eingerichtete
242
+ // Quelle wird hier nicht gelesen, sonst klonte jedes Öffnen des Dialogs. Kaputte Definitionen
243
+ // kommen als eigene Liste mit, damit die GUI sie nennen kann, statt sie zu verschlucken.
239
244
  ipcMain.handle('hmgr:bundles', async () => auffangen(async () => {
240
245
  const bundles = [];
241
246
  const kaputte = [];
@@ -244,7 +249,11 @@ function registriereIpc() {
244
249
  bundles.push({
245
250
  name: bundle.name,
246
251
  beschreibung: bundle.beschreibung,
247
- mitglieder: bundle.mitglieder.length,
252
+ mitglieder: bundle.mitglieder.map((m) => ({
253
+ quelleSchluessel: quelleSchluessel(m.quelle),
254
+ quelleText: quelleAlsText(m.quelle),
255
+ pfad: m.pfad,
256
+ })),
248
257
  // Anzeige im Dialog: aus welchen Quellen das Bundle Skills holt, ohne Dubletten.
249
258
  quellen: [...new Set(bundle.mitglieder.map((m) => quelleAlsText(m.quelle)))],
250
259
  quelleText: quelleAlsText(bundle.quelle),
@@ -272,6 +281,24 @@ function registriereIpc() {
272
281
  };
273
282
  }));
274
283
  ipcMain.handle('hmgr:bundleEntfernen', async (_event, args) => auffangen(async () => anwendungsfaelle.bundleEntfernen({ bundle: args.bundle, ziel: zielAusIpc(args.harness, args.ort) })));
284
+ // Vervollständigen (E114 L4, `aufnehmen: 'alle'`) und „Wieder verlinken" einzelner Mitglieder (L3,
285
+ // Namensliste): der Abgleich dieses einen Bundles in diesem Ziel. Wie bundleEntfernen ohne Zeiger.
286
+ // Der Abgleich meldet einen Fehlschlag im Ergebnis statt zu werfen; hier wird er zum Fehler, damit
287
+ // `rufe` im Renderer ihn zeigt.
288
+ ipcMain.handle('hmgr:bundleAbgleichen', async (_event, args) => auffangen(async () => {
289
+ const eintraege = await anwendungsfaelle.bundleAbgleich({
290
+ bundle: args.bundle,
291
+ quelle: quelleAusText(args.quelleText),
292
+ ziel: zielAusIpc(args.harness, args.ort),
293
+ aufnehmen: args.aufnehmen,
294
+ });
295
+ const eintrag = eintraege[0];
296
+ if (eintrag === undefined)
297
+ throw new Error(`Bundle „${args.bundle}" ist dort nicht installiert.`);
298
+ if (eintrag.ergebnis === 'fehlgeschlagen')
299
+ throw new Error(`Bundle „${args.bundle}" ließ sich nicht vervollständigen: ${eintrag.fehler ?? ''}`);
300
+ return { gelegt: eintrag.gelegt };
301
+ }));
275
302
  // Nur die Herkunft, ohne die Quellen zu lesen: der Katalog daneben tut das schon.
276
303
  ipcMain.handle('hmgr:quellen', async () => auffangen(async () => (await anwendungsfaelle.quellen()).map((quelle) => ({
277
304
  art: quelle.art,
@@ -858,6 +885,7 @@ function fensterOeffnen() {
858
885
  // fällt, was ins Ziel kommt, und zeigt je Bundle Beschreibung und Herkunft.
859
886
  var dskBundleBoxSichtbar = false;
860
887
  var dskBundleText = null;
888
+ var dskBundleZuVorher = null, dskBundleKoerperText = null;
861
889
  // Aufklappbereich je Zeile (E92): zu vor dem Klick, danach sichtbar mit der ungekürzten
862
890
  // Beschreibung; die Checkbox bleibt unberührt, der Dialog offen.
863
891
  var dskKoerperVorher = null;
@@ -895,6 +923,13 @@ function fensterOeffnen() {
895
923
  dskBundleBoxSichtbar = document.getElementById('dsk-bundlebox')?.hidden === false;
896
924
  dskBundleText = document.querySelector('#dsk-bundles .wahlzeile')?.textContent
897
925
  ?.replace(/\\s+/g, ' ').trim() ?? null;
926
+ // E114 L1: die Bundle-Wahlzeile klappt auf und zeigt ihre Skills samt Beschreibung.
927
+ var dskBundleKoerper = document.querySelector('#dsk-bundles .wahlzeile .wahlkoerper');
928
+ dskBundleZuVorher = dskBundleKoerper?.hidden ?? null;
929
+ document.querySelector('#dsk-bundles .wahlzeile .aufklappknopf')?.click();
930
+ dskBundleKoerperText = dskBundleKoerper && !dskBundleKoerper.hidden
931
+ ? dskBundleKoerper.textContent?.replace(/\\s+/g, ' ').trim() ?? null
932
+ : null;
898
933
  document.getElementById('d-skills')?.close();
899
934
  }
900
935
  // Verlinken-Dialog „Wohin?" (E76): er bietet nur Ziele aktiver Harnesses an. Der Text
@@ -908,20 +943,33 @@ function fensterOeffnen() {
908
943
  ?.replace(/\\s+/g, ' ').trim() ?? null;
909
944
  document.getElementById('d-ziel')?.close();
910
945
  }
911
- // Bundle-Gruppe an der Ziel-Karte (E69): Kopf mit Name, Quelle und Entfernen, darunter
912
- // die Zeilen der Mitglieder — sie stehen in der Gruppe, nicht in der allgemeinen Liste.
913
- var bundleGruppeText = document.querySelector('#zielliste .bundlegruppe .bundlekopf')?.textContent
946
+ // Bundle-Gruppe an der Claude-Code-Home-Karte (E69, E114): Kopf mit Name, Quelle, Zählung
947
+ // „1 von 2 Skills", „Vervollständigen" und „Entfernen"; darunter zugeklappt die Zeilen
948
+ // aller Mitglieder. „skill-creator" hat die Fixture herausgenommen, es steht als blasse
949
+ // Zeile „nicht verlinkt" mit „Wieder verlinken" in der Gruppe.
950
+ var homeGruppe = Array.from(document.querySelectorAll('#zielliste .ziel'))
951
+ .find((k) => (k.querySelector('.zielkopf')?.textContent || '').indexOf('Claude Code · Home') > -1)
952
+ ?.querySelector('.bundlegruppe') ?? null;
953
+ var bundleGruppeText = homeGruppe?.querySelector('.bundlekopf')?.textContent
914
954
  ?.replace(/\\s+/g, ' ').trim() ?? null;
915
- var bundleGruppeZeilen = Array.from(document.querySelectorAll('#zielliste .bundlegruppe .linkliste .name'))
955
+ var bundleGruppeZeilen = Array.from(homeGruppe?.querySelectorAll('.linkliste li:not(.nichtverlinkt) .name') ?? [])
916
956
  .map((n) => n.textContent.trim()).join(',');
917
- // Entfernen an der Einzelzeile innerhalb der Gruppe sagt, dass das Bundle zerbricht.
918
- var bundleZerbrichtText = null;
957
+ var bundleNichtVerlinktText = homeGruppe?.querySelector('.linkliste li.nichtverlinkt')?.textContent
958
+ ?.replace(/\\s+/g, ' ').trim() ?? null;
959
+ var bundleVervollstaendigenKnopf = Array.from(homeGruppe?.querySelectorAll('.bundlekopf button') ?? [])
960
+ .some((k) => k.textContent === 'Vervollständigen');
961
+ var bundleKoerperZuVorher = homeGruppe?.querySelector('.linkliste')?.hidden ?? null;
962
+ homeGruppe?.querySelector('.bundlekopf .aufklappknopf')?.click();
963
+ var bundleKoerperAufNachKlick = homeGruppe?.querySelector('.linkliste')?.hidden === false;
964
+ // Entfernen an der Mitgliedszeile sagt, dass der Skill herausgenommen wird und das
965
+ // Bundle bleibt (E114 L2, früher: zerbricht).
966
+ var bundleHerausnehmenText = null;
919
967
  try {
920
- document.querySelector('#zielliste .bundlegruppe .linkliste .knopf')?.click();
921
- bundleZerbrichtText = document.getElementById('dx-text')?.textContent ?? null;
968
+ homeGruppe?.querySelector('.linkliste li:not(.nichtverlinkt) .knopf')?.click();
969
+ bundleHerausnehmenText = document.getElementById('dx-text')?.textContent ?? null;
922
970
  document.querySelector('#d-entfernen button[value="nein"]')?.click();
923
971
  } catch (fehler) {
924
- bundleZerbrichtText = String(fehler);
972
+ bundleHerausnehmenText = String(fehler);
925
973
  }
926
974
  // Sammelstreifen unter „Alle" (E95): das Bundle „vorhaben" liegt an kunde-x in Claude Code
927
975
  // und Hermes — ein Streifen mit beiden Harness-Zeichen, Entfernen nennt beide Harnesses
@@ -932,12 +980,21 @@ function fensterOeffnen() {
932
980
  var streifenZahl = null, streifenHarnesse = null, streifenText = null;
933
981
  var streifenDialogTitel = null, streifenDialogText = null;
934
982
  var chipGruppeZahl = null, chipGruppeZeilenZahl = null, chipStreifenZahl = null;
983
+ // E114 L1: unter „Alle" stehen die Mitglieder in der Gruppe und nicht zusätzlich in der
984
+ // Tabelle. Name ohne Harness-Zeichen: nur die Textknoten der Namenszelle.
985
+ var nurName = (n) => Array.from(n.childNodes).filter((t) => t.nodeType === 3).map((t) => t.textContent).join('').trim();
986
+ var alleGruppeZeilen = null, alleTabelleZeilen = null;
935
987
  try {
936
988
  var streifen = Array.from(streifenKarte?.querySelectorAll('.bundlegruppe') ?? []);
937
989
  streifenZahl = streifen.length;
938
990
  streifenHarnesse = streifen[0]?.querySelector('.zeilenzeichen')?.getAttribute('aria-label') ?? null;
939
- streifenText = streifen[0]?.textContent?.replace(/\\s+/g, ' ').trim() ?? null;
940
- streifen[0]?.querySelector('button')?.click();
991
+ streifenText = streifen[0]?.querySelector('.bundlekopf')?.textContent?.replace(/\\s+/g, ' ').trim() ?? null;
992
+ alleGruppeZeilen = Array.from(streifen[0]?.querySelectorAll('.linkliste li .name') ?? []).map(nurName).join(',');
993
+ alleTabelleZeilen = Array.from(streifenKarte?.querySelectorAll('.linkliste li') ?? [])
994
+ .filter((li) => li.closest('.bundlegruppe') === null && li.querySelector('.name'))
995
+ .map((li) => nurName(li.querySelector('.name'))).join(',');
996
+ Array.from(streifen[0]?.querySelectorAll('.bundlekopf button') ?? [])
997
+ .find((k) => k.textContent === 'Entfernen')?.click();
941
998
  streifenDialogTitel = document.getElementById('dx-titel')?.textContent ?? null;
942
999
  streifenDialogText = document.getElementById('dx-text')?.textContent ?? null;
943
1000
  document.querySelector('#d-entfernen button[value="nein"]')?.click();
@@ -962,6 +1019,8 @@ function fensterOeffnen() {
962
1019
  document.querySelector('[data-ansicht="mcp"]')?.click();
963
1020
  var mcpKartenText = document.getElementById('mcpliste')?.textContent
964
1021
  ?.replace(/\\s+/g, ' ').trim() ?? null;
1022
+ var mcpKartenNamen = Array.from(document.querySelectorAll('#mcpliste .mcpkopf > b'))
1023
+ .map((b) => b.textContent).join(',');
965
1024
  var mcpHeimLinkVorhanden = document.querySelector('#mcpliste .mcpkopf .qlink') !== null;
966
1025
  var mcpWarnzeileVorhanden = document.querySelector('#mcpliste .mcp .ruhezeile') !== null;
967
1026
  // F46: die Orte-Liste je Karte und der Fremdkasten der Ansicht sind ganz weg, nicht nur
@@ -1234,13 +1293,21 @@ function fensterOeffnen() {
1234
1293
  dskAufklappFreiAngehakt,
1235
1294
  dskBundleBoxSichtbar,
1236
1295
  dskBundleText,
1296
+ dskBundleZuVorher,
1297
+ dskBundleKoerperText,
1237
1298
  dzZieleText,
1238
1299
  bundleGruppeText,
1239
1300
  bundleGruppeZeilen,
1240
- bundleZerbrichtText,
1301
+ bundleNichtVerlinktText,
1302
+ bundleVervollstaendigenKnopf,
1303
+ bundleKoerperZuVorher,
1304
+ bundleKoerperAufNachKlick,
1305
+ bundleHerausnehmenText,
1241
1306
  streifenZahl,
1242
1307
  streifenHarnesse,
1243
1308
  streifenText,
1309
+ alleGruppeZeilen,
1310
+ alleTabelleZeilen,
1244
1311
  streifenDialogTitel,
1245
1312
  streifenDialogText,
1246
1313
  chipGruppeZahl,
@@ -1273,6 +1340,7 @@ function fensterOeffnen() {
1273
1340
  skillOrdnerFehler,
1274
1341
  navReihenfolge,
1275
1342
  mcpKartenText,
1343
+ mcpKartenNamen,
1276
1344
  mcpHeimLinkVorhanden,
1277
1345
  mcpWarnzeileVorhanden,
1278
1346
  mcpOrteVorhanden,
@@ -15,6 +15,8 @@ contextBridge.exposeInMainWorld('hmgr', {
15
15
  bundles: () => ipcRenderer.invoke('hmgr:bundles'),
16
16
  bundleInstallieren: (args) => ipcRenderer.invoke('hmgr:bundleInstallieren', args),
17
17
  bundleEntfernen: (args) => ipcRenderer.invoke('hmgr:bundleEntfernen', args),
18
+ /** Vervollständigen ('alle') oder einzelne Mitglieder wieder verlinken (E114). */
19
+ bundleAbgleichen: (args) => ipcRenderer.invoke('hmgr:bundleAbgleichen', args),
18
20
  /** Icon einer Quelle als data-URL, oder null (E60). */
19
21
  icon: (text) => ipcRenderer.invoke('hmgr:icon', text),
20
22
  /** Öffnet eine Quelle: Repo-Seite im Browser oder Ordner im Dateimanager (E60). */