hmgr 0.0.7 → 0.0.12

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/cli/main.js CHANGED
@@ -5,19 +5,23 @@ import { realpathSync } from 'node:fs';
5
5
  import { homedir } from 'node:os';
6
6
  import { pathToFileURL } from 'node:url';
7
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";
8
+ import { AbhaengigkeitFehlt, BundleDateiFehler, BundleMehrdeutig, BundleMitgliederFehlen, BundleNamenskonflikte, BundleNichtInstalliert, BundleUnbekannt, EinschraenkungOhneZeiger, HarnessInaktiv, HarnessNichtVorhanden, HarnessTypUnbekannt, HeimAdresseUngueltig, HeimBereitsVorhanden, HeimFaehigkeitFehlt, HeimNurUnterWindows, HeimOhneCliZugriff, HeimUnbekannt, 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, WslBrueckeFehlgeschlagen, ZeigerSchreibenGesperrt, heimAdresseAlsText, heimAdresseZerlegen, pluginSchluessel, quelleName, quelleSchluessel, } from "../kern/modell.js";
9
9
  import { Agents } from "../adapter/agents.js";
10
- import { ClaudeCode } from "../adapter/claude-code.js";
10
+ import { ClaudeCode, claudeAusfuehrer } from "../adapter/claude-code.js";
11
11
  import { Hermes } from "../adapter/hermes.js";
12
12
  import { ComposerZeiger } from "../adapter/composer.js";
13
- import { DateisystemZiele } from "../adapter/dateisystem.js";
13
+ import { DateisystemZiele, LokalerZugriff } from "../adapter/dateisystem.js";
14
+ import { ZustandHeime } from "../adapter/heime.js";
15
+ import { WslBruecke, WslZugriff, wslAusfuehrer, wslDistros } from "../adapter/wsl.js";
14
16
  import { GitQuellen } from "../adapter/git.js";
15
17
  import { DateiMcpKatalog } from "../adapter/mcp-katalog.js";
18
+ import { cliUpdateHinweis } from "../adapter/npm-registry.js";
16
19
  import { paketVersion } from "../adapter/paket-version.js";
17
20
  import { quelleAlsText, quelleAusText } from "../adapter/quelle-text.js";
18
21
  import { TerminalRueckfrage } from "../adapter/terminal.js";
19
22
  import { ProzessUmgebung } from "../adapter/umgebung.js";
20
23
  import { ZustandJson } from "../adapter/zustand-json.js";
24
+ import { VerlaufJson } from "../adapter/verlauf-json.js";
21
25
  import { HILFE, hilfeText } from "./hilfe.js";
