hmgr 0.0.34 → 0.0.36
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/claude-code.js +40 -12
- package/dist/adapter/wsl.js +35 -0
- package/dist/cli/hilfe.js +2 -1
- package/dist/cli/main.js +3 -0
- package/dist/gui/renderer/renderer.js +17 -0
- package/dist/kern/anwendungsfaelle.js +21 -2
- package/package.json +1 -1
|
@@ -22,6 +22,8 @@ function distroOrt(distro) {
|
|
|
22
22
|
return { art: 'home', heim: `wsl:${distro}` };
|
|
23
23
|
}
|
|
24
24
|
const GITIGNORE_ZEILE = '.claude/';
|
|
25
|
+
/** Wie lange „keine claude-CLI erreichbar" gilt, bevor erneut gefragt wird (E103). */
|
|
26
|
+
const ERREICHBARKEIT_NEGATIV_MS = 60_000;
|
|
25
27
|
/**
|
|
26
28
|
* Standard-Ausführer: `claude` vom PATH. Ein Fehlschlag wird zum Code, nicht zur Ausnahme.
|
|
27
29
|
* stdin ist zu, sonst hängt eine CLI, die auf Eingabe wartet, den ganzen Ablauf; der Timeout
|
|
@@ -336,6 +338,10 @@ class ClaudeMcp {
|
|
|
336
338
|
ebenen() {
|
|
337
339
|
return ['home', 'arbeitsverzeichnis'];
|
|
338
340
|
}
|
|
341
|
+
/** Ob am Ort eine claude-CLI antwortet (E103): ohne sie bringt Einhängen oder Aushängen dort nichts. */
|
|
342
|
+
async ortBedienbar(ort) {
|
|
343
|
+
return await this.harness.cliErreichbar(ort);
|
|
344
|
+
}
|
|
339
345
|
hinweisNachAenderung() {
|
|
340
346
|
return 'Claude Code übernimmt die Änderung mit der nächsten Sitzung.';
|
|
341
347
|
}
|
|
@@ -508,7 +514,12 @@ export class ClaudeCode {
|
|
|
508
514
|
*/
|
|
509
515
|
plugins;
|
|
510
516
|
mcp;
|
|
511
|
-
/**
|
|
517
|
+
/**
|
|
518
|
+
* Ob die claude-CLI antwortet, je Welt: Schlüssel '' für Windows, sonst die Distro. Ein positives
|
|
519
|
+
* Ergebnis gilt für die Lebenszeit des Adapters, ein negatives verfällt nach 60 Sekunden (E103), damit
|
|
520
|
+
* eine laufende GUI eine inzwischen installierte claude findet. Solange `bis` fehlt (die Antwort steht
|
|
521
|
+
* aus) oder noch nicht erreicht ist, teilen sich parallele Anfragen das Versprechen.
|
|
522
|
+
*/
|
|
512
523
|
erreichbarkeit = new Map();
|
|
513
524
|
/**
|
|
514
525
|
* `heime` sagt, wo die Homedirs liegen (E78): das primäre und, seit F87, zusätzliche in einer
|
|
@@ -536,6 +547,7 @@ export class ClaudeCode {
|
|
|
536
547
|
bruecken.set(distro, (b = new WslBruecke(distro)));
|
|
537
548
|
return b;
|
|
538
549
|
}),
|
|
550
|
+
jetzt: wsl?.jetzt ?? (() => Date.now()),
|
|
539
551
|
distros: wsl?.distros ??
|
|
540
552
|
(async () => this.heime
|
|
541
553
|
.zusaetzliche(this.name)
|
|
@@ -551,18 +563,32 @@ export class ClaudeCode {
|
|
|
551
563
|
bruecke(distro) {
|
|
552
564
|
return this.wsl.bruecke(distro);
|
|
553
565
|
}
|
|
554
|
-
/**
|
|
566
|
+
/**
|
|
567
|
+
* Ob an diesem Ort eine claude-CLI antwortet (`claude --version`), je Welt: ein „ja" bleibt, ein
|
|
568
|
+
* „nein" gilt 60 Sekunden (E103).
|
|
569
|
+
*/
|
|
555
570
|
async erreichbar(ort) {
|
|
556
571
|
const schluessel = wslDistroVonOrt(ort) ?? '';
|
|
557
|
-
|
|
558
|
-
if (
|
|
559
|
-
|
|
560
|
-
|
|
572
|
+
const bekannt = this.erreichbarkeit.get(schluessel);
|
|
573
|
+
if (bekannt !== undefined && (bekannt.bis === undefined || this.wsl.jetzt() < bekannt.bis))
|
|
574
|
+
return await bekannt.versprechen;
|
|
575
|
+
const cli = schluessel === '' ? this.ausfuehrer : this.ausfuehrerFuer(ort);
|
|
576
|
+
const eintrag = {
|
|
577
|
+
versprechen: cli(['--version'])
|
|
561
578
|
.then(({ code }) => code === 0)
|
|
562
|
-
.catch(() => false)
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
579
|
+
.catch(() => false)
|
|
580
|
+
.then((ok) => {
|
|
581
|
+
if (!ok)
|
|
582
|
+
eintrag.bis = this.wsl.jetzt() + ERREICHBARKEIT_NEGATIV_MS;
|
|
583
|
+
return ok;
|
|
584
|
+
}),
|
|
585
|
+
};
|
|
586
|
+
this.erreichbarkeit.set(schluessel, eintrag);
|
|
587
|
+
return await eintrag.versprechen;
|
|
588
|
+
}
|
|
589
|
+
/** Ob an diesem Ort eine claude-CLI antwortet, ohne zu werfen (E103): für Plätze, die ohne CLI nichts bringen. */
|
|
590
|
+
async cliErreichbar(ort) {
|
|
591
|
+
return await this.erreichbar(ort);
|
|
566
592
|
}
|
|
567
593
|
/** Wirft `ClaudeCliNichtErreichbar`, wenn an diesem Ort keine claude-CLI antwortet (E100). */
|
|
568
594
|
async cliPruefen(ort) {
|
|
@@ -578,8 +604,10 @@ export class ClaudeCode {
|
|
|
578
604
|
* Windows durchläuft — oder, wenn dort keine CLI liegt, sobald sie in der Distro eines
|
|
579
605
|
* eingetragenen WSL-Orts antwortet. Die Distros werden erst dann befragt, nacheinander bis zur
|
|
580
606
|
* ersten Antwort, damit ein Rechner mit Windows-claude keinen wsl.exe-Start zahlt. Jede Welt wird
|
|
581
|
-
* einmal
|
|
582
|
-
* dazu, ohne die schon geprüften erneut zu fragen.
|
|
607
|
+
* einmal geprüft (ein „ja" bleibt); in der GUI kommt ein später eingetragener WSL-Ort beim nächsten Aufruf
|
|
608
|
+
* dazu, ohne die schon geprüften erneut zu fragen. Ein negatives Ergebnis verfällt nach 60 Sekunden
|
|
609
|
+
* (E103): ein erneuter Aufruf danach fragt noch einmal und setzt die Fähigkeiten, sobald claude
|
|
610
|
+
* inzwischen da ist.
|
|
583
611
|
*/
|
|
584
612
|
async vorhanden() {
|
|
585
613
|
const primaer = this.heime.primaer(this.name);
|
package/dist/adapter/wsl.js
CHANGED
|
@@ -51,6 +51,33 @@ export function wslClaudeAusfuehrer(distro, ausfuehren) {
|
|
|
51
51
|
return { stdout: ausgabe.toString('utf8'), code };
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
/** Die Zeile, die dem `env` im Umgebungs-Skript vorausgeht; was davor steht (MOTD, Profil-Echo), ist Rauschen. */
|
|
55
|
+
export const UMGEBUNG_MARKER = '__HMGR_ENV__';
|
|
56
|
+
/**
|
|
57
|
+
* Liest die Umgebung einer Distro (E104): die Login-Shell des Benutzers, interaktiv und als Login
|
|
58
|
+
* (`-lic`), wie sie im Terminal der Distro läuft und `~/.profile` wie `~/.bashrc` liest. Die Shell
|
|
59
|
+
* kann eine MOTD vor die eigene Ausgabe schreiben, deshalb steht der Marker davor.
|
|
60
|
+
*/
|
|
61
|
+
export const UMGEBUNG_IN_WSL_SKRIPT = 's=$(getent passwd "$(id -un)" 2>/dev/null | cut -d: -f7); [ -x "$s" ] || s=/bin/sh; ' +
|
|
62
|
+
`exec "$s" -lic 'echo ${UMGEBUNG_MARKER}; env'`;
|
|
63
|
+
/**
|
|
64
|
+
* Die Variablen aus der Ausgabe von `UMGEBUNG_IN_WSL_SKRIPT`: nur was nach der letzten Markerzeile
|
|
65
|
+
* steht, Zeilen `NAME=wert` (Wert darf `=` enthalten). Fortsetzungszeilen mehrzeiliger Werte passen
|
|
66
|
+
* nicht auf das Namensmuster und fallen weg. Ohne Marker: `undefined`.
|
|
67
|
+
*/
|
|
68
|
+
export function umgebungLesen(text) {
|
|
69
|
+
const zeilen = text.split(/\r?\n/);
|
|
70
|
+
const ab = zeilen.map((z) => z.trim()).lastIndexOf(UMGEBUNG_MARKER);
|
|
71
|
+
if (ab === -1)
|
|
72
|
+
return undefined;
|
|
73
|
+
const variablen = {};
|
|
74
|
+
for (const zeile of zeilen.slice(ab + 1)) {
|
|
75
|
+
const treffer = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(zeile);
|
|
76
|
+
if (treffer !== null)
|
|
77
|
+
variablen[treffer[1]] = treffer[2];
|
|
78
|
+
}
|
|
79
|
+
return variablen;
|
|
80
|
+
}
|
|
54
81
|
/**
|
|
55
82
|
* Eine wsl.exe-eigene Ausgabe als Text (E78, gemessen): UTF-16LE, mit BOM. Node kennt die Kodierung
|
|
56
83
|
* als „utf16le"; die BOM bleibt beim Dekodieren als `\uFEFF` stehen und wird hier entfernt. Ein
|
|
@@ -166,6 +193,14 @@ export class WslBruecke {
|
|
|
166
193
|
return null;
|
|
167
194
|
return { wurzel, relativerPfad: (prefix ?? '').replace(/\/+$/, '') };
|
|
168
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Die Umgebung, die eine im Terminal der Distro gestartete claude sähe (E104); `undefined`, wenn
|
|
198
|
+
* sie nicht lesbar ist (Code ≠ 0, kein Marker, Zeitüberschreitung).
|
|
199
|
+
*/
|
|
200
|
+
async variablen() {
|
|
201
|
+
const { text, code } = await this.exec(['sh', '-c', UMGEBUNG_IN_WSL_SKRIPT]);
|
|
202
|
+
return code === 0 ? umgebungLesen(text) : undefined;
|
|
203
|
+
}
|
|
169
204
|
/** Dasselbe, aber ein Fehlschlag ist kein Fehler: `undefined`. Für lesende Abfragen. */
|
|
170
205
|
async shWeich(skript) {
|
|
171
206
|
const { ausgabe, code } = await this.ausfuehren(['-d', this.distro, '/bin/sh', '-c', skript]);
|
package/dist/cli/hilfe.js
CHANGED
|
@@ -430,7 +430,8 @@ mit der nächsten Sitzung, Hermes lädt seine config.yaml selbst nach, dafür gi
|
|
|
430
430
|
|
|
431
431
|
Fehlende \${VAR}-Verweise beim Einhängen sind kein Abbruch, nur eine Warnung (F39): hmgr kann nicht
|
|
432
432
|
wissen, was die Shell beim Start des Harness setzt. Geprüft wird gegen die eigene Prozess-Umgebung
|
|
433
|
-
und, bei einem Hermes-Ziel, zusätzlich gegen <hermes-home>/.env.
|
|
433
|
+
und, bei einem Hermes-Ziel, zusätzlich gegen <hermes-home>/.env. Bei einem Ziel in WSL gilt statt der
|
|
434
|
+
Prozess-Umgebung die der Distro, wie ihre Login-Shell sie im Terminal setzt (Login-Shell, interaktiv).
|
|
434
435
|
|
|
435
436
|
Schalter (add, remove und migrate):
|
|
436
437
|
--harness <name> claude-code oder hermes, Standard claude-code
|
package/dist/cli/main.js
CHANGED
|
@@ -63,6 +63,8 @@ export function bauen(umgebung, optionen = {}) {
|
|
|
63
63
|
// der Bridge, `mcp add-json` startet in der Distro eine ganze Node-Laufzeit.
|
|
64
64
|
const windowsClaude = optionen.claudeAusfuehrer ?? claudeAusfuehrer;
|
|
65
65
|
const wslClaudeAusfuehren = optionen.wslAusfuehrer ?? wslAusfuehrerMit(120_000);
|
|
66
|
+
// Die Umgebung der Distro (E104): eine interaktive Login-Shell, die sich nicht melden darf.
|
|
67
|
+
const wslUmgebungAusfuehren = optionen.wslAusfuehrer ?? wslAusfuehrerMit(15_000);
|
|
66
68
|
const claudeFuerOrt = (ort) => {
|
|
67
69
|
const distro = wslDistroVonOrt(ort);
|
|
68
70
|
return distro === undefined ? windowsClaude : wslClaudeAusfuehrer(distro, wslClaudeAusfuehren);
|
|
@@ -99,6 +101,7 @@ export function bauen(umgebung, optionen = {}) {
|
|
|
99
101
|
distros: async () => await wslDistros(wslAusfuehren),
|
|
100
102
|
home: async (distro) => await bruecke(distro).home(),
|
|
101
103
|
gitWurzel: async (distro, linuxPfad) => await bruecke(distro).gitWurzel(linuxPfad),
|
|
104
|
+
variablen: async (distro) => await new WslBruecke(distro, wslUmgebungAusfuehren).variablen(),
|
|
102
105
|
},
|
|
103
106
|
verlauf,
|
|
104
107
|
};
|
|
@@ -1202,6 +1202,23 @@
|
|
|
1202
1202
|
var verzMcpZiele = MCP_PLAETZE.filter(function (p) {
|
|
1203
1203
|
return p.ort.art === 'arbeitsverzeichnis' && p.ort.pfad === pfad;
|
|
1204
1204
|
});
|
|
1205
|
+
// E103: mcpPlaetze() lässt Orte weg, an denen der Harness nicht bedienbar ist (keine CLI). Hängt
|
|
1206
|
+
// dort schon etwas, bleibt der Abschnitt samt Entfernen-Knopf trotzdem stehen; nur das
|
|
1207
|
+
// „hinzufügen" entfällt (mcpKannAn).
|
|
1208
|
+
var gesehen = {};
|
|
1209
|
+
verzMcpZiele.forEach(function (p) { gesehen[zielKey(p)] = true; });
|
|
1210
|
+
var vorhandene = [];
|
|
1211
|
+
MCP_KATALOG.forEach(function (k) {
|
|
1212
|
+
k.eingehaengt.forEach(function (z) { vorhandene.push(z); });
|
|
1213
|
+
k.geteilteFundorte.forEach(function (g) { vorhandene.push(g.ziel); });
|
|
1214
|
+
});
|
|
1215
|
+
MCP_FREMD.forEach(function (x) { vorhandene.push(x.ziel); });
|
|
1216
|
+
MCP_FREMD_GETEILT.forEach(function (x) { vorhandene.push(x.ziel); });
|
|
1217
|
+
vorhandene.forEach(function (z) {
|
|
1218
|
+
if (z.ort.art !== 'arbeitsverzeichnis' || z.ort.pfad !== pfad || gesehen[zielKey(z)]) return;
|
|
1219
|
+
gesehen[zielKey(z)] = true;
|
|
1220
|
+
verzMcpZiele.push(z);
|
|
1221
|
+
});
|
|
1205
1222
|
|
|
1206
1223
|
if (reihenfolge.length === 0) {
|
|
1207
1224
|
var leer = e('div', 'leerzeile');
|
|
@@ -402,16 +402,30 @@ export class Anwendungsfaelle {
|
|
|
402
402
|
const verweise = variablenVerweise(eintrag.konfig);
|
|
403
403
|
if (verweise.length === 0)
|
|
404
404
|
return undefined;
|
|
405
|
-
|
|
405
|
+
// Ein WSL-Ziel startet claude in der Distro mit deren Umgebung, nicht mit der von Windows (E104).
|
|
406
|
+
const distro = wslDistroVonOrt(ort);
|
|
407
|
+
let prozessEnv;
|
|
408
|
+
if (distro === undefined) {
|
|
409
|
+
prozessEnv = this.p.umgebung.variablen();
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
const distroEnv = await this.p.wsl.variablen(distro).catch(() => undefined);
|
|
413
|
+
if (distroEnv === undefined) {
|
|
414
|
+
return (`hmgr konnte die Umgebung der WSL-Distro ${distro} nicht lesen; die Verweise ` +
|
|
415
|
+
`${verweise.map((v) => '${' + v + '}').join(', ')} sind deshalb ungeprüft.`);
|
|
416
|
+
}
|
|
417
|
+
prozessEnv = distroEnv;
|
|
418
|
+
}
|
|
406
419
|
const envPfad = faehig.mcp.envDatei?.(ort);
|
|
407
420
|
const zusatzEnv = envPfad === undefined ? {} : await this.p.umgebung.envDatei(envPfad);
|
|
408
421
|
const fehlend = verweise.filter((v) => !(v in prozessEnv) && !(v in zusatzEnv));
|
|
409
422
|
if (fehlend.length === 0)
|
|
410
423
|
return undefined;
|
|
411
424
|
const mehrzahl = fehlend.length > 1;
|
|
425
|
+
const wo = distro === undefined ? 'aktuellen Umgebung' : `Umgebung der WSL-Distro ${distro} (Login-Shell, interaktiv)`;
|
|
412
426
|
return (`hmgr kann nicht wissen, was die Shell beim Start des Harness setzt: ` +
|
|
413
427
|
`${mehrzahl ? 'Variablen' : 'Variable'} ${fehlend.join(', ')} ${mehrzahl ? 'sind' : 'ist'} weder in der ` +
|
|
414
|
-
|
|
428
|
+
`${wo} noch in einer bekannten .env-Datei zu finden.`);
|
|
415
429
|
}
|
|
416
430
|
/**
|
|
417
431
|
* MCP einhängen (E61): Katalog-Eintrag an ein Ziel legen, Projekt oder global. Die Fähigkeit hängt
|
|
@@ -499,10 +513,15 @@ export class Anwendungsfaelle {
|
|
|
499
513
|
* Die Plätze für den Einhängen-Dialog der GUI (E61-Folgerunde, F42): dieselbe Liste wie in
|
|
500
514
|
* `mcpUebersicht`, aber roh, ohne den Abgleich mit Katalog-Einträgen. Die Fähigkeit hängt am
|
|
501
515
|
* Adapter, nicht am Harness-Namen (E16): agents kommt nie vor, weil es `mcp` nicht implementiert.
|
|
516
|
+
* Orte, an denen der Harness nicht bedienbar ist (E103, etwa keine claude-CLI), fehlen; die
|
|
517
|
+
* Übersicht lässt sie stehen, damit vorhandene Einträge sichtbar bleiben.
|
|
502
518
|
*/
|
|
503
519
|
async mcpPlaetze() {
|
|
504
520
|
const plaetze = [];
|
|
505
521
|
for (const ziel of await this.mcpZiele()) {
|
|
522
|
+
const mcp = this.harnessMitMcp(ziel.harness).mcp;
|
|
523
|
+
if (mcp.ortBedienbar !== undefined && !(await mcp.ortBedienbar(ziel.ort)))
|
|
524
|
+
continue;
|
|
506
525
|
const wurzel = await this.mcpGitWurzelFuer(this.harnessMitMcp(ziel.harness).mcp, ziel);
|
|
507
526
|
plaetze.push(wurzel === null ? ziel : { ...ziel, gitWurzel: wurzel });
|
|
508
527
|
}
|