hmgr 0.0.6 → 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/dist/adapter/bundle-yaml.js +87 -0
- package/dist/adapter/git.js +148 -0
- package/dist/adapter/paket-version.js +25 -0
- package/dist/adapter/zustand-json.js +9 -2
- package/dist/cli/hilfe.js +99 -0
- package/dist/cli/main.js +159 -5
- package/dist/gui/main.js +101 -0
- package/dist/gui/preload.cjs +4 -0
- package/dist/gui/renderer/index.html +12 -1
- package/dist/gui/renderer/renderer.js +415 -107
- package/dist/gui/renderer/styles.css +20 -0
- package/dist/kern/anwendungsfaelle.js +450 -9
- package/dist/kern/modell.js +80 -0
- package/dist/ports/zustand.js +15 -1
- package/package.json +20 -5
package/dist/cli/main.js
CHANGED
|
@@ -4,8 +4,8 @@ import path from 'node:path';
|
|
|
4
4
|
import { realpathSync } from 'node:fs';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
|
-
import { Anwendungsfaelle } from "../kern/anwendungsfaelle.js";
|
|
8
|
-
import { AbhaengigkeitFehlt, EinschraenkungOhneZeiger, HarnessNichtVorhanden, HarnessTypUnbekannt, KeinPassenderRef, LinkAnlegenFehlgeschlagen, McpAbweichenderEintrag, McpEbeneNichtUnterstuetzt, McpFaehigkeitFehlt, McpKatalogFehler, McpNichtInGeteilterDatei, McpNurGeteilterFundort, McpUebernahmeNichtMoeglich, McpUebernahmeUnvollstaendig, McpUnbekannt, NameBelegt, OrtNichtUnterstuetzt, PluginMehrdeutig, PluginUnbekannt, QuelleAufraeumenLokaleAenderungen, QuelleNameBelegt, QuelleUnbekannt, SkillMehrdeutig, SkillOrdnerBereitsVerwaltet, SkillOrdnerFaehigkeitFehlt, SkillOrdnerNichtVerwaltet, SkillOrdnerUngueltig, SkillUnbekannt, ZeigerMehrdeutig, ZeigerSchreibenGesperrt, pluginSchluessel, quelleName, quelleSchluessel, } from "../kern/modell.js";
|
|
7
|
+
import { Anwendungsfaelle, } from "../kern/anwendungsfaelle.js";
|
|
8
|
+
import { AbhaengigkeitFehlt, BundleDateiFehler, BundleMehrdeutig, BundleMitgliederFehlen, BundleNamenskonflikte, BundleNichtInstalliert, BundleUnbekannt, EinschraenkungOhneZeiger, HarnessNichtVorhanden, HarnessTypUnbekannt, KeinPassenderRef, LinkAnlegenFehlgeschlagen, McpAbweichenderEintrag, McpEbeneNichtUnterstuetzt, McpFaehigkeitFehlt, McpKatalogFehler, McpNichtInGeteilterDatei, McpNurGeteilterFundort, McpUebernahmeNichtMoeglich, McpUebernahmeUnvollstaendig, McpUnbekannt, NameBelegt, OrtNichtUnterstuetzt, PluginMehrdeutig, PluginUnbekannt, QuelleAufraeumenLokaleAenderungen, QuelleNameBelegt, QuelleUnbekannt, SkillMehrdeutig, SkillOrdnerBereitsVerwaltet, SkillOrdnerFaehigkeitFehlt, SkillOrdnerNichtVerwaltet, SkillOrdnerUngueltig, SkillUnbekannt, ZeigerMehrdeutig, ZeigerSchreibenGesperrt, pluginSchluessel, quelleName, quelleSchluessel, } from "../kern/modell.js";
|
|
9
9
|
import { Agents } from "../adapter/agents.js";
|
|
10
10
|
import { ClaudeCode } from "../adapter/claude-code.js";
|
|
11
11
|
import { Hermes } from "../adapter/hermes.js";
|
|
@@ -13,6 +13,7 @@ import { ComposerZeiger } from "../adapter/composer.js";
|
|
|
13
13
|
import { DateisystemZiele } from "../adapter/dateisystem.js";
|
|
14
14
|
import { GitQuellen } from "../adapter/git.js";
|
|
15
15
|
import { DateiMcpKatalog } from "../adapter/mcp-katalog.js";
|
|
16
|
+
import { paketVersion } from "../adapter/paket-version.js";
|
|
16
17
|
import { quelleAlsText, quelleAusText } from "../adapter/quelle-text.js";
|
|
17
18
|
import { TerminalRueckfrage } from "../adapter/terminal.js";
|
|
18
19
|
import { ProzessUmgebung } from "../adapter/umgebung.js";
|
|
@@ -59,6 +60,7 @@ const SCHALTER = {
|
|
|
59
60
|
unlink: { werte: ['harness', 'ort', 'composer'], flaggen: ['local', 'lokal'] },
|
|
60
61
|
adopt: { werte: ['harness', 'ort', 'composer'], flaggen: ['local', 'lokal', 'probelauf'] },
|
|
61
62
|
plugin: { werte: ['harness', 'ort', 'composer', 'quelle'], flaggen: ['local', 'lokal'] },
|
|
63
|
+
bundle: { werte: ['harness', 'ort', 'composer', 'quelle'], flaggen: [] },
|
|
62
64
|
mcp: { werte: ['harness', 'ort'], flaggen: [] },
|
|
63
65
|
sync: { werte: [], flaggen: [] },
|
|
64
66
|
update: { werte: [], flaggen: [] },
|
|
@@ -87,6 +89,13 @@ const UNTERKOMMANDOS = {
|
|
|
87
89
|
remove: { woerter: 2, schalter: ['harness', 'ort', 'composer', 'local', 'lokal'] },
|
|
88
90
|
list: { woerter: 1, schalter: [] },
|
|
89
91
|
},
|
|
92
|
+
bundle: {
|
|
93
|
+
add: { woerter: 2, schalter: ['harness', 'ort', 'composer', 'quelle'] },
|
|
94
|
+
remove: { woerter: 2, schalter: ['harness', 'ort', 'composer'] },
|
|
95
|
+
// sync nimmt einen Bundle-Namen, aber keinen zwingend: ohne Namen gilt der ganze Bestand.
|
|
96
|
+
sync: { woerter: 2, schalter: ['harness', 'ort', 'composer'] },
|
|
97
|
+
list: { woerter: 1, schalter: [] },
|
|
98
|
+
},
|
|
90
99
|
mcp: {
|
|
91
100
|
add: { woerter: 2, schalter: ['harness', 'ort'] },
|
|
92
101
|
remove: { woerter: 2, schalter: ['harness', 'ort'] },
|
|
@@ -179,6 +188,11 @@ const UEBERNAHME_GRUND = {
|
|
|
179
188
|
export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
180
189
|
const { anwendungsfaelle, ports } = bauen(umgebung, optionen);
|
|
181
190
|
const kommando = argv[0];
|
|
191
|
+
// Version vor allem anderen (E72): nur die Nummer, damit ein Skript sie ohne Nacharbeit liest.
|
|
192
|
+
if (kommando === '--version') {
|
|
193
|
+
console.log(paketVersion());
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
182
196
|
// Hilfe zuerst: --help ist überall eine Flagge, damit kein Kommando sie als Wert liest.
|
|
183
197
|
if (kommando === undefined) {
|
|
184
198
|
console.log(HILFE);
|
|
@@ -377,6 +391,9 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
377
391
|
if (kommando === 'unlink') {
|
|
378
392
|
const ergebnis = await anwendungsfaelle.linkEntfernen({ skill, ziel, lokal });
|
|
379
393
|
console.log(`Link ${skill} entfernt.`);
|
|
394
|
+
// Zerbrochen (E69): die übrigen Links des Bundles bleiben als Einzel-Installationen liegen.
|
|
395
|
+
for (const bundle of ergebnis.zerbrochen)
|
|
396
|
+
console.log(`Bundle „${bundle}" ist damit zerbrochen, seine übrigen Links bleiben als einzelne Links liegen.`);
|
|
380
397
|
if (ergebnis.hinweis !== undefined)
|
|
381
398
|
console.log(ergebnis.hinweis);
|
|
382
399
|
return 0;
|
|
@@ -490,6 +507,89 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
490
507
|
console.log(` ${zeile}`);
|
|
491
508
|
return 0;
|
|
492
509
|
}
|
|
510
|
+
if (kommando === 'bundle') {
|
|
511
|
+
const schalter = schalterLesen(argv.slice(1), 'bundle');
|
|
512
|
+
unterkommandoPruefen('bundle', schalter);
|
|
513
|
+
const { rest, werte } = schalter;
|
|
514
|
+
const unterkommando = rest[0];
|
|
515
|
+
if (unterkommando === 'list') {
|
|
516
|
+
const katalog = await anwendungsfaelle.bundleKatalog();
|
|
517
|
+
const leer = katalog.every((k) => k.bundles.length === 0 && k.kaputte.length === 0 && k.fehler === undefined);
|
|
518
|
+
if (leer)
|
|
519
|
+
console.log('Keine Quelle mit Bundles. hmgr help bundle zeigt das Format von bundles/<name>.yaml.');
|
|
520
|
+
for (const eintrag of katalog) {
|
|
521
|
+
if (eintrag.fehler !== undefined) {
|
|
522
|
+
console.log(`Quelle ${quelleName(eintrag.quelle)} (${quelleAlsText(eintrag.quelle)}) nicht lesbar: ${eintrag.fehler}`);
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (eintrag.bundles.length === 0 && eintrag.kaputte.length === 0)
|
|
526
|
+
continue;
|
|
527
|
+
console.log(`Quelle ${quelleName(eintrag.quelle)} (${quelleAlsText(eintrag.quelle)})`);
|
|
528
|
+
for (const bundle of eintrag.bundles) {
|
|
529
|
+
console.log(` ${bundle.name} ${kappen(bundle.beschreibung)}`);
|
|
530
|
+
for (const mitglied of bundle.mitglieder)
|
|
531
|
+
console.log(` ${mitglied.pfad} aus ${quelleAlsText(mitglied.quelle)}`);
|
|
532
|
+
}
|
|
533
|
+
// Eine kaputte Definition verdeckt die übrigen nicht, sie wird eigens genannt (E69).
|
|
534
|
+
for (const kaputt of eintrag.kaputte)
|
|
535
|
+
console.log(` Fehler ${kaputt.datei}: ${kaputt.fehler}`);
|
|
536
|
+
}
|
|
537
|
+
return 0;
|
|
538
|
+
}
|
|
539
|
+
if (unterkommando !== 'add' && unterkommando !== 'remove' && unterkommando !== 'sync')
|
|
540
|
+
throw new Error(`${unterkommando === undefined ? 'bundle braucht ein Unterkommando' : `Unbekanntes Unterkommando „${unterkommando}" für bundle`}: bundle add <name>, bundle remove <name>, bundle sync [name] oder bundle list. hmgr help bundle zeigt Beispiele.`);
|
|
541
|
+
const name = rest[1];
|
|
542
|
+
if (name === undefined && unterkommando !== 'sync')
|
|
543
|
+
throw new Error(`bundle ${unterkommando} braucht einen Bundle-Namen. hmgr bundle list zeigt die Bundles der bekannten Quellen.`);
|
|
544
|
+
const ziel = { harness: werte.get('harness') ?? 'claude-code', ort: ortLesen(werte.get('ort')) };
|
|
545
|
+
if (ziel.ort.art === 'arbeitsverzeichnis') {
|
|
546
|
+
// Wie bei link: Verzeichnis merken und binden, damit status es wiederfindet (E21).
|
|
547
|
+
const composer = werte.get('composer');
|
|
548
|
+
if (composer === undefined)
|
|
549
|
+
await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad);
|
|
550
|
+
else
|
|
551
|
+
await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad, composer);
|
|
552
|
+
}
|
|
553
|
+
if (unterkommando === 'add') {
|
|
554
|
+
const quelle = werte.get('quelle');
|
|
555
|
+
const ergebnis = await anwendungsfaelle.bundleInstallieren({
|
|
556
|
+
bundle: name,
|
|
557
|
+
ziel,
|
|
558
|
+
...(quelle === undefined ? {} : { quelle }),
|
|
559
|
+
});
|
|
560
|
+
console.log(`Bundle ${ergebnis.bundle.name} in ${ziel.harness} ${beschreibeOrt(ziel.ort)} installiert (${quelleAlsText(ergebnis.bundle.quelle)}).`);
|
|
561
|
+
for (const neu of ergebnis.neueQuellen)
|
|
562
|
+
console.log(` neue Quelle ${quelleAlsText(neu)} als „${quelleName(neu)}"`);
|
|
563
|
+
if (ergebnis.gelegt.length > 0)
|
|
564
|
+
console.log(` gelegt ${ergebnis.gelegt.join(', ')}`);
|
|
565
|
+
// Übernommen (F67): schon vorhandene Links derselben Quelle gehören jetzt zum Bundle.
|
|
566
|
+
if (ergebnis.uebernommen.length > 0)
|
|
567
|
+
console.log(` übernommen ${ergebnis.uebernommen.join(', ')}`);
|
|
568
|
+
if (ergebnis.hinweis !== undefined)
|
|
569
|
+
for (const zeile of ergebnis.hinweis.split('\n'))
|
|
570
|
+
console.log(` ${zeile}`);
|
|
571
|
+
return 0;
|
|
572
|
+
}
|
|
573
|
+
if (unterkommando === 'remove') {
|
|
574
|
+
const ergebnis = await anwendungsfaelle.bundleEntfernen({ bundle: name, ziel });
|
|
575
|
+
console.log(`Bundle ${ergebnis.bundle} aus ${ziel.harness} ${beschreibeOrt(ziel.ort)} entfernt.`);
|
|
576
|
+
console.log(ergebnis.entfernt.length === 0
|
|
577
|
+
? ' Kein Link mehr da, nur der Bundle-Verweis ist weg.'
|
|
578
|
+
: ` entfernt ${ergebnis.entfernt.join(', ')}`);
|
|
579
|
+
return 0;
|
|
580
|
+
}
|
|
581
|
+
// sync: ohne Ziel-Schalter der ganze Bestand, mit --ort/--harness nur dieses Ziel.
|
|
582
|
+
const nurZiel = werte.has('harness') || werte.has('ort');
|
|
583
|
+
const eintraege = await anwendungsfaelle.bundleAbgleich({
|
|
584
|
+
...(name === undefined ? {} : { bundle: name }),
|
|
585
|
+
...(nurZiel ? { ziel } : {}),
|
|
586
|
+
});
|
|
587
|
+
if (eintraege.length === 0)
|
|
588
|
+
console.log('Kein Bundle installiert, nichts abzugleichen.');
|
|
589
|
+
for (const zeile of bundleBericht(eintraege))
|
|
590
|
+
console.log(zeile);
|
|
591
|
+
return 0;
|
|
592
|
+
}
|
|
493
593
|
if (kommando === 'mcp') {
|
|
494
594
|
const schalter = schalterLesen(argv.slice(1), 'mcp');
|
|
495
595
|
unterkommandoPruefen('mcp', schalter);
|
|
@@ -568,13 +668,18 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
568
668
|
}
|
|
569
669
|
if (kommando === 'update') {
|
|
570
670
|
restPruefen(schalterLesen(argv.slice(1), 'update').rest, 0, 'update');
|
|
571
|
-
const { checkouts: liste, pluginHarnesses, aufraeumen } = await anwendungsfaelle.aktualisieren();
|
|
671
|
+
const { checkouts: liste, pluginHarnesses, bundles, aufraeumen } = await anwendungsfaelle.aktualisieren();
|
|
572
672
|
if (liste.length === 0)
|
|
573
673
|
console.log('Keine Checkouts.');
|
|
574
|
-
for (const { checkout, aktualisierung } of liste) {
|
|
674
|
+
for (const { checkout, aktualisierung, geaenderteSkills } of liste) {
|
|
575
675
|
const kopf = `${quelleAlsText(checkout.quelle)} @ ${checkout.ref}`;
|
|
576
|
-
if (aktualisierung.ergebnis === 'aktualisiert')
|
|
676
|
+
if (aktualisierung.ergebnis === 'aktualisiert') {
|
|
577
677
|
console.log(` aktualisiert ${kopf} ${aktualisierung.von.slice(0, 8)} -> ${aktualisierung.nach.slice(0, 8)}`);
|
|
678
|
+
// Auf Skill-Ebene berichten (E70): sonst steht da nur, dass die Quelle neue Commits hat.
|
|
679
|
+
// Ohne Treffer bleibt die Zeile weg — der Commit hat dann nichts in einem Skill geändert.
|
|
680
|
+
if (geaenderteSkills.length > 0)
|
|
681
|
+
console.log(` ${zahlwort(geaenderteSkills.length, 'Skill', 'Skills')} geändert: ${geaenderteSkills.join(', ')}`);
|
|
682
|
+
}
|
|
578
683
|
else if (aktualisierung.ergebnis === 'unveraendert')
|
|
579
684
|
console.log(` unverändert ${kopf}`);
|
|
580
685
|
else if (aktualisierung.ergebnis === 'uebersprungen')
|
|
@@ -584,6 +689,9 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
584
689
|
}
|
|
585
690
|
for (const name of pluginHarnesses)
|
|
586
691
|
console.log(` Plugins ${name} aktualisiert (E14)`);
|
|
692
|
+
// Bundles im selben Lauf (E69, F64=O1): eine geänderte Definition wird hier nachgezogen.
|
|
693
|
+
for (const zeile of bundleBericht(bundles))
|
|
694
|
+
console.log(zeile);
|
|
587
695
|
// Zum Schluss die verwaisten Checkouts: was gelöscht wurde und was liegen bleibt.
|
|
588
696
|
for (const verwaist of aufraeumen.verwaiste) {
|
|
589
697
|
const kopf = `${quelleAlsText(verwaist.checkout.quelle)} @ ${verwaist.checkout.ref}`;
|
|
@@ -618,6 +726,9 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
618
726
|
console.log(` ${eintrag.link.name} ${spanne}${quelleAlsText(eintrag.link.checkout.quelle)} @ ${eintrag.link.checkout.ref} (${eintrag.link.skill.pfad})${beschreibeGrund(zielZustand.gruende.get(eintrag.link.name))}`);
|
|
619
727
|
}
|
|
620
728
|
}
|
|
729
|
+
// Bundles des Ziels (E69), unter den Einträgen: welche Links als Einheit zusammengehören.
|
|
730
|
+
for (const bundle of zielZustand.bundles)
|
|
731
|
+
console.log(` Bundle ${bundle.name} (${quelleAlsText(bundle.quelle)}) ${bundle.skills.join(', ')}`);
|
|
621
732
|
if (zielZustand.plugins !== undefined)
|
|
622
733
|
console.log(` Plugins: ${zielZustand.plugins.length === 0 ? 'keine' : zielZustand.plugins.join(', ')}`);
|
|
623
734
|
}
|
|
@@ -738,6 +849,31 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
738
849
|
}
|
|
739
850
|
throw new Error(`Unbekanntes Kommando „${kommando}". hmgr --help zeigt alle Kommandos.`);
|
|
740
851
|
}
|
|
852
|
+
/**
|
|
853
|
+
* Bericht über den Bundle-Abgleich (E69), gleich für `hmgr bundle sync` und `hmgr update`: je
|
|
854
|
+
* Bundle eine Zeile mit seinem Ausgang, darunter, was sich geändert hat. „fehlgeschlagen" heißt
|
|
855
|
+
* genau das — der Zusatz sagt, dass das Bundle im Ziel auf dem alten Stand bleibt.
|
|
856
|
+
*/
|
|
857
|
+
function bundleBericht(eintraege) {
|
|
858
|
+
const zeilen = [];
|
|
859
|
+
for (const eintrag of eintraege) {
|
|
860
|
+
const kopf = `Bundle ${eintrag.bundle} ${eintrag.ziel.harness} ${beschreibeOrt(eintrag.ziel.ort)}`;
|
|
861
|
+
if (eintrag.ergebnis === 'fehlgeschlagen') {
|
|
862
|
+
zeilen.push(` fehlgeschlagen ${kopf} bleibt auf dem alten Stand`);
|
|
863
|
+
for (const zeile of (eintrag.fehler ?? '').split('\n'))
|
|
864
|
+
zeilen.push(` ${zeile}`);
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
zeilen.push(` ${eintrag.ergebnis === 'aktualisiert' ? 'aktualisiert ' : 'unverändert '} ${kopf}`);
|
|
868
|
+
for (const neu of eintrag.neueQuellen)
|
|
869
|
+
zeilen.push(` neue Quelle ${quelleAlsText(neu)}`);
|
|
870
|
+
if (eintrag.gelegt.length > 0)
|
|
871
|
+
zeilen.push(` gelegt ${eintrag.gelegt.join(', ')}`);
|
|
872
|
+
if (eintrag.entfernt.length > 0)
|
|
873
|
+
zeilen.push(` entfernt ${eintrag.entfernt.join(', ')}`);
|
|
874
|
+
}
|
|
875
|
+
return zeilen;
|
|
876
|
+
}
|
|
741
877
|
/** Grund eines Links als Suffix (E37). „manuell" bleibt stumm, damit die Ausgabe ruhig bleibt. */
|
|
742
878
|
function beschreibeGrund(grund) {
|
|
743
879
|
if (grund === undefined || grund.art === 'manuell')
|
|
@@ -774,6 +910,12 @@ function istModellFehler(fehler) {
|
|
|
774
910
|
fehler instanceof PluginMehrdeutig ||
|
|
775
911
|
fehler instanceof PluginUnbekannt ||
|
|
776
912
|
fehler instanceof AbhaengigkeitFehlt ||
|
|
913
|
+
fehler instanceof BundleUnbekannt ||
|
|
914
|
+
fehler instanceof BundleMehrdeutig ||
|
|
915
|
+
fehler instanceof BundleMitgliederFehlen ||
|
|
916
|
+
fehler instanceof BundleNamenskonflikte ||
|
|
917
|
+
fehler instanceof BundleNichtInstalliert ||
|
|
918
|
+
fehler instanceof BundleDateiFehler ||
|
|
777
919
|
fehler instanceof QuelleUnbekannt ||
|
|
778
920
|
fehler instanceof QuelleNameBelegt ||
|
|
779
921
|
fehler instanceof QuelleAufraeumenLokaleAenderungen ||
|
|
@@ -808,6 +950,18 @@ function naechsterSchritt(fehler) {
|
|
|
808
950
|
return 'hmgr plugin list zeigt die Plugins der bekannten Quellen. Fehlt der Marketplace, erst hmgr source add <url>.';
|
|
809
951
|
if (fehler instanceof AbhaengigkeitFehlt)
|
|
810
952
|
return 'Die Abhängigkeit steht in keinem Plugin desselben Marketplace, das kann nur die Quelle beheben.';
|
|
953
|
+
if (fehler instanceof BundleUnbekannt)
|
|
954
|
+
return 'hmgr bundle list zeigt die Bundles der bekannten Quellen. Fehlt die Quelle, erst hmgr source add <url|pfad>.';
|
|
955
|
+
if (fehler instanceof BundleMehrdeutig)
|
|
956
|
+
return 'Quelle eingrenzen: --quelle <teilstring der URL oder des Pfads>. hmgr bundle list zeigt, wo welches Bundle steht.';
|
|
957
|
+
if (fehler instanceof BundleMitgliederFehlen)
|
|
958
|
+
return 'Das Bundle ist nur ganz oder gar nicht zu haben. Wer nur einen Teil braucht, verlinkt die erreichbaren Skills einzeln mit hmgr link.';
|
|
959
|
+
if (fehler instanceof BundleNamenskonflikte)
|
|
960
|
+
return 'Die genannten Namen erst freimachen (hmgr unlink <skill> bzw. hmgr adopt <name>), dann bundle add erneut.';
|
|
961
|
+
if (fehler instanceof BundleNichtInstalliert)
|
|
962
|
+
return 'hmgr status zeigt je Ziel, welche Bundles dort installiert sind.';
|
|
963
|
+
if (fehler instanceof BundleDateiFehler)
|
|
964
|
+
return 'Die Definition liegt in der Quelle und wird dort korrigiert, hmgr schreibt sie nie. hmgr help bundle zeigt das Format.';
|
|
811
965
|
if (fehler instanceof KeinPassenderRef)
|
|
812
966
|
return 'Die Tags der Quelle sind frisch geholt, keiner passt. Sonst einen vorhandenen Tag- oder Branchnamen als --version nennen.';
|
|
813
967
|
if (fehler instanceof HarnessNichtVorhanden)
|
package/dist/gui/main.js
CHANGED
|
@@ -9,6 +9,7 @@ import { stat } from 'node:fs/promises';
|
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
10
|
import { bauen, umgebungLesen } from "../cli/main.js";
|
|
11
11
|
import { quelleAlsText, quelleAusText } from "../adapter/quelle-text.js";
|
|
12
|
+
import { paketVersion } from "../adapter/paket-version.js";
|
|
12
13
|
import { quelleName, quelleSchluessel } from "../kern/modell.js";
|
|
13
14
|
import { kannProfile } from "../ports/harness.js";
|
|
14
15
|
import { iconFuerQuelle } from "./icons.js";
|
|
@@ -136,6 +137,14 @@ function zielZustandSerialisieren(z) {
|
|
|
136
137
|
gruende: Object.fromEntries(z.gruende),
|
|
137
138
|
ignoriert: z.ignoriert,
|
|
138
139
|
uebernahme: Object.fromEntries(z.uebernahme),
|
|
140
|
+
// Bundles des Ziels (E69): Adresse und Anzeigename der Quelle kommen mit, damit der Renderer
|
|
141
|
+
// die Gruppe beschriften kann, ohne die Quelle selbst nachzuschlagen.
|
|
142
|
+
bundles: z.bundles.map((b) => ({
|
|
143
|
+
name: b.name,
|
|
144
|
+
skills: b.skills,
|
|
145
|
+
quelleText: quelleAlsText(b.quelle),
|
|
146
|
+
quelleName: quelleName(b.quelle),
|
|
147
|
+
})),
|
|
139
148
|
istRegistriert: z.istRegistriert,
|
|
140
149
|
kannSkillOrdner: z.kannSkillOrdner,
|
|
141
150
|
...(z.plugins === undefined ? {} : { plugins: z.plugins }),
|
|
@@ -181,6 +190,7 @@ function registriereIpc() {
|
|
|
181
190
|
...(skill.modell === undefined ? {} : { modell: skill.modell }),
|
|
182
191
|
...(skill.eigenerKontext === undefined ? {} : { eigenerKontext: skill.eigenerKontext }),
|
|
183
192
|
...(skill.argumentHinweis === undefined ? {} : { argumentHinweis: skill.argumentHinweis }),
|
|
193
|
+
...(skill.geaendert === undefined ? {} : { geaendert: skill.geaendert }),
|
|
184
194
|
quelleText: quelleAlsText(eintrag.quelle),
|
|
185
195
|
quelleName: quelleName(eintrag.quelle),
|
|
186
196
|
quelleSchluessel: quelleSchluessel(eintrag.quelle),
|
|
@@ -189,6 +199,45 @@ function registriereIpc() {
|
|
|
189
199
|
}
|
|
190
200
|
return ergebnis;
|
|
191
201
|
}));
|
|
202
|
+
// Bundles aller bekannten Quellen (E69): flach wie der Skill-Katalog, je Bundle seine Quelle und
|
|
203
|
+
// die Zahl seiner Mitglieder. Kaputte Definitionen kommen als eigene Liste mit, damit die GUI sie
|
|
204
|
+
// nennen kann, statt sie zu verschlucken.
|
|
205
|
+
ipcMain.handle('hmgr:bundles', async () => auffangen(async () => {
|
|
206
|
+
const bundles = [];
|
|
207
|
+
const kaputte = [];
|
|
208
|
+
for (const eintrag of await anwendungsfaelle.bundleKatalog()) {
|
|
209
|
+
for (const bundle of eintrag.bundles)
|
|
210
|
+
bundles.push({
|
|
211
|
+
name: bundle.name,
|
|
212
|
+
beschreibung: bundle.beschreibung,
|
|
213
|
+
mitglieder: bundle.mitglieder.length,
|
|
214
|
+
// Anzeige im Dialog: aus welchen Quellen das Bundle Skills holt, ohne Dubletten.
|
|
215
|
+
quellen: [...new Set(bundle.mitglieder.map((m) => quelleAlsText(m.quelle)))],
|
|
216
|
+
quelleText: quelleAlsText(bundle.quelle),
|
|
217
|
+
quelleName: quelleName(bundle.quelle),
|
|
218
|
+
});
|
|
219
|
+
for (const kaputt of eintrag.kaputte)
|
|
220
|
+
kaputte.push({ quelleName: quelleName(eintrag.quelle), datei: kaputt.datei, fehler: kaputt.fehler });
|
|
221
|
+
}
|
|
222
|
+
return { bundles, kaputte };
|
|
223
|
+
}));
|
|
224
|
+
// Installieren und Entfernen wirken auf das Bundle als Ganzes (E69). Der Zeiger ist nicht im
|
|
225
|
+
// Spiel, die Zugehörigkeit steht im Zustand — anders als bei linkLegen braucht es kein `lokal`.
|
|
226
|
+
ipcMain.handle('hmgr:bundleInstallieren', async (_event, args) => auffangen(async () => {
|
|
227
|
+
const ergebnis = await anwendungsfaelle.bundleInstallieren({
|
|
228
|
+
bundle: args.bundle,
|
|
229
|
+
ziel: zielAusIpc(args.harness, args.ort),
|
|
230
|
+
...(args.quelle === undefined ? {} : { quelle: args.quelle }),
|
|
231
|
+
});
|
|
232
|
+
return {
|
|
233
|
+
name: ergebnis.bundle.name,
|
|
234
|
+
gelegt: ergebnis.gelegt,
|
|
235
|
+
uebernommen: ergebnis.uebernommen,
|
|
236
|
+
neueQuellen: ergebnis.neueQuellen.map((q) => quelleName(q)),
|
|
237
|
+
...(ergebnis.hinweis === undefined ? {} : { hinweis: ergebnis.hinweis }),
|
|
238
|
+
};
|
|
239
|
+
}));
|
|
240
|
+
ipcMain.handle('hmgr:bundleEntfernen', async (_event, args) => auffangen(async () => anwendungsfaelle.bundleEntfernen({ bundle: args.bundle, ziel: zielAusIpc(args.harness, args.ort) })));
|
|
192
241
|
// Nur die Herkunft, ohne die Quellen zu lesen: der Katalog daneben tut das schon.
|
|
193
242
|
ipcMain.handle('hmgr:quellen', async () => auffangen(async () => (await anwendungsfaelle.quellen()).map((quelle) => ({
|
|
194
243
|
art: quelle.art,
|
|
@@ -386,6 +435,11 @@ function fensterOeffnen() {
|
|
|
386
435
|
minWidth: 860,
|
|
387
436
|
minHeight: 560,
|
|
388
437
|
backgroundColor: '#E3E7E0', // --platte, verhindert einen weißen Blitz beim Start
|
|
438
|
+
// E72: der Fenstertitel nennt die Version, damit im Feld erkennbar ist, welcher Stand läuft.
|
|
439
|
+
// Nicht `app.getVersion()`: das liefert nur im gepackten Bundle die Paketversion, beim Start
|
|
440
|
+
// über `electron dist/gui/main.js` (npm run gui, Smoke-Test) dagegen die Electron-Version —
|
|
441
|
+
// dort liegt neben dem Hauptskript keine package.json (gemessen: 44.0.0 statt 0.0.6).
|
|
442
|
+
title: `hmgr ${paketVersion()}`,
|
|
389
443
|
// Im Smoke-Test bleibt das Fenster unsichtbar: der Renderer lädt trotzdem vollständig, der
|
|
390
444
|
// DOM-Auszug kommt per executeJavaScript. So klaut ein automatisierter Lauf niemandem den
|
|
391
445
|
// Eingabefokus und öffnet kein sichtbares Fenster.
|
|
@@ -400,6 +454,9 @@ function fensterOeffnen() {
|
|
|
400
454
|
hauptfenster.on('closed', () => {
|
|
401
455
|
hauptfenster = null;
|
|
402
456
|
});
|
|
457
|
+
// Der <title> der index.html setzt den Fenstertitel beim Laden sonst zurück und überschriebe die
|
|
458
|
+
// Option oben (E72). Der DOM-Titel bleibt dabei, wie er ist, nur das Fenster folgt ihm nicht mehr.
|
|
459
|
+
hauptfenster.on('page-title-updated', (ereignis) => ereignis.preventDefault());
|
|
403
460
|
// Renderer-Konsole im Main-Log, für Fehlersuche und den Smoke-Test.
|
|
404
461
|
hauptfenster.webContents.on('console-message', (event) => {
|
|
405
462
|
console.log(`[renderer] ${event.message}`);
|
|
@@ -507,6 +564,9 @@ function fensterOeffnen() {
|
|
|
507
564
|
// Katalog (E40 R3, R5): Beschreibung aus dem Fixture muss in der Skill-Zeile stehen.
|
|
508
565
|
document.querySelector('[data-ansicht="katalog"]')?.click();
|
|
509
566
|
var katalogBeschreibung = document.querySelector('#katalog .zeile .beschreibung')?.textContent ?? null;
|
|
567
|
+
// E70/F70=O1: die Herkunftszeile trägt hinter dem Ref das Änderungsdatum des Skills.
|
|
568
|
+
var katalogHerkunft = document.querySelector('#katalog .zeile .herkunft')?.textContent
|
|
569
|
+
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
510
570
|
// Besonderheiten aus dem Frontmatter (E55): Marken an der Zeile, Details erst nach
|
|
511
571
|
// einem Klick auf den Aufklapp-Knopf.
|
|
512
572
|
var katalogMarken = document.querySelector('#katalog .zeile .marken')?.textContent
|
|
@@ -524,12 +584,34 @@ function fensterOeffnen() {
|
|
|
524
584
|
var dskKnopf = Array.from(document.querySelectorAll('button'))
|
|
525
585
|
.find((b) => b.textContent === 'Skills hinzufügen');
|
|
526
586
|
var dskMarken = null;
|
|
587
|
+
// Bundle-Sektion im selben Dialog (E69, F68): sie steht dort, wo die Entscheidung
|
|
588
|
+
// fällt, was ins Ziel kommt, und zeigt je Bundle Beschreibung und Herkunft.
|
|
589
|
+
var dskBundleBoxSichtbar = false;
|
|
590
|
+
var dskBundleText = null;
|
|
527
591
|
if (dskKnopf) {
|
|
528
592
|
dskKnopf.click();
|
|
529
593
|
dskMarken = document.querySelector('#dsk-liste .wahlzeile .marken')?.textContent
|
|
530
594
|
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
595
|
+
dskBundleBoxSichtbar = document.getElementById('dsk-bundlebox')?.hidden === false;
|
|
596
|
+
dskBundleText = document.querySelector('#dsk-bundles .wahlzeile')?.textContent
|
|
597
|
+
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
531
598
|
document.getElementById('d-skills')?.close();
|
|
532
599
|
}
|
|
600
|
+
// Bundle-Gruppe an der Ziel-Karte (E69): Kopf mit Name, Quelle und Entfernen, darunter
|
|
601
|
+
// die Zeilen der Mitglieder — sie stehen in der Gruppe, nicht in der allgemeinen Liste.
|
|
602
|
+
var bundleGruppeText = document.querySelector('#zielliste .bundlegruppe .bundlekopf')?.textContent
|
|
603
|
+
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
604
|
+
var bundleGruppeZeilen = Array.from(document.querySelectorAll('#zielliste .bundlegruppe .linkliste .name'))
|
|
605
|
+
.map((n) => n.textContent.trim()).join(',');
|
|
606
|
+
// Entfernen an der Einzelzeile innerhalb der Gruppe sagt, dass das Bundle zerbricht.
|
|
607
|
+
var bundleZerbrichtText = null;
|
|
608
|
+
try {
|
|
609
|
+
document.querySelector('#zielliste .bundlegruppe .linkliste .knopf')?.click();
|
|
610
|
+
bundleZerbrichtText = document.getElementById('dx-text')?.textContent ?? null;
|
|
611
|
+
document.querySelector('#d-entfernen button[value="nein"]')?.click();
|
|
612
|
+
} catch (fehler) {
|
|
613
|
+
bundleZerbrichtText = String(fehler);
|
|
614
|
+
}
|
|
533
615
|
// MCP-Ansicht (E61-Folgerunde, F46: abgespeckt zum reinen Katalog): Navigationsreihenfolge
|
|
534
616
|
// Ziele, MCP, Plugins, Skills, Karten mit Name/Beschreibung/Homepage-Link, F39-Warnzeile,
|
|
535
617
|
// Einhängen-Dialog öffnen/abbrechen ohne IPC-Schreibaktion. Orte-Liste und Fremdkasten
|
|
@@ -640,6 +722,14 @@ function fensterOeffnen() {
|
|
|
640
722
|
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
641
723
|
var kundeXMcpZeilenKnopfZahl = kundeXKarte?.querySelectorAll('.mcpkartezeilen button').length ?? 0;
|
|
642
724
|
var kundeXMcpKnopfVorhanden = mcpHinzuKnopfAn(kundeXKarte) !== undefined;
|
|
725
|
+
// Kartenkopf-Zahl (E71): der Kopf zählt Skills, gebündelt wie der Körper zeilt. An der
|
|
726
|
+
// Verzeichnis-Karte kunde-x hängen dieselben zwei Skills an zwei Harnesses, eine Summe
|
|
727
|
+
// über die Abschnitte ergäbe hier 4.
|
|
728
|
+
var kopfZahlVon = function (karte) {
|
|
729
|
+
return karte?.querySelector('.zielkopf .zahl')?.textContent?.trim() ?? null;
|
|
730
|
+
};
|
|
731
|
+
var kundeXKopfZahl = kopfZahlVon(kundeXKarte);
|
|
732
|
+
var hermesKopfZahl = kopfZahlVon(hermesKarte);
|
|
643
733
|
var agentsKarte = karteMitKopftext('Agents · Home');
|
|
644
734
|
// Ohne diese Zusicherung wären die beiden Abwesenheits-Prüfungen darunter leer erfüllt,
|
|
645
735
|
// sobald die Karte selbst aus der Liste fiele: „kein Abschnitt" hieße dann nur „keine Karte".
|
|
@@ -678,12 +768,18 @@ function fensterOeffnen() {
|
|
|
678
768
|
zielManuellVorhanden,
|
|
679
769
|
zahnradVorhanden,
|
|
680
770
|
katalogBeschreibung,
|
|
771
|
+
katalogHerkunft,
|
|
681
772
|
katalogMarken,
|
|
682
773
|
katalogDetailsZu,
|
|
683
774
|
katalogAufgeklappt,
|
|
684
775
|
katalogDetails,
|
|
685
776
|
katalogMarkenWarnend,
|
|
686
777
|
dskMarken,
|
|
778
|
+
dskBundleBoxSichtbar,
|
|
779
|
+
dskBundleText,
|
|
780
|
+
bundleGruppeText,
|
|
781
|
+
bundleGruppeZeilen,
|
|
782
|
+
bundleZerbrichtText,
|
|
687
783
|
interGeladen: document.fonts.check('400 16px Inter'),
|
|
688
784
|
monoGeladen: document.fonts.check('500 16px "JetBrains Mono"'),
|
|
689
785
|
quelleneuVorhanden,
|
|
@@ -733,6 +829,8 @@ function fensterOeffnen() {
|
|
|
733
829
|
kundeXMcpText,
|
|
734
830
|
kundeXMcpZeilenKnopfZahl,
|
|
735
831
|
kundeXMcpKnopfVorhanden,
|
|
832
|
+
kundeXKopfZahl,
|
|
833
|
+
hermesKopfZahl,
|
|
736
834
|
agentsKarteVorhanden,
|
|
737
835
|
agentsMcpAbschnittVorhanden,
|
|
738
836
|
agentsMcpKnopfVorhanden,
|
|
@@ -743,6 +841,9 @@ function fensterOeffnen() {
|
|
|
743
841
|
});
|
|
744
842
|
})()`)
|
|
745
843
|
.then((auszug) => console.log(`[smoke] DOM-Auszug: ${auszug}`))
|
|
844
|
+
// E72: der Fenstertitel steht nicht im DOM (dort steht weiter der <title> der
|
|
845
|
+
// index.html), er lässt sich nur hier im Main-Prozess ablesen.
|
|
846
|
+
.then(() => console.log(`[smoke] Fenster: ${JSON.stringify({ fenstertitel: fenster.getTitle() })}`))
|
|
746
847
|
.finally(() => app.quit());
|
|
747
848
|
}, 3000);
|
|
748
849
|
});
|
package/dist/gui/preload.cjs
CHANGED
|
@@ -11,6 +11,10 @@ contextBridge.exposeInMainWorld('hmgr', {
|
|
|
11
11
|
marketplaces: () => ipcRenderer.invoke('hmgr:marketplaces'),
|
|
12
12
|
skills: () => ipcRenderer.invoke('hmgr:skills'),
|
|
13
13
|
quellen: () => ipcRenderer.invoke('hmgr:quellen'),
|
|
14
|
+
/** Bundles aller bekannten Quellen (E69), entdeckt beim Quellen-Scan: kein eigener Katalog. */
|
|
15
|
+
bundles: () => ipcRenderer.invoke('hmgr:bundles'),
|
|
16
|
+
bundleInstallieren: (args) => ipcRenderer.invoke('hmgr:bundleInstallieren', args),
|
|
17
|
+
bundleEntfernen: (args) => ipcRenderer.invoke('hmgr:bundleEntfernen', args),
|
|
14
18
|
/** Icon einer Quelle als data-URL, oder null (E60). */
|
|
15
19
|
icon: (text) => ipcRenderer.invoke('hmgr:icon', text),
|
|
16
20
|
/** Öffnet eine Quelle: Repo-Seite im Browser oder Ordner im Dateimanager (E60). */
|
|
@@ -216,7 +216,18 @@
|
|
|
216
216
|
<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>
|
|
217
217
|
<input type="search" id="dsk-suche" placeholder="Skill suchen" aria-label="Skills durchsuchen">
|
|
218
218
|
</div>
|
|
219
|
-
|
|
219
|
+
<!-- Bundle-Sektion (E69, F68): kein eigener Navigationspunkt, Bundles stehen hier, wo die
|
|
220
|
+
Entscheidung fällt, was ins Ziel kommt. Leer bleibt der Kasten weg. -->
|
|
221
|
+
<div class="dskgruppe" id="dsk-bundlebox" hidden>
|
|
222
|
+
<span class="etikett">Bundles</span>
|
|
223
|
+
<p class="ohne dskgruppentext">Ein Bundle bringt eine ganze Zusammenstellung auf einmal, auch über Quellen hinweg. Fehlende Quellen legt hmgr dabei an.</p>
|
|
224
|
+
<div class="wahl" id="dsk-bundles"></div>
|
|
225
|
+
<p class="ohne dskgruppenfehler" id="dsk-bundlefehler" hidden></p>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="dskgruppe">
|
|
228
|
+
<span class="etikett" id="dsk-skilletikett">Skills</span>
|
|
229
|
+
<div class="wahl" id="dsk-liste"></div>
|
|
230
|
+
</div>
|
|
220
231
|
</div>
|
|
221
232
|
<div class="dfuss">
|
|
222
233
|
<span class="links mono" id="dsk-bilanz">0 ausgewählt</span>
|