22
26
  export function umgebungLesen(env = process.env) {
23
27
  const home = env.HOME ?? homedir();
@@ -30,19 +34,45 @@ export function umgebungLesen(env = process.env) {
30
34
  }
31
35
  export function bauen(umgebung, optionen = {}) {
32
36
  const zustand = new ZustandJson(path.join(umgebung.hmgrHome, 'state.json'));
37
+ const verlauf = new VerlaufJson(umgebung.hmgrHome);
33
38
  const wurzel = path.join(umgebung.hmgrHome, 'checkouts');
34
- const claude = optionen.claudeAusfuehrer === undefined
35
- ? new ClaudeCode(umgebung.home)
36
- : new ClaudeCode(umgebung.home, optionen.claudeAusfuehrer);
39
+ const umgebungPort = optionen.umgebung ?? new ProzessUmgebung();
40
+ // Homedirs aus dem Zustand (E78): das primäre lässt sich umstellen (F89), dazu kommen
41
+ // zusätzliche in einer WSL-Distro (F87). `wsl:`-Heime fallen außerhalb von Windows heraus.
42
+ const heime = new ZustandHeime(zustand, umgebung.home, umgebungPort.plattform());
43
+ const wslAusfuehren = optionen.wslAusfuehrer ?? wslAusfuehrer;
44
+ // Je Distro eine Bridge, damit die einmal ermittelte Automount-Wurzel im Lauf erhalten bleibt.
45
+ const bruecken = new Map();
46
+ const bruecke = (distro) => {
47
+ let vorhanden = bruecken.get(distro);
48
+ if (vorhanden === undefined) {
49
+ vorhanden = new WslBruecke(distro, wslAusfuehren);
50
+ bruecken.set(distro, vorhanden);
51
+ }
52
+ return vorhanden;
53
+ };
54
+ const lokal = new LokalerZugriff();
55
+ const zugriffFuerOrt = (ort) => {
56
+ const adresse = ort.art === 'home' ? ort.heim : undefined;
57
+ const zerlegt = adresse === undefined ? undefined : heimAdresseZerlegen(adresse);
58
+ return zerlegt === undefined ? lokal : new WslZugriff(bruecke(zerlegt.distro));
59
+ };
60
+ const claude = new ClaudeCode(heime, optionen.claudeAusfuehrer ?? claudeAusfuehrer, zugriffFuerOrt);
37
61
  const ports = {
38
- harnesses: [claude, new Hermes(umgebung.home), new Agents(umgebung.home)],
62
+ harnesses: [claude, new Hermes(heime), new Agents(heime)],
39
63
  quellen: new GitQuellen(wurzel),
40
- ziele: new DateisystemZiele(wurzel, zustand, umgebung.xdgStateHome),
64
+ ziele: new DateisystemZiele(wurzel, zustand, umgebung.xdgStateHome, (ziel) => zugriffFuerOrt(ziel.ort)),
41
65
  zeiger: new ComposerZeiger(),
42
66
  zustand,
43
67
  rueckfrage: optionen.rueckfrage ?? new TerminalRueckfrage(),
44
68
  mcpKatalog: new DateiMcpKatalog(path.join(umgebung.hmgrHome, 'mcp.yaml')),
45
- umgebung: optionen.umgebung ?? new ProzessUmgebung(),
69
+ umgebung: umgebungPort,
70
+ heime,
71
+ wsl: {
72
+ distros: async () => await wslDistros(wslAusfuehren),
73
+ home: async (distro) => await bruecke(distro).home(),
74
+ },
75
+ verlauf,
46
76
  };
47
77
  return { anwendungsfaelle: new Anwendungsfaelle(ports), ports };
48
78
  }
@@ -65,6 +95,7 @@ const SCHALTER = {
65
95
  sync: { werte: [], flaggen: [] },
66
96
  update: { werte: [], flaggen: [] },
67
97
  status: { werte: [], flaggen: [] },
98
+ log: { werte: [], flaggen: [] },
68
99
  harness: { werte: ['profil'], flaggen: [] },
69
100
  };
70
101
  /**
@@ -165,6 +196,11 @@ function unterkommandoPruefen(kommando, schalter) {
165
196
  `hmgr help ${kommando} zeigt die Schalter.`);
166
197
  restPruefen(schalter.rest, menge.woerter, kommando, unterkommando);
167
198
  }
199
+ /**
200
+ * `--ort` deuten. Ein zusätzliches Homedir (E78, F87) heißt nach seiner Adresse: `--ort wsl:Ubuntu`,
201
+ * auch in der ausgeschriebenen Form `--ort home:wsl:Ubuntu`. Beides ergibt denselben Ort, damit
202
+ * niemand raten muss, ob das `home:` davor gehört.
203
+ */
168
204
  function ortLesen(text) {
169
205
  const roh = text ?? 'home';
170
206
  if (roh === 'home')
@@ -173,7 +209,10 @@ function ortLesen(text) {
173
209
  return { art: 'arbeitsverzeichnis', pfad: path.resolve(roh.slice(4)) };
174
210
  if (roh.startsWith('profil:'))
175
211
  return { art: 'profil', name: roh.slice(7) };
176
- throw new Error(`Unbekannter Ort „${roh}", erwartet home, dir:<pfad> oder profil:<name>`);
212
+ const heim = heimAdresseZerlegen(roh.startsWith('home:') ? roh.slice(5) : roh);
213
+ if (heim !== undefined)
214
+ return { art: 'home', heim: heimAdresseAlsText(heim) };
215
+ throw new Error(`Unbekannter Ort „${roh}", erwartet home, wsl:<distro>, dir:<pfad> oder profil:<name>`);
177
216
  }
178
217
  /** Klartext für den Grund, aus dem ein fremder Eintrag liegen bleibt (B12). */
179
218
  const UEBERNAHME_GRUND = {
@@ -217,6 +256,14 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
217
256
  console.log(text);
218
257
  return 0;
219
258
  }
259
+ // Versionssprung vermerken (E80): bei jedem echten Kommando, Hilfe und Version sind oben schon
260
+ // zurückgekehrt. Ein Fehler hier (etwa ein kaputtes verlauf.json) darf kein Kommando kippen.
261
+ try {
262
+ await anwendungsfaelle.versionsSprungVermerken('cli', paketVersion(), 'cli');
263
+ }
264
+ catch {
265
+ // still, siehe oben.
266
+ }
220
267
  if (kommando === 'source') {
221
268
  const schalter = schalterLesen(argv.slice(1), 'source');
222
269
  unterkommandoPruefen('source', schalter);
@@ -668,7 +715,7 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
668
715
  }
669
716
  if (kommando === 'update') {
670
717
  restPruefen(schalterLesen(argv.slice(1), 'update').rest, 0, 'update');
671
- const { checkouts: liste, pluginHarnesses, bundles, aufraeumen } = await anwendungsfaelle.aktualisieren();
718
+ const { checkouts: liste, bundles, aufraeumen } = await anwendungsfaelle.aktualisieren({ ausloeser: 'cli' });
672
719
  if (liste.length === 0)
673
720
  console.log('Keine Checkouts.');
674
721
  for (const { checkout, aktualisierung, geaenderteSkills } of liste) {
@@ -687,8 +734,6 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
687
734
  else
688
735
  console.log(` fehlgeschlagen ${kopf} ${aktualisierung.fehler}`);
689
736
  }
690
- for (const name of pluginHarnesses)
691
- console.log(` Plugins ${name} aktualisiert (E14)`);
692
737
  // Bundles im selben Lauf (E69, F64=O1): eine geänderte Definition wird hier nachgezogen.
693
738
  for (const zeile of bundleBericht(bundles))
694
739
  console.log(zeile);
@@ -706,11 +751,26 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
706
751
  const geloescht = aufraeumen.verwaiste.filter((v) => v.geloescht).length;
707
752
  console.log(`${aufraeumen.verwaiste.length} verwaist, ${geloescht} gelöscht, ${aufraeumen.verwaiste.length - geloescht} liegen geblieben.`);
708
753
  }
754
+ // Zuletzt hmgr selbst (E77): wer schon aktualisiert, will auch wissen, dass sein hmgr alt ist.
755
+ // Verglichen wird die eigene Version (E72) gegen npm, gemeldet wird nur — installiert wird
756
+ // nichts. Offline oder Registry unerreichbar heißt keine Zeile, still: der Lauf selbst ist
757
+ // fertig und gelungen, ein fehlgeschlagener Nebenblick darf ihn nicht kommentieren.
758
+ const selbst = await cliUpdateHinweis(paketVersion());
759
+ if (selbst !== null) {
760
+ console.log(selbst.text);
761
+ await anwendungsfaelle.hmgrEreignisVermerken({ art: 'cli-verfuegbar', version: selbst.neu }, 'cli');
762
+ }
709
763
  return 0;
710
764
  }
711
765
  if (kommando === 'status') {
712
766
  restPruefen(schalterLesen(argv.slice(1), 'status').rest, 0, 'status');
713
- for (const zielZustand of await anwendungsfaelle.istZustand()) {
767
+ // Ziele inaktiver Harnesses bleiben aus der Liste (E74), auch wenn dort noch Links oder fremde
768
+ // Einträge liegen: was hmgr nicht managed, zeigt es nicht. Gefiltert wird hier, nicht in
769
+ // istZustand — der Kern bleibt ungefiltert (E19), sonst hielte kein Link in einem inaktiven
770
+ // Harness seinen Checkout mehr vor dem Aufräumen.
771
+ const aktiveStatus = new Set(await anwendungsfaelle.aktiveHarnesses());
772
+ const sichtbareZiele = (await anwendungsfaelle.istZustand()).filter((z) => aktiveStatus.has(z.ziel.harness));
773
+ for (const zielZustand of sichtbareZiele) {
714
774
  console.log(`Ziel ${zielZustand.ziel.harness} ${beschreibeOrt(zielZustand.ziel.ort)} (${zielZustand.verzeichnis})`);
715
775
  if (zielZustand.eintraege.length === 0)
716
776
  console.log(' (leer)');
@@ -737,6 +797,18 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
737
797
  console.log(`Nicht vorhanden: ${fehlend.join(', ')}`);
738
798
  return 0;
739
799
  }
800
+ if (kommando === 'log') {
801
+ restPruefen(schalterLesen(argv.slice(1), 'log').rest, 0, 'log');
802
+ const eintraege = await anwendungsfaelle.verlauf();
803
+ if (eintraege.length === 0)
804
+ console.log('Noch keine Ereignisse.');
805
+ for (const eintrag of eintraege) {
806
+ console.log(verlaufZeitpunkt(eintrag.zeit));
807
+ for (const ereignis of eintrag.ereignisse)
808
+ console.log(` ${verlaufEreignisZeile(ereignis)}`);
809
+ }
810
+ return 0;
811
+ }
740
812
  if (kommando === 'harness') {
741
813
  const schalter = schalterLesen(argv.slice(1), 'harness');
742
814
  unterkommandoPruefen('harness', schalter);
@@ -790,13 +862,83 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
790
862
  'harness skills-dir <name> add <pfad> oder harness skills-dir <name> remove. ' +
791
863
  'hmgr help harness zeigt Beispiele.');
792
864
  }
865
+ // Homedirs eines Harness (E78): zeigen, das primäre umstellen (F89), zusätzliche in einer
866
+ // WSL-Distro hinzufügen und entfernen (F87).
867
+ if (unterkommando === 'home') {
868
+ const harnessName = rest[1];
869
+ if (harnessName === undefined)
870
+ throw new Error('harness home braucht einen Harness-Namen, etwa hmgr harness home claude-code. ' +
871
+ 'hmgr harness list zeigt die bekannten Namen.');
872
+ const unter = rest[2];
873
+ if (unter === undefined) {
874
+ restPruefen(rest, 2, 'harness', 'home');
875
+ const ansicht = (await anwendungsfaelle.harnessHeime()).find((h) => h.harness === harnessName);
876
+ if (ansicht === undefined)
877
+ throw new HarnessTypUnbekannt(harnessName);
878
+ for (const heim of ansicht.heime) {
879
+ const wie = heim.adresse ?? (heim.eingestellt ? 'home (eingestellt)' : 'home');
880
+ console.log(`${harnessName} ${wie} ${heim.pfad}`);
881
+ }
882
+ if (ansicht.kannZusaetzliche)
883
+ console.log(` Weiteres Homedir: hmgr harness home ${harnessName} add wsl:<distro>.`);
884
+ return 0;
885
+ }
886
+ if (unter === 'set') {
887
+ restPruefen(rest, 4, 'harness', 'home');
888
+ const pfad = rest[3];
889
+ if (pfad === undefined)
890
+ throw new Error('harness home <name> set braucht einen Pfad, etwa hmgr harness home claude-code set D:\\Profile\\tim.');
891
+ const gesetzt = await anwendungsfaelle.harnessHeimSetzen(harnessName, pfad);
892
+ console.log(`${harnessName} liest sein Homedir jetzt unter ${gesetzt}.`);
893
+ console.log(' Was am alten Ort liegt, bleibt dort liegen. hmgr zeigt es nicht mehr.');
894
+ return 0;
895
+ }
896
+ if (unter === 'reset') {
897
+ restPruefen(rest, 3, 'harness', 'home');
898
+ await anwendungsfaelle.harnessHeimZuruecksetzen(harnessName);
899
+ const ansicht = (await anwendungsfaelle.harnessHeime()).find((h) => h.harness === harnessName);
900
+ console.log(`${harnessName} liest sein Homedir wieder unter ${ansicht?.heime[0]?.pfad ?? '?'}.`);
901
+ return 0;
902
+ }
903
+ if (unter === 'add') {
904
+ restPruefen(rest, 4, 'harness', 'home');
905
+ const adresse = rest[3];
906
+ if (adresse === undefined) {
907
+ const distros = await anwendungsfaelle.wslDistros();
908
+ const beispiel = distros[0] ?? 'Ubuntu';
909
+ throw new Error(`harness home <name> add braucht eine Adresse, etwa hmgr harness home ${harnessName} add wsl:${beispiel}.` +
910
+ (distros.length > 0 ? ` Installierte Distros: ${distros.join(', ')}.` : ''));
911
+ }
912
+ const heim = await anwendungsfaelle.harnessHeimHinzufuegen(harnessName, adresse);
913
+ console.log(`${harnessName} ${heim.adresse}: Homedir ${heim.pfad}.`);
914
+ console.log(` Verlinken dorthin mit --harness ${harnessName} --ort ${heim.adresse}.`);
915
+ return 0;
916
+ }
917
+ if (unter === 'remove') {
918
+ restPruefen(rest, 4, 'harness', 'home');
919
+ const adresse = rest[3];
920
+ if (adresse === undefined)
921
+ throw new Error(`harness home <name> remove braucht eine Adresse, etwa hmgr harness home ${harnessName} remove wsl:Ubuntu.`);
922
+ await anwendungsfaelle.harnessHeimEntfernen(harnessName, adresse);
923
+ console.log(`${harnessName} ${adresse}: Homedir vergessen. Die Links dort bleiben liegen.`);
924
+ return 0;
925
+ }
926
+ throw new Error(`Unbekanntes Unterkommando „${unter}" für harness home: harness home <name>, ` +
927
+ 'harness home <name> set <pfad>, harness home <name> reset, harness home <name> add wsl:<distro> ' +
928
+ 'oder harness home <name> remove wsl:<distro>. hmgr help harness zeigt Beispiele.');
929
+ }
793
930
  if (unterkommando === 'list') {
794
931
  const aktive = new Set(await anwendungsfaelle.aktiveHarnesses());
795
932
  const fehlend = new Set(await anwendungsfaelle.nichtVorhandeneHarnesses());
933
+ const heime = await anwendungsfaelle.harnessHeime();
796
934
  for (const name of anwendungsfaelle.harnessTypen()) {
797
935
  const aktivText = aktive.has(name) ? 'aktiv' : 'inaktiv';
798
936
  const vorhandenText = fehlend.has(name) ? 'nicht vorhanden' : 'vorhanden';
799
- console.log(`${name} ${aktivText}, ${vorhandenText}`);
937
+ // Zusätzliche Homedirs (E78) gehören in die Übersicht: sonst sähe niemand, dass ein
938
+ // zweites Ziel je Harness existiert, ohne extra danach zu fragen.
939
+ const zusatz = (heime.find((h) => h.harness === name)?.heime ?? []).filter((h) => h.adresse !== undefined);
940
+ const heimText = zusatz.length === 0 ? '' : `, ${zusatz.map((h) => h.adresse).join(', ')}`;
941
+ console.log(`${name} ${aktivText}, ${vorhandenText}${heimText}`);
800
942
  }
801
943
  return 0;
802
944
  }
@@ -845,7 +987,7 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
845
987
  }
846
988
  return 0;
847
989
  }
