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/adapter/agents.js +14 -5
- package/dist/adapter/claude-code.js +53 -12
- package/dist/adapter/dateisystem.js +85 -43
- package/dist/adapter/heime.js +76 -0
- package/dist/adapter/hermes.js +16 -7
- package/dist/adapter/npm-registry.js +61 -0
- package/dist/adapter/umgebung.js +6 -1
- package/dist/adapter/verlauf-json.js +39 -0
- package/dist/adapter/wsl.js +212 -0
- package/dist/adapter/zustand-json.js +36 -15
- package/dist/cli/hilfe.js +100 -13
- package/dist/cli/main.js +219 -18
- package/dist/gui/hmgr-cli.js +44 -0
- package/dist/gui/main.js +344 -4
- package/dist/gui/preload.cjs +26 -2
- package/dist/gui/renderer/index.html +67 -15
- package/dist/gui/renderer/renderer.js +441 -228
- package/dist/gui/renderer/styles.css +69 -16
- package/dist/kern/anwendungsfaelle.js +244 -16
- package/dist/kern/modell.js +112 -1
- package/dist/kern/version.js +13 -0
- package/dist/ports/dateizugriff.js +1 -0
- package/dist/ports/harness.js +4 -0
- package/dist/ports/heime.js +1 -0
- package/dist/ports/verlauf.js +4 -0
- package/dist/ports/zustand.js +4 -3
- package/package.json +7 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// Die Bridge in eine WSL-Distro (E78): `wsl.exe -d <distro> <linux-kommando>`. Über `\\wsl$` geht
|
|
2
|
+
// nichts davon — Windows legt dort keine Links an und liest durch Linux-Symlinks nicht hindurch
|
|
3
|
+
// (gemessen, HARNESS-FAKTEN „Links zwischen Windows und WSL").
|
|
4
|
+
import spawn from 'cross-spawn';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { WslBrueckeFehlgeschlagen } from "../kern/modell.js";
|
|
7
|
+
/**
|
|
8
|
+
* Standard-Ausführer: `wsl.exe` vom PATH, über cross-spawn wie die claude-CLI (E65/B7). stdin ist
|
|
9
|
+
* zu, sonst hängt eine Distro, die auf Eingabe wartet; der Timeout fängt alles Übrige. Ein
|
|
10
|
+
* Fehlschlag wird zum Code, nicht zur Ausnahme.
|
|
11
|
+
*/
|
|
12
|
+
export const wslAusfuehrer = (args) => new Promise((aufloesen) => {
|
|
13
|
+
const kind = spawn('wsl.exe', args, { stdio: ['ignore', 'pipe', 'pipe'], timeout: 60_000, killSignal: 'SIGKILL' });
|
|
14
|
+
const teile = [];
|
|
15
|
+
const fehlerTeile = [];
|
|
16
|
+
kind.stdout.on('data', (d) => teile.push(d));
|
|
17
|
+
kind.stderr.on('data', (d) => fehlerTeile.push(d));
|
|
18
|
+
kind.on('error', (fehler) => aufloesen({ ausgabe: Buffer.from(fehler.message, 'utf8'), code: 1 }));
|
|
19
|
+
kind.on('close', (code) => aufloesen({ ausgabe: Buffer.concat(code === 0 ? teile : [...teile, ...fehlerTeile]), code: code ?? 1 }));
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Eine wsl.exe-eigene Ausgabe als Text (E78, gemessen): UTF-16LE, mit BOM. Node kennt die Kodierung
|
|
23
|
+
* als „utf16le"; die BOM bleibt beim Dekodieren als `\uFEFF` stehen und wird hier entfernt. Ein
|
|
24
|
+
* ungerades Byte am Ende (abgeschnittene Ausgabe) fällt weg statt ein Ersatzzeichen zu ergeben.
|
|
25
|
+
*/
|
|
26
|
+
export function utf16Text(ausgabe) {
|
|
27
|
+
const gerade = ausgabe.length % 2 === 0 ? ausgabe : ausgabe.subarray(0, ausgabe.length - 1);
|
|
28
|
+
return gerade.toString('utf16le').replace(/^\uFEFF/, '');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Die Distronamen aus `wsl -l -q` (E78): eine je Zeile, UTF-16LE. Leerzeilen und Reste des
|
|
32
|
+
* Zeilenumbruchs fallen weg. Der Standard-Distro hängt in anderen Listenformen ein „(Standard)" an,
|
|
33
|
+
* `-q` liefert nur die nackten Namen.
|
|
34
|
+
*/
|
|
35
|
+
export function distroListeLesen(ausgabe) {
|
|
36
|
+
return utf16Text(ausgabe)
|
|
37
|
+
.split(/\r?\n/)
|
|
38
|
+
.map((zeile) => zeile.trim())
|
|
39
|
+
.filter((zeile) => zeile.length > 0);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Ein Wert für die einfach zitierte Form der Shell: alles bleibt wörtlich, ein enthaltenes
|
|
43
|
+
* Apostroph wird ausgeklammert. Ohne das zerfiele ein Pfad mit Leerzeichen in mehrere Argumente,
|
|
44
|
+
* sobald wsl.exe die Kommandozeile wieder zusammensetzt.
|
|
45
|
+
*/
|
|
46
|
+
export function shZitat(text) {
|
|
47
|
+
return `'${text.replace(/'/g, `'\\''`)}'`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Ein POSIX-Pfad in die Windows-Schreibweise (E78): `/mnt/c/work/quelle` wird `C:\work\quelle`.
|
|
51
|
+
* `wurzel` ist die Automount-Wurzel der Distro, üblich `/mnt`, aber über `[automount] root`
|
|
52
|
+
* einstellbar — deshalb wird sie einmal per `wslpath` ermittelt statt geraten. Ein Pfad außerhalb
|
|
53
|
+
* (im Linux-Dateisystem) hat keine Windows-Entsprechung: `undefined`.
|
|
54
|
+
*/
|
|
55
|
+
export function wslNachWindows(pfad, wurzel) {
|
|
56
|
+
const basis = wurzel.endsWith('/') ? wurzel.slice(0, -1) : wurzel;
|
|
57
|
+
const treffer = new RegExp(`^${basis.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/([a-zA-Z])(/.*)?$`).exec(pfad);
|
|
58
|
+
if (treffer === null)
|
|
59
|
+
return undefined;
|
|
60
|
+
const rest = (treffer[2] ?? '').replace(/\//g, '\\');
|
|
61
|
+
return `${treffer[1].toUpperCase()}:${rest.length === 0 ? '\\' : rest}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Die Gegenrichtung: `C:\work\quelle` wird `/mnt/c/work/quelle`. Ein Pfad ohne Laufwerksbuchstaben
|
|
65
|
+
* (UNC, relativ) hat keine Entsprechung unter der Automount-Wurzel: `undefined`.
|
|
66
|
+
*/
|
|
67
|
+
export function windowsNachWsl(pfad, wurzel) {
|
|
68
|
+
const treffer = /^([a-zA-Z]):[\\/](.*)$/.exec(pfad);
|
|
69
|
+
if (treffer === null)
|
|
70
|
+
return undefined;
|
|
71
|
+
const basis = wurzel.endsWith('/') ? wurzel.slice(0, -1) : wurzel;
|
|
72
|
+
const rest = treffer[2].replace(/\\/g, '/').replace(/\/+$/, '');
|
|
73
|
+
return `${basis}/${treffer[1].toLowerCase()}${rest.length === 0 ? '' : `/${rest}`}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Eine WSL-Distro über die Bridge. Jede Dateisystem-Operation läuft als `/bin/sh -c "<skript>"` in
|
|
77
|
+
* der Distro; Pfade gehen einfach zitiert hinein (`shZitat`), weil wsl.exe die Kommandozeile wieder
|
|
78
|
+
* zusammensetzt und ein Leerzeichen sonst trennte.
|
|
79
|
+
*/
|
|
80
|
+
export class WslBruecke {
|
|
81
|
+
distro;
|
|
82
|
+
ausfuehren;
|
|
83
|
+
/** Automount-Wurzel, einmal je Lauf ermittelt: ein `wslpath`-Aufruf statt einem je Linkziel. */
|
|
84
|
+
wurzelVersprechen;
|
|
85
|
+
constructor(distro, ausfuehren = wslAusfuehrer) {
|
|
86
|
+
this.distro = distro;
|
|
87
|
+
this.ausfuehren = ausfuehren;
|
|
88
|
+
}
|
|
89
|
+
/** Ein Linux-Kommando in der Distro, Ausgabe als UTF-8. Wirft, wenn die Bridge selbst scheitert. */
|
|
90
|
+
async sh(skript) {
|
|
91
|
+
const { ausgabe, code } = await this.ausfuehren(['-d', this.distro, '/bin/sh', '-c', skript]);
|
|
92
|
+
const text = ausgabe.toString('utf8');
|
|
93
|
+
if (code !== 0)
|
|
94
|
+
throw new WslBrueckeFehlgeschlagen(this.distro, text);
|
|
95
|
+
return text;
|
|
96
|
+
}
|
|
97
|
+
/** Dasselbe, aber ein Fehlschlag ist kein Fehler: `undefined`. Für lesende Abfragen. */
|
|
98
|
+
async shWeich(skript) {
|
|
99
|
+
const { ausgabe, code } = await this.ausfuehren(['-d', this.distro, '/bin/sh', '-c', skript]);
|
|
100
|
+
return code === 0 ? ausgabe.toString('utf8') : undefined;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Das Home des Standardbenutzers der Distro (F87). wsl.exe startet als dieser Benutzer — derselbe,
|
|
104
|
+
* der Claude Code dort interaktiv nutzt; eine Benutzer-Option gibt es deshalb nicht.
|
|
105
|
+
*/
|
|
106
|
+
async home() {
|
|
107
|
+
const text = (await this.sh('echo $HOME')).trim();
|
|
108
|
+
if (text.length === 0)
|
|
109
|
+
throw new WslBrueckeFehlgeschlagen(this.distro, 'kein $HOME in der Distro');
|
|
110
|
+
return text;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Die Automount-Wurzel, aus `wslpath -u 'C:\'`. wslpath kommt von WSL selbst und liegt auch in
|
|
114
|
+
* einer Minimal-Distro (gemessen). Schlägt der Aufruf fehl, gilt der Standard `/mnt`: eine
|
|
115
|
+
* Übersetzung mit der üblichen Wurzel ist besser als gar keine.
|
|
116
|
+
*/
|
|
117
|
+
async wurzel() {
|
|
118
|
+
this.wurzelVersprechen ??= (async () => {
|
|
119
|
+
const text = await this.shWeich(`wslpath -u ${shZitat('C:\\')}`);
|
|
120
|
+
const pfad = text?.trim() ?? '';
|
|
121
|
+
const treffer = /^(.*)\/[a-zA-Z]\/?$/.exec(pfad);
|
|
122
|
+
return treffer?.[1] !== undefined && treffer[1].length > 0 ? treffer[1] : '/mnt';
|
|
123
|
+
})();
|
|
124
|
+
return this.wurzelVersprechen;
|
|
125
|
+
}
|
|
126
|
+
/** Einen Pfad der Distro in die Schreibweise des Windows-Rechners. Ohne Entsprechung: unverändert. */
|
|
127
|
+
async nachHost(pfad) {
|
|
128
|
+
return wslNachWindows(pfad, await this.wurzel()) ?? pfad;
|
|
129
|
+
}
|
|
130
|
+
/** Die Gegenrichtung. Ohne Entsprechung: unverändert — der Link zeigt dann eben ins Leere. */
|
|
131
|
+
async vomHost(pfad) {
|
|
132
|
+
return windowsNachWsl(pfad, await this.wurzel()) ?? pfad;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Die installierten Distros (`wsl -l -q`). Ohne wsl.exe oder ohne Distro: leere Liste. */
|
|
136
|
+
export async function wslDistros(ausfuehren = wslAusfuehrer) {
|
|
137
|
+
const { ausgabe, code } = await ausfuehren(['-l', '-q']);
|
|
138
|
+
if (code !== 0)
|
|
139
|
+
return [];
|
|
140
|
+
return distroListeLesen(ausgabe);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Zugriff auf ein Zielverzeichnis in einer WSL-Distro (E78). Jede Operation ist ein Aufruf über die
|
|
144
|
+
* Bridge; Pfade sind POSIX-Pfade der Distro, Linkziele übersetzt der Zugriff selbst nach `/mnt/c`
|
|
145
|
+
* und zurück. Ein Symlink im WSL-Home darf auf `/mnt/c/…` zeigen — die Distro liest selbst
|
|
146
|
+
* hindurch, also bleiben die vorhandenen Windows-Checkouts die Linkziele (gemessen).
|
|
147
|
+
*/
|
|
148
|
+
export class WslZugriff {
|
|
149
|
+
trenner = '/';
|
|
150
|
+
bruecke;
|
|
151
|
+
constructor(bruecke) {
|
|
152
|
+
this.bruecke = bruecke;
|
|
153
|
+
}
|
|
154
|
+
verbinden(verzeichnis, name) {
|
|
155
|
+
return path.posix.join(verzeichnis, name);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* `ls -1A` statt `find -printf`: letzteres ist eine GNU-Erweiterung und fehlt in einer
|
|
159
|
+
* Busybox-Distro. Ein Name mit Zeilenumbruch zerfiele dabei — bei Skill-Ordnern kommt das nicht
|
|
160
|
+
* vor, und der Preis wäre ein Sonderformat für einen Fall, den es nicht gibt.
|
|
161
|
+
*/
|
|
162
|
+
async namen(verzeichnis) {
|
|
163
|
+
const text = await this.weich(`ls -1A ${shZitat(verzeichnis)} 2>/dev/null`);
|
|
164
|
+
if (text === undefined)
|
|
165
|
+
return [];
|
|
166
|
+
return text
|
|
167
|
+
.split('\n')
|
|
168
|
+
.map((zeile) => zeile.replace(/\r$/, ''))
|
|
169
|
+
.filter((zeile) => zeile.length > 0);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Drei Ja/Nein-Fragen in einem Aufruf: gibt es den Eintrag (auch als toten Link), ist er ein Link,
|
|
173
|
+
* zeigt er auf ein Verzeichnis. `-d` folgt dem Link, `-L` nicht — genau die Aufteilung, die der
|
|
174
|
+
* lokale Zugriff mit lstat und stat trifft.
|
|
175
|
+
*/
|
|
176
|
+
async info(pfad) {
|
|
177
|
+
const p = shZitat(pfad);
|
|
178
|
+
const text = await this.weich(`if [ -e ${p} ] || [ -L ${p} ]; then printf e; fi; if [ -L ${p} ]; then printf l; fi; if [ -d ${p} ]; then printf d; fi`);
|
|
179
|
+
if (text === undefined || !text.includes('e'))
|
|
180
|
+
return undefined;
|
|
181
|
+
return { istLink: text.includes('l'), istVerzeichnis: text.includes('d') };
|
|
182
|
+
}
|
|
183
|
+
async linkZiel(pfad) {
|
|
184
|
+
const text = await this.weich(`readlink ${shZitat(pfad)}`);
|
|
185
|
+
if (text === undefined)
|
|
186
|
+
return undefined;
|
|
187
|
+
const ziel = text.trim();
|
|
188
|
+
if (ziel.length === 0)
|
|
189
|
+
return undefined;
|
|
190
|
+
// Ein relatives Linkziel gibt es bei hmgr-Links nicht, aber ein von Hand gelegter Link könnte
|
|
191
|
+
// eines haben: erst gegen das Verzeichnis auflösen, dann übersetzen.
|
|
192
|
+
const absolut = ziel.startsWith('/') ? ziel : path.posix.resolve(path.posix.dirname(pfad), ziel);
|
|
193
|
+
return await this.bruecke.nachHost(absolut);
|
|
194
|
+
}
|
|
195
|
+
async verzeichnisAnlegen(pfad) {
|
|
196
|
+
await this.bruecke.sh(`mkdir -p ${shZitat(pfad)}`);
|
|
197
|
+
}
|
|
198
|
+
async linkAnlegen(pfad, hostZiel) {
|
|
199
|
+
const ziel = await this.bruecke.vomHost(hostZiel);
|
|
200
|
+
await this.bruecke.sh(`ln -sfn ${shZitat(ziel)} ${shZitat(pfad)}`);
|
|
201
|
+
}
|
|
202
|
+
async linkEntfernen(pfad) {
|
|
203
|
+
await this.bruecke.sh(`rm -f ${shZitat(pfad)}`);
|
|
204
|
+
}
|
|
205
|
+
async loeschen(pfad) {
|
|
206
|
+
await this.bruecke.sh(`rm -rf ${shZitat(pfad)}`);
|
|
207
|
+
}
|
|
208
|
+
/** Lesende Abfragen brechen nicht ab, wenn die Distro schweigt: sie ergeben dann „nichts da". */
|
|
209
|
+
async weich(skript) {
|
|
210
|
+
return await this.bruecke.sh(skript).catch(() => undefined);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
// Lokaler Zustand als eine JSON-Datei, Standard ~/.hmgr/state.json (E29).
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { ZUSTAND_FASSUNG, leererZustand, } from "../ports/zustand.js";
|
|
5
6
|
import { ohneBom } from "./datei-text.js";
|
|
7
|
+
/** Der gelesene Text als Zustand. Fehlende Felder bekommen den Wert, der dem Bestand entspricht. */
|
|
8
|
+
function deuten(roh) {
|
|
9
|
+
const daten = JSON.parse(ohneBom(roh));
|
|
10
|
+
return {
|
|
11
|
+
// Fehlt das Feld (state.json vor E69), ist es die erste Fassung: alles ohne Bundles.
|
|
12
|
+
// Geschrieben wird danach in der aktuellen Fassung, ein Rückweg ist nicht vorgesehen.
|
|
13
|
+
fassung: typeof daten.fassung === 'number' ? daten.fassung : 1,
|
|
14
|
+
quellen: (daten.quellen ?? []),
|
|
15
|
+
arbeitsverzeichnisse: (daten.arbeitsverzeichnisse ?? []),
|
|
16
|
+
bindungen: (daten.bindungen ?? []),
|
|
17
|
+
// Fehlt das Feld (state.json vor E38), gilt der bisherige Ist-Zustand: nur claude-code aktiv.
|
|
18
|
+
aktiveHarnesses: (daten.aktiveHarnesses ?? ['claude-code']),
|
|
19
|
+
// Fehlt das Feld (state.json vor E43), gilt: nichts ignoriert.
|
|
20
|
+
ignorierteEintraege: (daten.ignorierteEintraege ?? []),
|
|
21
|
+
// Fehlt das Feld (state.json vor E69), gilt: kein Bundle installiert.
|
|
22
|
+
bundleInstallationen: (daten.bundleInstallationen ?? []),
|
|
23
|
+
// Fehlt das Feld (state.json vor E78), gilt: jeder Harness hat nur sein Standard-Homedir.
|
|
24
|
+
harnessHeime: (daten.harnessHeime ?? []),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
6
27
|
export class ZustandJson {
|
|
7
28
|
pfad;
|
|
8
29
|
constructor(pfad) {
|
|
@@ -12,21 +33,21 @@ export class ZustandJson {
|
|
|
12
33
|
const roh = await readFile(this.pfad, 'utf8').catch(() => undefined);
|
|
13
34
|
if (roh === undefined)
|
|
14
35
|
return leererZustand();
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
36
|
+
return deuten(roh);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Derselbe Zustand, synchron gelesen (E78). Nötig für die Auflösung der Homedirs: sie hängt an
|
|
40
|
+
* `HarnessTyp.zielVerzeichnis`, und das ist synchron. Ein kaputter oder fehlender Zustand ergibt
|
|
41
|
+
* hier den leeren — die Homedir-Auflösung darf keine Ansicht abbrechen, sie fällt dann auf das
|
|
42
|
+
* Home des Nutzers zurück. Nicht Teil von `ZustandPort`: dort bleibt alles asynchron.
|
|
43
|
+
*/
|
|
44
|
+
lesenSync() {
|
|
45
|
+
try {
|
|
46
|
+
return deuten(readFileSync(this.pfad, 'utf8'));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return leererZustand();
|
|
50
|
+
}
|
|
30
51
|
}
|
|
31
52
|
async schreiben(zustand) {
|
|
32
53
|
await mkdir(path.dirname(this.pfad), { recursive: true });
|
package/dist/cli/hilfe.js
CHANGED
|
@@ -33,9 +33,11 @@ hmgr <kommando> [schalter]
|
|
|
33
33
|
sync [pfad] Ziel auf den Stand seines Zeigers bringen
|
|
34
34
|
update Alle Checkouts aktualisieren
|
|
35
35
|
status Ist-Zustand der Ziele
|
|
36
|
+
log Verlauf der letzten Läufe (Changelog)
|
|
36
37
|
harness list Harness-Typen mit Stand aktiv/inaktiv und vorhanden
|
|
37
38
|
harness enable <name> Harness-Typ aktivieren, hmgr richtet ihn künftig ein
|
|
38
39
|
harness disable <name> Harness-Typ deaktivieren, mit Angebot zum Säubern
|
|
40
|
+
harness home <name> Homedirs zeigen, umstellen oder ein zusätzliches eintragen
|
|
39
41
|
harness skills-dir <name> Verwalteten Skill-Ordner zeigen, festlegen oder entfernen
|
|
40
42
|
--version Versionsnummer von hmgr ausgeben
|
|
41
43
|
|
|
@@ -128,7 +130,8 @@ Arbeitsverzeichnis mit Zeiger bricht link ohne --local ab, der Link wird nicht g
|
|
|
128
130
|
|
|
129
131
|
Schalter:
|
|
130
132
|
--harness <name> claude-code, hermes oder agents, Standard claude-code
|
|
131
|
-
--ort <ort> home, dir:<pfad> oder profil:<name>, Standard home. profil
|
|
133
|
+
--ort <ort> home, wsl:<distro>, dir:<pfad> oder profil:<name>, Standard home. profil
|
|
134
|
+
kennt nur hermes, wsl:<distro> nur claude-code unter Windows (E78)
|
|
132
135
|
--quelle <teil> Teilstring der Quelle, wenn der Skill-Name in mehreren Quellen vorkommt
|
|
133
136
|
--version <spanne> Versionseinschränkung nach Composer-Regeln (etwa ^1.2) oder ein Tag- oder
|
|
134
137
|
Branchname. Nur mit Zeiger, also dir:<pfad> ohne --local (E33)
|
|
@@ -146,8 +149,10 @@ Häufige Fälle:
|
|
|
146
149
|
„liegt bereits ein fremder Eintrag": --ersetzen setzen oder vorher hmgr adopt <name>.
|
|
147
150
|
„gibt es keinen Zeiger": --version braucht ein Arbeitsverzeichnis mit composer.json und kein
|
|
148
151
|
--local. Ohne Zeiger den Link ohne --version legen.
|
|
152
|
+
„ist deaktiviert, hmgr legt dort keine Links an": erst hmgr harness enable <name>, dann erneut
|
|
153
|
+
link. Bestehende Links im deaktivierten Harness bleiben liegen (E76).
|
|
149
154
|
|
|
150
|
-
Siehe auch: hmgr help unlink, hmgr help sync, hmgr help adopt`;
|
|
155
|
+
Siehe auch: hmgr help unlink, hmgr help sync, hmgr help adopt, hmgr help harness`;
|
|
151
156
|
const UNLINK = `hmgr unlink <skill> [schalter]
|
|
152
157
|
|
|
153
158
|
Entfernt den Link aus dem Ziel und, wenn das Ziel an einen Zeiger gebunden ist, den Skill aus dem
|
|
@@ -158,7 +163,8 @@ Arbeitsverzeichnis mit Zeiger bricht unlink ohne --local ab, der Link bleibt lie
|
|
|
158
163
|
|
|
159
164
|
Schalter:
|
|
160
165
|
--harness <name> claude-code, hermes oder agents, Standard claude-code
|
|
161
|
-
--ort <ort> home, dir:<pfad> oder profil:<name>, Standard home. profil
|
|
166
|
+
--ort <ort> home, wsl:<distro>, dir:<pfad> oder profil:<name>, Standard home. profil
|
|
167
|
+
kennt nur hermes, wsl:<distro> nur claude-code unter Windows (E78)
|
|
162
168
|
--composer <pfad> composer.json des Arbeitsverzeichnisses, wenn mehrere in Frage kommen
|
|
163
169
|
--local nur den Link entfernen, den Eintrag im Zeiger stehen lassen
|
|
164
170
|
|
|
@@ -184,7 +190,8 @@ Probelauf bleibt frei, er schreibt ohnehin nichts.
|
|
|
184
190
|
|
|
185
191
|
Schalter:
|
|
186
192
|
--harness <name> claude-code, hermes oder agents, Standard claude-code
|
|
187
|
-
--ort <ort> home, dir:<pfad> oder profil:<name>, Standard home. profil
|
|
193
|
+
--ort <ort> home, wsl:<distro>, dir:<pfad> oder profil:<name>, Standard home. profil
|
|
194
|
+
kennt nur hermes, wsl:<distro> nur claude-code unter Windows (E78)
|
|
188
195
|
--composer <pfad> composer.json des Arbeitsverzeichnisses, wenn mehrere in Frage kommen
|
|
189
196
|
--local Übernahme nicht in den Zeiger schreiben
|
|
190
197
|
--probelauf nur melden, nichts schreiben
|
|
@@ -217,7 +224,9 @@ verlinkt oder entfernt.
|
|
|
217
224
|
|
|
218
225
|
Schalter (add und remove):
|
|
219
226
|
--harness <name> claude-code, hermes oder agents, Standard claude-code
|
|
220
|
-
--ort <ort> home, dir:<pfad> oder profil:<name>, Standard home. profil kennt nur hermes
|
|
227
|
+
--ort <ort> home, dir:<pfad> oder profil:<name>, Standard home. profil kennt nur hermes.
|
|
228
|
+
Ein zusätzliches Homedir (wsl:<distro>) kennt die claude-CLI nicht, dort
|
|
229
|
+
gibt es keine Plugins (E78)
|
|
221
230
|
--composer <pfad> composer.json des Arbeitsverzeichnisses, wenn mehrere in Frage kommen
|
|
222
231
|
--local Plugin nicht in den Zeiger schreiben
|
|
223
232
|
--quelle <teil> nur bei add: Quelle eingrenzen, wenn der Name in mehreren Marketplaces steht
|
|
@@ -297,7 +306,8 @@ composer.json. --local gibt es deshalb nicht, und die Sperre aus E57 trifft bund
|
|
|
297
306
|
|
|
298
307
|
Schalter (add, remove und sync):
|
|
299
308
|
--harness <name> claude-code, hermes oder agents, Standard claude-code
|
|
300
|
-
--ort <ort> home, dir:<pfad> oder profil:<name>, Standard home. profil
|
|
309
|
+
--ort <ort> home, wsl:<distro>, dir:<pfad> oder profil:<name>, Standard home. profil
|
|
310
|
+
kennt nur hermes, wsl:<distro> nur claude-code unter Windows (E78)
|
|
301
311
|
--composer <pfad> composer.json des Arbeitsverzeichnisses, wenn mehrere in Frage kommen
|
|
302
312
|
--quelle <teil> nur bei add: Quelle eingrenzen, wenn den Namen mehrere Quellen führen
|
|
303
313
|
|
|
@@ -314,8 +324,10 @@ Häufige Fälle:
|
|
|
314
324
|
meist eine Quelle ohne Zugriff oder ein Pfad, an dem kein Skill (mehr) liegt.
|
|
315
325
|
„lässt sich in … nicht anlegen": die Meldung listet alle Namen, die am Ziel schon belegt sind.
|
|
316
326
|
„kein Bundle … installiert": hmgr status zeigt je Ziel seine Bundles.
|
|
327
|
+
„ist deaktiviert, hmgr legt dort keine Links an": bundle add braucht einen aktiven Harness, erst
|
|
328
|
+
hmgr harness enable <name>. bundle sync und bundle remove trifft die Sperre nicht (E76).
|
|
317
329
|
|
|
318
|
-
Siehe auch: hmgr help link, hmgr help plugin, hmgr help status`;
|
|
330
|
+
Siehe auch: hmgr help link, hmgr help plugin, hmgr help status, hmgr help harness`;
|
|
319
331
|
const MCP = `hmgr mcp add <name> [schalter]
|
|
320
332
|
hmgr mcp remove <name> [schalter]
|
|
321
333
|
hmgr mcp migrate <name> [schalter]
|
|
@@ -459,6 +471,16 @@ gemeldet. Ein verwaister Checkout mit lokaler Änderung wird nie gelöscht (E15)
|
|
|
459
471
|
So wird eine Änderung an einem Skill überall wirksam: in der Quelle committen und pushen, dann
|
|
460
472
|
hmgr update.
|
|
461
473
|
|
|
474
|
+
Zum Schluss vergleicht update die eigene Version mit der auf npm veröffentlichten (E77). Ist dort
|
|
475
|
+
eine neuere, nennt eine letzte Zeile sie und den Befehl dazu (npm install -g hmgr@latest);
|
|
476
|
+
installiert wird nichts von selbst. Ohne Netz oder ohne neuere Version bleibt die Zeile weg.
|
|
477
|
+
|
|
478
|
+
Ergibt der Lauf mindestens ein Ereignis (aktualisierter Checkout, nachgezogenes Bundle,
|
|
479
|
+
aufgeräumter Checkout, ein Fehler), schreibt update denselben Eintrag auch in ~/.hmgr/verlauf.json
|
|
480
|
+
(E80) — dieselbe Datei, in die auch die GUI schreibt. hmgr log zeigt das Changelog. Plugins stehen
|
|
481
|
+
nicht im Verlauf: „claude plugin marketplace update" liefert keine Auskunft, ob sich etwas geändert
|
|
482
|
+
hat, eine Zeile dazu wäre geschönt.
|
|
483
|
+
|
|
462
484
|
Schalter: keine.
|
|
463
485
|
|
|
464
486
|
Beispiel:
|
|
@@ -471,10 +493,35 @@ Häufige Fälle:
|
|
|
471
493
|
„liegen lassen … ohne Bestätigung nicht gelöscht": die Frage kam ohne Ja zurück, etwa in einer
|
|
472
494
|
Pipe oder einem Composer-Script. In einem Terminal erneut hmgr update aufrufen.
|
|
473
495
|
|
|
474
|
-
Siehe auch: hmgr help sync, hmgr help status`;
|
|
496
|
+
Siehe auch: hmgr help sync, hmgr help status, hmgr help log`;
|
|
497
|
+
const LOG = `hmgr log
|
|
498
|
+
|
|
499
|
+
Zeigt den Verlauf der letzten Läufe aus ~/.hmgr/verlauf.json (E80), neueste zuerst: dieselbe Datei,
|
|
500
|
+
die hmgr update schreibt und die GUI bei jedem automatischen und angestoßenen Lauf schreibt und
|
|
501
|
+
liest. Ein Lauf ohne Ereignis (nichts Neues) steht nicht drin, die Datei ist ein Changelog, keine
|
|
502
|
+
vollständige Historie — sie hält die letzten 20 Einträge.
|
|
503
|
+
|
|
504
|
+
Je Eintrag steht eine Kopfzeile mit Datum und Uhrzeit, darunter je Ereignis eine Zeile: ein
|
|
505
|
+
aktualisierter Checkout mit Quelle, Ref und Commit-Stand samt geänderten Skills (E70), ein
|
|
506
|
+
nachgezogenes Bundle mit gelegten und entfernten Links, ein aufgeräumter (gelöschter) Checkout, ein
|
|
507
|
+
Fehler zu einer Quelle oder einem Bundle, dazu hmgr-Ereignisse: ein Versionssprung von hmgr selbst
|
|
508
|
+
(GUI oder CLI, erkannt beim Start bzw. bei jedem CLI-Aufruf) oder ein gefundenes Update, das noch
|
|
509
|
+
nicht installiert ist. Plugins stehen nicht im Verlauf (siehe hmgr help update).
|
|
510
|
+
|
|
511
|
+
Der Verlauf ist je Rechner: mehrere Maschinen führen ihre eigene Datei, es gibt kein Zusammenführen.
|
|
512
|
+
|
|
513
|
+
Schalter: keine.
|
|
514
|
+
|
|
515
|
+
Beispiel:
|
|
516
|
+
hmgr log
|
|
517
|
+
|
|
518
|
+
Häufige Fälle:
|
|
519
|
+
„Noch keine Ereignisse": entweder frisch installiert, oder jeder bisherige Lauf war ohne Fund.
|
|
520
|
+
|
|
521
|
+
Siehe auch: hmgr help update, hmgr help status`;
|
|
475
522
|
const STATUS = `hmgr status
|
|
476
523
|
|
|
477
|
-
Liest den Ist-Zustand vom Dateisystem: je Ziel eines vorhandenen Harness die Einträge mit Quelle
|
|
524
|
+
Liest den Ist-Zustand vom Dateisystem: je Ziel eines vorhandenen, aktiven Harness die Einträge mit Quelle
|
|
478
525
|
und Ref, davor die Versionseinschränkung, falls der Zeiger eine nennt. Dahinter steht der
|
|
479
526
|
abgeleitete Grund des Links: das Plugin, zu dem er gehört, oder „über Zeiger", von Hand gelegte
|
|
480
527
|
Links bleiben ohne Zusatz. Fremde Einträge und Links ohne bekannte Quelle sind eigens
|
|
@@ -492,11 +539,18 @@ Häufige Fälle:
|
|
|
492
539
|
Zustand steht. Mit hmgr source add <url> die Quelle wieder eintragen.
|
|
493
540
|
„Nicht vorhanden": der Harness hat sein Verzeichnis auf diesem Rechner nicht angelegt, hmgr
|
|
494
541
|
überspringt seine Ziele.
|
|
542
|
+
Ein Ziel fehlt: Ziele inaktiver Harness-Typen zeigt status nicht, auch wenn dort noch Links
|
|
543
|
+
liegen. hmgr harness list zeigt den Stand, hmgr harness enable <name> holt die Ziele zurück.
|
|
495
544
|
|
|
496
545
|
Siehe auch: hmgr help adopt, hmgr help sync`;
|
|
497
546
|
const HARNESS = `hmgr harness list
|
|
498
547
|
hmgr harness enable <name>
|
|
499
548
|
hmgr harness disable <name>
|
|
549
|
+
hmgr harness home <name>
|
|
550
|
+
hmgr harness home <name> set <pfad>
|
|
551
|
+
hmgr harness home <name> reset
|
|
552
|
+
hmgr harness home <name> add wsl:<distro>
|
|
553
|
+
hmgr harness home <name> remove wsl:<distro>
|
|
500
554
|
hmgr harness skills-dir <name> [--profil <name>]
|
|
501
555
|
hmgr harness skills-dir <name> add <pfad> [--profil <name>]
|
|
502
556
|
hmgr harness skills-dir <name> remove [--profil <name>]
|
|
@@ -509,9 +563,27 @@ alle bekannten Harness-Typen mit ihrem Stand: aktiv oder inaktiv, dazu vorhanden
|
|
|
509
563
|
|
|
510
564
|
disable säubert nicht von selbst: liegen noch eigene Links oder eine harness-eigene Konfiguration wie
|
|
511
565
|
Hermes' external_dirs-Eintrag, fragt hmgr, ob sie entfernt werden sollen. Ohne Terminal, etwa in einem
|
|
512
|
-
Script, gilt Nein und die Konfiguration bleibt liegen.
|
|
513
|
-
|
|
514
|
-
|
|
566
|
+
Script, gilt Nein und die Konfiguration bleibt liegen. In ein Ziel eines inaktiven Harness legt hmgr
|
|
567
|
+
keine Links mehr an (E76): hmgr link und hmgr bundle add brechen ab und nennen hmgr harness enable
|
|
568
|
+
<name> als Weg, auch mit ausdrücklich genanntem --harness. Bestehende Links bleiben liegen, hmgr sync
|
|
569
|
+
und hmgr unlink trifft die Sperre nicht.
|
|
570
|
+
|
|
571
|
+
home zeigt die Homedirs eines Harness (E78). set stellt das primäre um: hmgr merkt sich nur den neuen
|
|
572
|
+
Pfad und zeigt ab dann ihn, was am alten Ort liegt, bleibt unberührt liegen — wer umziehen will,
|
|
573
|
+
verlinkt am neuen Ort neu, einen Umzugsassistenten gibt es nicht. reset nimmt die Umstellung zurück,
|
|
574
|
+
ebenfalls ohne etwas zu verschieben.
|
|
575
|
+
|
|
576
|
+
add trägt ein zusätzliches Homedir ein, heute nur eines in einer WSL-Distro: Adresse wsl:<distro>,
|
|
577
|
+
ohne Benutzer-Option, denn wsl.exe startet als Standardbenutzer der Distro — derselbe, der Claude Code
|
|
578
|
+
dort nutzt. Das Home wird einmal ermittelt (wsl -d <distro> /bin/sh -c 'echo $HOME'), angezeigt und
|
|
579
|
+
gespeichert. Jedes Homedir ist danach ein eigenständiges Ziel: es steht als eigene Zeile im status und
|
|
580
|
+
wird mit --ort wsl:<distro> angesprochen. Gespiegelt wird nichts, jedes Ziel wird einzeln verlinkt.
|
|
581
|
+
Links dort legt hmgr über wsl.exe an; das Linkziel bleibt der vorhandene Windows-Checkout, übersetzt
|
|
582
|
+
nach /mnt/c. MCP-Server und Plugins gibt es dort nicht: die claude-CLI dieses Rechners bedient nur das
|
|
583
|
+
primäre Homedir. remove lässt hmgr das Homedir vergessen, die Links dort bleiben liegen.
|
|
584
|
+
|
|
585
|
+
Zusätzliche Homedirs kennt bisher nur claude-code, und nur unter Windows. Steht eines im Zustand,
|
|
586
|
+
etwa weil er von einem Windows-Rechner kommt, zeigt hmgr sein Ziel auf anderen Plattformen nicht.
|
|
515
587
|
|
|
516
588
|
skills-dir zeigt, legt fest oder entfernt den Ordner, aus dem Hermes externe Skills liest
|
|
517
589
|
(skills.external_dirs in seiner config.yaml, E13, E56). hmgr verwaltet je Ziel (Home oder ein Profil
|
|
@@ -531,14 +603,28 @@ Beispiele:
|
|
|
531
603
|
hmgr harness list
|
|
532
604
|
hmgr harness enable hermes
|
|
533
605
|
hmgr harness disable hermes
|
|
606
|
+
hmgr harness home claude-code
|
|
607
|
+
hmgr harness home claude-code set D:\\Profile\\tim
|
|
608
|
+
hmgr harness home claude-code add wsl:Ubuntu
|
|
609
|
+
hmgr link netzwerk --harness claude-code --ort wsl:Ubuntu
|
|
610
|
+
hmgr harness home claude-code remove wsl:Ubuntu
|
|
534
611
|
hmgr harness skills-dir hermes
|
|
535
612
|
hmgr harness skills-dir hermes add ~/.hermes/external-skills
|
|
536
613
|
hmgr harness skills-dir hermes remove --profil researcher
|
|
537
614
|
|
|
538
615
|
Häufige Fälle:
|
|
539
616
|
„ist kein bekannter Harness-Typ": hmgr harness list zeigt die bekannten Namen.
|
|
617
|
+
„ist deaktiviert, hmgr legt dort keine Links an": hmgr harness enable <name>, dann erneut link
|
|
618
|
+
bzw. bundle add (E76).
|
|
540
619
|
Konfiguration bleibt nach disable liegen, wenn die Rückfrage mit Nein oder ohne Terminal beantwortet
|
|
541
|
-
wird.
|
|
620
|
+
wird. hmgr status zeigt die Ziele des Typs dann nicht mehr (E74), sie erscheinen nach hmgr harness
|
|
621
|
+
enable <name> wieder. Ein erneutes hmgr harness disable <name> bietet das Säubern wieder an.
|
|
622
|
+
„ist keine Adresse für ein zusätzliches Homedir": add nimmt wsl:<distro>. Ein abweichender
|
|
623
|
+
normaler Pfad gehört an set, nicht an add.
|
|
624
|
+
„kennt keine zusätzlichen Homedirs": add und remove kennen heute nur claude-code.
|
|
625
|
+
„gibt es nur unter Windows": das WSL-Homedir wird auf dem Windows-Rechner eingetragen, auf dem
|
|
626
|
+
die Distro läuft.
|
|
627
|
+
„wsl.exe -d <distro> ließ sich nicht ausführen": wsl -l -q zeigt die installierten Distros.
|
|
542
628
|
„kennt keinen verwalteten Skill-Ordner": skills-dir kennt heute nur hermes.
|
|
543
629
|
„ist schon ein Skill-Ordner verwaltet": erst hmgr harness skills-dir <name> remove, dann add.
|
|
544
630
|
„ist kein Skill-Ordner verwaltet": remove kennt nur einen vorhandenen Eintrag, add legt ihn fest.
|
|
@@ -559,6 +645,7 @@ const THEMEN = new Map([
|
|
|
559
645
|
['sync', SYNC],
|
|
560
646
|
['update', UPDATE],
|
|
561
647
|
['status', STATUS],
|
|
648
|
+
['log', LOG],
|
|
562
649
|
['harness', HARNESS],
|
|
563
650
|
['help', HILFE],
|
|
564
651
|
]);
|