848
- throw new Error(`${unterkommando === undefined ? 'harness braucht ein Unterkommando' : `Unbekanntes Unterkommando „${unterkommando}" für harness`}: harness list, harness enable <name> oder harness disable <name>. hmgr help harness zeigt Beispiele.`);
990
+ throw new Error(`${unterkommando === undefined ? 'harness braucht ein Unterkommando' : `Unbekanntes Unterkommando „${unterkommando}" für harness`}: harness list, harness enable <name>, harness disable <name> oder harness home <name>. hmgr help harness zeigt Beispiele.`);
849
991
  }
850
992
  throw new Error(`Unbekanntes Kommando „${kommando}". hmgr --help zeigt alle Kommandos.`);
851
993
  }
@@ -874,6 +1016,40 @@ function bundleBericht(eintraege) {
874
1016
  }
875
1017
  return zeilen;
876
1018
  }
1019
+ /** Kopfzeile eines Verlaufseintrags (E80): lokale Zeit, damit sie zum Rechner des Lesers passt. */
1020
+ function verlaufZeitpunkt(iso) {
1021
+ const datum = new Date(iso);
1022
+ const zwei = (n) => String(n).padStart(2, '0');
1023
+ return (`${zwei(datum.getDate())}.${zwei(datum.getMonth() + 1)}.${datum.getFullYear()} ` +
1024
+ `${zwei(datum.getHours())}:${zwei(datum.getMinutes())}`);
1025
+ }
1026
+ /** Eine Ereigniszeile im Verlauf (E80), eingerückt unter der Kopfzeile ihres Laufs. */
1027
+ function verlaufEreignisZeile(ereignis) {
1028
+ if (ereignis.art === 'checkout') {
1029
+ const stand = `${ereignis.von.slice(0, 7)} → ${ereignis.nach.slice(0, 7)}`;
1030
+ const skills = ereignis.skills.length > 0 ? `, Skills: ${ereignis.skills.join(', ')}` : '';
1031
+ return `${ereignis.quelle} @ ${ereignis.ref}: ${stand}${skills}`;
1032
+ }
1033
+ if (ereignis.art === 'bundle') {
1034
+ const teile = [
1035
+ ereignis.gelegt.length > 0 ? `gelegt ${ereignis.gelegt.join(', ')}` : undefined,
1036
+ ereignis.entfernt.length > 0 ? `entfernt ${ereignis.entfernt.join(', ')}` : undefined,
1037
+ ].filter((t) => t !== undefined);
1038
+ // Formatierung wie in bundleBericht (Harness + beschreibeOrt); ein String-Ziel (vor E80-Nachtrag
1039
+ // geschrieben) bleibt unverändert stehen.
1040
+ const ziel = typeof ereignis.ziel === 'string' ? ereignis.ziel : `${ereignis.ziel.harness} ${beschreibeOrt(ereignis.ziel.ort)}`;
1041
+ return `Bundle ${ereignis.bundle} → ${ziel}: ${teile.join('; ')}`;
1042
+ }
1043
+ if (ereignis.art === 'aufgeraeumt')
1044
+ return `Checkout ${ereignis.checkout} aufgeräumt`;
1045
+ if (ereignis.art === 'fehler')
1046
+ return `Fehler ${ereignis.betrifft}: ${ereignis.fehler}`;
1047
+ if (ereignis.art === 'hmgr-installiert')
1048
+ return ereignis.paket === 'gui' ? `hmgr-GUI ${ereignis.version} installiert` : `hmgr ${ereignis.version} installiert`;
1049
+ if (ereignis.art === 'gui-update-geladen')
1050
+ return `GUI-Update ${ereignis.version} geladen`;
1051
+ return `CLI ${ereignis.version} verfügbar`;
1052
+ }
877
1053
  /** Grund eines Links als Suffix (E37). „manuell" bleibt stumm, damit die Ausgabe ruhig bleibt. */
878
1054
  function beschreibeGrund(grund) {
879
1055
  if (grund === undefined || grund.art === 'manuell')
@@ -888,9 +1064,10 @@ function zahlwort(n, ein, viele) {
888
1064
  function kappen(text, laenge = 80) {
889
1065
  return text.length <= laenge ? text : `${text.slice(0, laenge - 1)}…`;
890
1066
  }
1067
+ /** Ort als Text, so geschrieben, wie `--ort` ihn wieder annimmt. */
891
1068
  function beschreibeOrt(ort) {
892
1069
  if (ort.art === 'home')
893
- return 'home';
1070
+ return ort.heim === undefined ? 'home' : ort.heim;
894
1071
  if (ort.art === 'profil')
895
1072
  return `profil:${ort.name}`;
896
1073
  return `dir:${ort.pfad}`;
@@ -904,6 +1081,7 @@ function istModellFehler(fehler) {
904
1081
  fehler instanceof ZeigerMehrdeutig ||
905
1082
  fehler instanceof ZeigerSchreibenGesperrt ||
906
1083
  fehler instanceof HarnessNichtVorhanden ||
1084
+ fehler instanceof HarnessInaktiv ||
907
1085
  fehler instanceof HarnessTypUnbekannt ||
908
1086
  fehler instanceof KeinPassenderRef ||
909
1087
  fehler instanceof EinschraenkungOhneZeiger ||
@@ -931,7 +1109,14 @@ function istModellFehler(fehler) {
931
1109
  fehler instanceof McpNurGeteilterFundort ||
932
1110
  fehler instanceof McpUebernahmeNichtMoeglich ||
933
1111
  fehler instanceof McpNichtInGeteilterDatei ||
934
- fehler instanceof McpUebernahmeUnvollstaendig);
1112
+ fehler instanceof McpUebernahmeUnvollstaendig ||
1113
+ fehler instanceof HeimAdresseUngueltig ||
1114
+ fehler instanceof HeimBereitsVorhanden ||
1115
+ fehler instanceof HeimFaehigkeitFehlt ||
1116
+ fehler instanceof HeimNurUnterWindows ||
1117
+ fehler instanceof HeimOhneCliZugriff ||
1118
+ fehler instanceof HeimUnbekannt ||
1119
+ fehler instanceof WslBrueckeFehlgeschlagen);
935
1120
  }
936
1121
  /**
937
1122
  * Der nächste Schritt zu einem Fehler aus dem Kern. Die Fehlertexte selbst bleiben, wie das Modell
@@ -966,10 +1151,26 @@ function naechsterSchritt(fehler) {
966
1151
  return 'Die Tags der Quelle sind frisch geholt, keiner passt. Sonst einen vorhandenen Tag- oder Branchnamen als --version nennen.';
967
1152
  if (fehler instanceof HarnessNichtVorhanden)
968
1153
  return 'Der Harness legt sein Verzeichnis beim ersten Start an. hmgr status zeigt, welche Harnesses hier liegen.';
1154
+ if (fehler instanceof HarnessInaktiv)
1155
+ return `Erst aktivieren: hmgr harness enable ${fehler.harness}. Bestehende Links dort bleiben unberührt.`;
969
1156
  if (fehler instanceof OrtNichtUnterstuetzt)
970
1157
  return 'profil:<name> kennt nur hermes, claude-code und agents kennen home und dir:<pfad>.';
971
1158
  if (fehler instanceof HarnessTypUnbekannt)
972
1159
  return 'Bekannt sind claude-code, hermes und agents. hmgr harness list zeigt sie mit ihrem Stand.';
1160
+ if (fehler instanceof HeimAdresseUngueltig)
1161
+ return 'Ein zusätzliches Homedir wird als wsl:<distro> angegeben. Ein abweichender normaler Pfad gehört an hmgr harness home <name> set <pfad>.';
1162
+ if (fehler instanceof HeimFaehigkeitFehlt)
1163
+ return 'Zusätzliche Homedirs kennt bisher nur claude-code. Für einen abweichenden Pfad: hmgr harness home <name> set <pfad>.';
1164
+ if (fehler instanceof HeimNurUnterWindows)
1165
+ return 'Auf diesem Rechner gibt es kein WSL. Das Homedir wird auf dem Windows-Rechner eingetragen, auf dem die Distro läuft.';
1166
+ if (fehler instanceof HeimBereitsVorhanden)
1167
+ return `hmgr harness home ${fehler.harness} zeigt die eingetragenen Homedirs.`;
1168
+ if (fehler instanceof HeimUnbekannt)
1169
+ return `hmgr harness home ${fehler.harness} zeigt die eingetragenen Homedirs, add trägt eines ein.`;
1170
+ if (fehler instanceof HeimOhneCliZugriff)
1171
+ return 'Skill-Links dort legt hmgr selbst: hmgr link <skill> --harness claude-code --ort <adresse>.';
1172
+ if (fehler instanceof WslBrueckeFehlgeschlagen)
1173
+ return 'wsl -l -q zeigt die installierten Distros. Startet die Distro nicht, hilft wsl --shutdown und ein erneuter Versuch.';
973
1174
  if (fehler instanceof QuelleUnbekannt)
974
1175
  return 'hmgr source list zeigt die bekannten Quellen.';
975
1176
  if (fehler instanceof QuelleNameBelegt)
@@ -0,0 +1,44 @@
1
+ // Die installierte hmgr-CLI aus Sicht der GUI (E77). Die GUI trägt ihre eigene Version in sich
2
+ // (`paketVersion()`, E72) — die CLI daneben ist ein zweites, unabhängig installiertes Paket, ihre
3
+ // Version steht nur über ihren eigenen Aufruf fest.
4
+ import spawn from 'cross-spawn';
5
+ import { versionLesen } from "../kern/version.js";
6
+ /**
7
+ * `hmgr --version` (E72: nur die Nummer, Exit 0). Null heißt „nichts zu melden": keine CLI im PATH,
8
+ * Fehlercode, Timeout, oder eine Ausgabe, die keine Version ist. Kein Fehler dringt nach außen —
9
+ * eine fehlende CLI ist der Normalfall bei einer reinen GUI-Installation, keine Störung.
10
+ *
11
+ * `spawn` von cross-spawn wie im claude-Adapter (E65/B7): unter Windows ist die npm-installierte
12
+ * `hmgr` ein `.cmd`-Shim, das Node seit dem Fix zu CVE-2024-27980 ohne Umweg über cmd.exe nicht
13
+ * mehr startet. stdin zu und ein Timeout, damit nichts hängen bleibt.
14
+ */
15
+ export function hmgrCliVersion() {
16
+ return new Promise((aufloesen) => {
17
+ let fertig = false;
18
+ const melden = (wert) => {
19
+ if (fertig)
20
+ return;
21
+ fertig = true;
22
+ aufloesen(wert);
23
+ };
24
+ try {
25
+ const kind = spawn('hmgr', ['--version'], {
26
+ stdio: ['ignore', 'pipe', 'ignore'],
27
+ timeout: 10_000,
28
+ killSignal: 'SIGKILL',
29
+ });
30
+ let stdout = '';
31
+ kind.stdout.on('data', (stueck) => (stdout += stueck));
32
+ kind.on('error', () => melden(null));
33
+ kind.on('close', (code) => {
34
+ if (code !== 0)
35
+ return melden(null);
36
+ const text = stdout.trim().split('\n')[0]?.trim() ?? '';
37
+ melden(versionLesen(text) === null ? null : text);
38
+ });
39
+ }
40
+ catch {
41
+ melden(null);
42
+ }
43
+ });
44
+ }