hmgr 0.0.3 → 0.0.5

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.
@@ -1,4 +1,4 @@
1
- // Name eines Skills: `name` aus dem Frontmatter der SKILL.md, sonst der Verzeichnisname (E32).
1
+ // Name, Beschreibung und Besonderheiten eines Skills aus dem Frontmatter der SKILL.md (E32, E39, E55).
2
2
  import { parse } from 'yaml';
3
3
  export const SKILL_DATEI = 'SKILL.md';
4
4
  /** Der Block zwischen der ersten Zeile `---` und der nächsten Zeile `---`. Fehlt er, undefined. */
@@ -12,38 +12,114 @@ function frontmatter(inhalt) {
12
12
  return undefined;
13
13
  }
14
14
  /**
15
- * Der Verzeichnisname ist nur Konvention, das Frontmatter ist die Aussage des Autors (E18, E32).
16
- * Ohne Frontmatter, ohne YAML-Objekt, bei kaputtem YAML oder ohne nicht leeren `name` gilt der
17
- * Verzeichnisname. Ein Name mit Pfadtrenner gilt nicht, aus ihm würde im Ziel kein Eintrag,
18
- * sondern ein Pfad.
15
+ * Das Frontmatter als Objekt, einmal geparst für alle Felder. Fehlt der Block, ist das YAML
16
+ * kaputt oder liest es sich nicht als Objekt, gibt es kein Ergebnis (E32).
19
17
  */
20
- export function skillName(verzeichnisname, skillMdInhalt) {
18
+ function frontmatterDaten(skillMdInhalt) {
21
19
  if (skillMdInhalt === undefined)
22
- return verzeichnisname;
20
+ return undefined;
23
21
  const block = frontmatter(skillMdInhalt);
24
22
  if (block === undefined)
25
- return verzeichnisname;
23
+ return undefined;
26
24
  let daten;
27
25
  try {
28
26
  daten = parse(block);
29
27
  }
30
28
  catch {
31
- return verzeichnisname;
29
+ return undefined;
32
30
  }
33
31
  if (typeof daten !== 'object' || daten === null || Array.isArray(daten))
34
- return verzeichnisname;
35
- const roh = daten['name'];
32
+ return undefined;
33
+ return daten;
34
+ }
35
+ /** Ein Textfeld, getrimmt, leer heißt „nicht vorhanden" (E39). */
36
+ function textFeld(daten, feld) {
37
+ const roh = daten?.[feld];
36
38
  if (typeof roh !== 'string')
37
- return verzeichnisname;
38
- const name = roh.trim();
39
- if (name.length === 0 || name === '.' || name === '..')
39
+ return undefined;
40
+ const wert = roh.trim();
41
+ return wert.length === 0 ? undefined : wert;
42
+ }
43
+ /** Ein boolesches Feld mit dem genannten erwarteten Wert, sonst „nicht vorhanden" (E55). */
44
+ function boolFeld(daten, feld, erwartet) {
45
+ return daten?.[feld] === erwartet ? true : undefined;
46
+ }
47
+ /**
48
+ * Ein Feld, das vorhanden und nicht leer sein muss, unabhängig von seiner Art (E55: hooks,
49
+ * allowed-tools). Leere Liste, leeres Objekt, leerer Text und `false` heißen „nicht vorhanden":
50
+ * beides sind Arten zu sagen, dass es nichts gibt, und die Marke behauptete sonst etwas
51
+ * Sicherheitsrelevantes, das nicht dasteht.
52
+ */
53
+ function vorhandenFeld(daten, feld) {
54
+ const roh = daten?.[feld];
55
+ if (roh === undefined || roh === null || roh === false)
56
+ return undefined;
57
+ if (typeof roh === 'string')
58
+ return roh.trim().length > 0 ? true : undefined;
59
+ if (Array.isArray(roh))
60
+ return roh.length > 0 ? true : undefined;
61
+ if (typeof roh === 'object')
62
+ return Object.keys(roh).length > 0 ? true : undefined;
63
+ return true;
64
+ }
65
+ /**
66
+ * Der Verzeichnisname ist nur Konvention, das Frontmatter ist die Aussage des Autors (E18, E32).
67
+ * Ohne Frontmatter, ohne YAML-Objekt, bei kaputtem YAML oder ohne nicht leeren `name` gilt der
68
+ * Verzeichnisname. Ein Name mit Pfadtrenner gilt nicht, aus ihm würde im Ziel kein Eintrag,
69
+ * sondern ein Pfad.
70
+ */
71
+ function nameAus(daten, verzeichnisname) {
72
+ const name = textFeld(daten, 'name');
73
+ if (name === undefined || name === '.' || name === '..')
40
74
  return verzeichnisname;
41
75
  if (name.includes('/') || name.includes('\\'))
42
76
  return verzeichnisname;
43
77
  return name;
44
78
  }
45
- /** Skill aus seinem Pfad in der Quelle, Name nach `skillName`. */
46
- export function skillAusPfad(pfad, skillMdInhalt) {
47
- const verzeichnisname = pfad.split('/').filter((t) => t.length > 0).at(-1) ?? pfad;
48
- return { name: skillName(verzeichnisname, skillMdInhalt), pfad };
79
+ /** Name eines Skills aus dem Frontmatter, sonst der Verzeichnisname (E32). */
80
+ export function skillName(verzeichnisname, skillMdInhalt) {
81
+ return nameAus(frontmatterDaten(skillMdInhalt), verzeichnisname);
82
+ }
83
+ /**
84
+ * Beschreibung eines Skills aus dem Feld `description` im Frontmatter (E39), analog zur
85
+ * Namensregel E32: fehlendes Frontmatter, kein YAML-Objekt, kaputtes YAML oder kein nicht leerer
86
+ * Text ⇒ keine Beschreibung, nichts bricht ab.
87
+ */
88
+ export function skillBeschreibung(skillMdInhalt) {
89
+ return textFeld(frontmatterDaten(skillMdInhalt), 'description');
90
+ }
91
+ /**
92
+ * Skill aus seinem Pfad in der Quelle: Name nach `skillName`, Beschreibung nach
93
+ * `skillBeschreibung`, Besonderheiten aus dem Frontmatter (E55), jedes Feld nur gesetzt, wenn
94
+ * etwas da ist. Das Frontmatter wird dafür einmal geparst, nicht je Feld: der Katalog einer Quelle
95
+ * geht über alle ihre Skills. `ersatzname` greift nur, wenn der Pfad kein Segment hergibt.
96
+ */
97
+ export function skillAusPfad(pfad, skillMdInhalt, ersatzname) {
98
+ const verzeichnisname = pfad.split('/').filter((t) => t.length > 0).at(-1) ?? ersatzname ?? pfad;
99
+ const daten = frontmatterDaten(skillMdInhalt);
100
+ const skill = { name: nameAus(daten, verzeichnisname), pfad };
101
+ const beschreibung = textFeld(daten, 'description');
102
+ if (beschreibung !== undefined)
103
+ skill.beschreibung = beschreibung;
104
+ // `disable-model-invocation: true`: lädt erst bei `/name`. `user-invocable: false`: kein `/name`.
105
+ if (boolFeld(daten, 'disable-model-invocation', true))
106
+ skill.nurPerBefehl = true;
107
+ if (boolFeld(daten, 'user-invocable', false))
108
+ skill.nichtPerBefehl = true;
109
+ if (vorhandenFeld(daten, 'hooks'))
110
+ skill.registriertHooks = true;
111
+ if (vorhandenFeld(daten, 'allowed-tools'))
112
+ skill.werkzeugeErlaubt = true;
113
+ const kompatibilitaet = textFeld(daten, 'compatibility');
114
+ if (kompatibilitaet !== undefined)
115
+ skill.kompatibilitaet = kompatibilitaet;
116
+ const modell = textFeld(daten, 'model');
117
+ if (modell !== undefined)
118
+ skill.modell = modell;
119
+ if (textFeld(daten, 'context') === 'fork')
120
+ skill.eigenerKontext = true;
121
+ const argumentHinweis = textFeld(daten, 'argument-hint');
122
+ if (argumentHinweis !== undefined)
123
+ skill.argumentHinweis = argumentHinweis;
124
+ return skill;
49
125
  }
@@ -14,11 +14,34 @@ export class TerminalRueckfrage {
14
14
  return false;
15
15
  const leser = createInterface({ input: process.stdin, output: process.stdout });
16
16
  try {
17
- const antwort = await leser.question(`${frage} `);
17
+ const antwort = await leser.question(`${frage} [j/N] `);
18
18
  return JA.has(antwort.trim().toLowerCase());
19
19
  }
20
20
  finally {
21
21
  leser.close();
22
22
  }
23
23
  }
24
+ /**
25
+ * Nummerierte Liste, eine gelesene Zahl. Dieselbe Weiche wie bei `bestaetigen`: ohne Terminal auf
26
+ * beiden Seiten fragt hmgr nicht und gibt nichts aus. Alles, was keine der Nummern ist, auch die
27
+ * leere Eingabe, gilt als keine Antwort. Der Adapter rät nicht, er reicht das Nichts weiter.
28
+ */
29
+ async auswaehlen(frage, optionen) {
30
+ if (optionen.length === 0)
31
+ return undefined;
32
+ if (process.stdin.isTTY !== true || process.stdout.isTTY !== true)
33
+ return undefined;
34
+ const leser = createInterface({ input: process.stdin, output: process.stdout });
35
+ try {
36
+ const liste = optionen.map((option, i) => ` ${i + 1}) ${option}`).join('\n');
37
+ const antwort = await leser.question(`${frage}\n${liste}\nNummer [1-${optionen.length}]: `);
38
+ const nummer = Number(antwort.trim());
39
+ if (!Number.isInteger(nummer) || nummer < 1 || nummer > optionen.length)
40
+ return undefined;
41
+ return nummer - 1;
42
+ }
43
+ finally {
44
+ leser.close();
45
+ }
46
+ }
24
47
  }
@@ -16,6 +16,10 @@ export class ZustandJson {
16
16
  quellen: (daten.quellen ?? []),
17
17
  arbeitsverzeichnisse: (daten.arbeitsverzeichnisse ?? []),
18
18
  bindungen: (daten.bindungen ?? []),
19
+ // Fehlt das Feld (state.json vor E38), gilt der bisherige Ist-Zustand: nur claude-code aktiv.
20
+ aktiveHarnesses: (daten.aktiveHarnesses ?? ['claude-code']),
21
+ // Fehlt das Feld (state.json vor E43), gilt: nichts ignoriert.
22
+ ignorierteEintraege: (daten.ignorierteEintraege ?? []),
19
23
  };
20
24
  }
21
25
  async schreiben(zustand) {
package/dist/cli/hilfe.js CHANGED
@@ -9,23 +9,31 @@ geklontes Vorhaben holt seinen Stand mit hmgr sync.
9
9
 
10
10
  hmgr <kommando> [schalter]
11
11
 
12
- source add <url|pfad> Quelle hinzufügen (URL wird geklont, Pfad bleibt Arbeitskopie)
13
- source list Quellen zeigen
14
- dir add <pfad> Arbeitsverzeichnis merken und an seinen Zeiger binden
15
- dir forget <pfad> Arbeitsverzeichnis und Bindung vergessen, Links bleiben
16
- dir list Arbeitsverzeichnisse mit ihrer Bindung zeigen
17
- link <skill> Skill in ein Ziel verlinken
18
- unlink <skill> Link entfernen
19
- adopt [name] Fremde Links im Ziel durch eigene hmgr-Links ersetzen
20
- plugin add <name> Plugin in ein Ziel legen (installieren oder Skills verlinken)
21
- plugin remove <name> Plugin aus einem Ziel entfernen
22
- plugin list Plugins der Marketplaces aller Quellen
23
- sync [pfad] Ziel auf den Stand seines Zeigers bringen
24
- update Alle Checkouts aktualisieren
25
- status Ist-Zustand der Ziele
12
+ source add <url|pfad> Quelle hinzufügen (URL wird geklont, Pfad bleibt Arbeitskopie)
13
+ source rename <url|pfad> <name> Anzeigename der Quelle ändern
14
+ source remove <url|pfad> Quelle vergessen, Checkouts und Links bleiben liegen
15
+ source list Quellen zeigen
16
+ dir add <pfad> Arbeitsverzeichnis merken und an seinen Zeiger binden
17
+ dir forget <pfad> Arbeitsverzeichnis und Bindung vergessen, Links bleiben
18
+ dir list Arbeitsverzeichnisse mit ihrer Bindung zeigen
19
+ link <skill> Skill in ein Ziel verlinken
20
+ unlink <skill> Link entfernen
21
+ adopt [name] Fremde Links im Ziel durch eigene hmgr-Links ersetzen
22
+ plugin add <name> Plugin in ein Ziel legen (installieren oder Skills verlinken)
23
+ plugin remove <name> Plugin aus einem Ziel entfernen
24
+ plugin list Plugins der Marketplaces aller Quellen
25
+ sync [pfad] Ziel auf den Stand seines Zeigers bringen
26
+ update Alle Checkouts aktualisieren
27
+ status Ist-Zustand der Ziele
28
+ harness list Harness-Typen mit Stand aktiv/inaktiv und vorhanden
29
+ harness enable <name> Harness-Typ aktivieren, hmgr richtet ihn künftig ein
30
+ harness disable <name> Harness-Typ deaktivieren, mit Angebot zum Säubern
31
+ harness skills-dir <name> Verwalteten Skill-Ordner zeigen, festlegen oder entfernen
26
32
 
27
33
  hmgr help <kommando> zeigt Schalter, Beispiele und Zusammenhänge.`;
28
- const SOURCE = `hmgr source add <url|pfad>
34
+ const SOURCE = `hmgr source add <url|pfad> [--name <name>]
35
+ hmgr source rename <url|pfad> <name>
36
+ hmgr source remove <url|pfad>
29
37
  hmgr source list
30
38
 
31
39
  Eine Quelle ist ein Git-Repo mit Skills. source add prüft, ob sie erreichbar ist, liest ihre Skills
@@ -33,18 +41,47 @@ und ihren Marketplace und merkt sie unter ~/.hmgr. Erst danach kennt hmgr die Sk
33
41
  denen link und plugin add arbeiten. Eine URL wird geklont, ein Pfad bleibt die Arbeitskopie, die
34
42
  dort schon liegt, und wird von hmgr nicht verändert.
35
43
 
36
- Schalter: keine.
44
+ Welche Quelle gemeint ist, hängt an Host, Besitzer und Repo, nicht an der Schreibweise der URL. Ein
45
+ Repo in SSH- und in HTTPS-Form ist dieselbe Quelle, mit und ohne .git-Suffix ebenso. Gefetcht wird
46
+ unter der Schreibweise, die beim ersten source add stand. source remove trifft dieselbe Quelle auch
47
+ in anderer Schreibweise (E36).
48
+
49
+ Jede Quelle trägt einen Anzeigenamen (E59): ohne --name schlägt hmgr den Repo-Namen vor (letztes
50
+ Pfadsegment, bei einer lokalen Quelle der Ordnername) und speichert ihn. Namen sind eindeutig, trägt
51
+ eine andere geführte Quelle schon denselben, bricht source add ab. source rename ändert den Namen
52
+ später; die Identität der Quelle bleibt dabei ihre URL bzw. ihr Pfad, nicht der Name. Bei einer schon
53
+ bekannten Quelle (source add trifft sie erneut) bleibt ihr Name unverändert.
54
+
55
+ source remove vergisst die Quelle nur aus dem Zustand (E54), wie dir forget das
56
+ Arbeitsverzeichnis (E26): Checkouts und Links bleiben liegen, bestehende Links gelten danach als
57
+ „Link ohne bekannte Quelle" und tauchen so in hmgr status auf, hmgr update prüft sie nicht mehr. Ein
58
+ erneutes source add derselben Quelle macht sie wieder zu normalen Links.
59
+
60
+ Danach bietet hmgr als eigene Rückfrage an, die Skills dieser Quelle komplett vom Rechner zu löschen
61
+ (E58): alle ihre Links in allen Zielen und alle ihre Checkouts, alles oder nichts. Über die
62
+ Harness-CLI installierte Plugins deinstalliert hmgr dabei nicht, sie werden nur genannt, ebenso
63
+ Zeiger-Einträge (E57). Ohne Links und ohne Checkouts kommt die Rückfrage gar nicht erst. Ablehnen
64
+ lässt alles liegen wie bisher.
65
+
66
+ Schalter:
67
+ --name <name> nur bei add: Anzeigename statt des abgeleiteten Vorschlags
37
68
 
38
69
  Beispiele:
39
70
  hmgr source add https://github.com/tim/skills.git
40
- hmgr source add ~/.skills/tim-skills
71
+ hmgr source add ~/.skills/tim-skills --name tim-skills
72
+ hmgr source rename https://github.com/tim/skills.git meine-skills
73
+ hmgr source remove https://github.com/tim/skills.git
41
74
  hmgr source list
42
75
 
43
76
  Häufige Fälle:
44
77
  „Skill … liegt in keiner Quelle": die Quelle fehlt, erst source add, dann link.
45
78
  Neue Commits einer bekannten Quelle holt hmgr update, nicht source add.
79
+ „Quelle ist schon bekannt": dasselbe Repo steht schon im Zustand, hier in anderer Schreibweise.
80
+ „Quelle … ist nicht bekannt": source remove und source rename kennen nur geführte Quellen,
81
+ hmgr source list zeigt sie.
82
+ „Name … ist schon vergeben": einen anderen Namen wählen, mit --name bzw. source rename.
46
83
 
47
- Siehe auch: hmgr help link, hmgr help plugin, hmgr help update`;
84
+ Siehe auch: hmgr help link, hmgr help plugin, hmgr help update, hmgr help status`;
48
85
  const DIR = `hmgr dir add <pfad> [--composer <pfad>]
49
86
  hmgr dir forget <pfad>
50
87
  hmgr dir list
@@ -52,10 +89,12 @@ hmgr dir list
52
89
  Ein Arbeitsverzeichnis ist ein Verzeichnis, in dem ein Harness gestartet wird. dir add merkt es und
53
90
  bindet es an seinen Zeiger, also an die composer.json, in deren extra-Abschnitt Quellen, Skills und
54
91
  Plugins des Vorhabens stehen. Ohne Zeiger bleibt das Verzeichnis gemerkt und jeder Link dort lokal
55
- (E26). dir forget löst Bindung und Eintrag, die gelegten Links bleiben liegen.
92
+ (E26). dir forget löst Bindung und Eintrag, die gelegten Links bleiben liegen. Gebunden wird nur für
93
+ Harnesses, die hmgr verwaltet: standardmäßig nur claude-code (E38).
56
94
 
57
95
  Schalter:
58
- --composer <pfad> composer.json wählen, wenn unter dem Verzeichnis mehrere in Frage kommen
96
+ --composer <pfad> nur bei add: composer.json wählen, wenn unter dem Verzeichnis mehrere in
97
+ Frage kommen
59
98
 
60
99
  Beispiele:
61
100
  hmgr dir add ~/Code/site
@@ -63,17 +102,21 @@ Beispiele:
63
102
  hmgr dir list
64
103
 
65
104
  Häufige Fälle:
66
- „Mehrere composer.json unter …": eine mit --composer nennen, hmgr rät nicht.
105
+ „Mehrere composer.json unter …": hmgr fragt am Terminal, welche es ist. Ohne Antwort, etwa in
106
+ einem Script, bleibt der Fehler und --composer nennt sie, hmgr rät nicht.
67
107
  link --ort dir:<pfad> merkt das Verzeichnis von selbst. dir add braucht es vorher nur, wenn die
68
108
  composer.json mehrdeutig ist.
69
109
 
70
- Siehe auch: hmgr help link, hmgr help sync`;
110
+ Siehe auch: hmgr help link, hmgr help sync, hmgr help harness`;
71
111
  const LINK = `hmgr link <skill> [schalter]
72
112
 
73
113
  Legt im Ziel einen Symlink auf den Skill an, der aus einer bekannten Quelle stammt, und trägt ihn
74
114
  in den Zeiger des Ziels ein, damit hmgr sync ihn anderswo reproduzieren kann. Einen Zeiger gibt es
75
115
  nur für ein Arbeitsverzeichnis mit composer.json, an Home und Profil bleibt jeder Link lokal.
76
116
 
117
+ Das Schreiben in einen Zeiger ist gesperrt, bis das Composer-Thema entschieden ist (E57): an einem
118
+ Arbeitsverzeichnis mit Zeiger bricht link ohne --local ab, der Link wird nicht gelegt.
119
+
77
120
  Schalter:
78
121
  --harness <name> claude-code, hermes oder agents, Standard claude-code
79
122
  --ort <ort> home, dir:<pfad> oder profil:<name>, Standard home. profil kennt nur hermes
@@ -101,6 +144,9 @@ const UNLINK = `hmgr unlink <skill> [schalter]
101
144
  Entfernt den Link aus dem Ziel und, wenn das Ziel an einen Zeiger gebunden ist, den Skill aus dem
102
145
  Zeiger. Der Checkout unter ~/.hmgr bleibt liegen, Links anderer Ziele bleiben unberührt.
103
146
 
147
+ Das Schreiben in einen Zeiger ist gesperrt, bis das Composer-Thema entschieden ist (E57): an einem
148
+ Arbeitsverzeichnis mit Zeiger bricht unlink ohne --local ab, der Link bleibt liegen.
149
+
104
150
  Schalter:
105
151
  --harness <name> claude-code, hermes oder agents, Standard claude-code
106
152
  --ort <ort> home, dir:<pfad> oder profil:<name>, Standard home. profil kennt nur hermes
@@ -123,6 +169,10 @@ ein Link stammt, nimmt deren remote.origin.url als Quelle, legt einen Checkout a
123
169
  und verlinkt den Skill daraus. Ohne Namen laufen alle fremden Einträge des Ziels durch, mit Namen
124
170
  nur dieser eine. Was sich nicht auflösen lässt, bleibt liegen und wird mit Grund gemeldet.
125
171
 
172
+ Das Schreiben in einen Zeiger ist gesperrt, bis das Composer-Thema entschieden ist (E57): an einem
173
+ Arbeitsverzeichnis mit Zeiger bricht adopt ohne --local ab, bevor etwas übernommen wird. Der
174
+ Probelauf bleibt frei, er schreibt ohnehin nichts.
175
+
126
176
  Schalter:
127
177
  --harness <name> claude-code, hermes oder agents, Standard claude-code
128
178
  --ort <ort> home, dir:<pfad> oder profil:<name>, Standard home. profil kennt nur hermes
@@ -152,6 +202,10 @@ Plugin von dessen CLI installieren, sonst verlinkt es die flache Skill-Liste des
152
202
  seinen Abhängigkeiten. In den Zeiger kommt das Plugin, nicht seine Einzelskills. plugin list zeigt
153
203
  die Marketplaces aller bekannten Quellen mit ihren Plugins.
154
204
 
205
+ Das Schreiben in einen Zeiger ist gesperrt, bis das Composer-Thema entschieden ist (E57): an einem
206
+ Arbeitsverzeichnis mit Zeiger brechen add und remove ohne --local ab, nichts wird installiert,
207
+ verlinkt oder entfernt.
208
+
155
209
  Schalter (add und remove):
156
210
  --harness <name> claude-code, hermes oder agents, Standard claude-code
157
211
  --ort <ort> home, dir:<pfad> oder profil:<name>, Standard home. profil kennt nur hermes
@@ -178,7 +232,8 @@ Bringt ein Arbeitsverzeichnis auf den Stand seines Zeigers: fehlende Quellen üb
178
232
  Links legen, Links auf einen anderen Ref umlegen, Plugins herstellen. Ohne Pfad gilt das aktuelle
179
233
  Verzeichnis. Das ist der Befehl nach einem frischen Clone eines Vorhabens und nach jeder Änderung
180
234
  am Zeiger. Eigene Links, die im Zeiger fehlen, werden als abweichend gemeldet und nicht entfernt,
181
- fremde Einträge bleiben unberührt.
235
+ fremde Einträge bleiben unberührt. Eingerichtet wird nur für Harnesses, die hmgr verwaltet:
236
+ standardmäßig nur claude-code (E38).
182
237
 
183
238
  Schalter: keine.
184
239
 
@@ -193,7 +248,7 @@ Häufige Fälle:
193
248
  hmgr unlink <skill> --ort dir:<pfad> --local entfernen.
194
249
  „Kein Harness auf diesem Rechner": weder ~/.claude noch ~/.hermes noch ~/.agents ist angelegt.
195
250
 
196
- Siehe auch: hmgr help dir, hmgr help link, hmgr help update`;
251
+ Siehe auch: hmgr help dir, hmgr help link, hmgr help update, hmgr help harness`;
197
252
  const UPDATE = `hmgr update
198
253
 
199
254
  Holt für jeden Checkout unter ~/.hmgr den neuesten Stand seines Refs und zieht bei Harnesses mit
@@ -201,6 +256,11 @@ Plugin-Fähigkeit deren Plugins nach (E14). Die Links zeigen weiter auf dieselbe
201
256
  nicht erneuert werden. Ein Checkout mit lokaler Änderung wird übersprungen und gemeldet (E15),
202
257
  damit nichts verloren geht. Lokale Quellen haben keinen Checkout und bleiben unberührt.
203
258
 
259
+ Danach räumt update verwaiste Ref-Verzeichnisse auf: Checkouts, in die kein Link mehr zeigt, weil
260
+ ein Link inzwischen auf einen anderen Ref umgelegt wurde. Vor dem Löschen fragt hmgr einmal für
261
+ alle Kandidaten. Ohne Bestätigung, also auch ohne Terminal, bleibt alles liegen und wird nur
262
+ gemeldet. Ein verwaister Checkout mit lokaler Änderung wird nie gelöscht (E15).
263
+
204
264
  So wird eine Änderung an einem Skill überall wirksam: in der Quelle committen und pushen, dann
205
265
  hmgr update.
206
266
 
@@ -213,14 +273,18 @@ Häufige Fälle:
213
273
  „übersprungen … lokale Änderung (E15)": im Checkout liegt eine Änderung, die durch einen Link
214
274
  hindurch gemacht wurde. Dort committen und pushen, dann erneut hmgr update.
215
275
  „Keine Checkouts": es gibt nur lokale Quellen oder noch keinen Link.
276
+ „liegen lassen … ohne Bestätigung nicht gelöscht": die Frage kam ohne Ja zurück, etwa in einer
277
+ Pipe oder einem Composer-Script. In einem Terminal erneut hmgr update aufrufen.
216
278
 
217
279
  Siehe auch: hmgr help sync, hmgr help status`;
218
280
  const STATUS = `hmgr status
219
281
 
220
282
  Liest den Ist-Zustand vom Dateisystem: je Ziel eines vorhandenen Harness die Einträge mit Quelle
221
- und Ref, davor die Versionseinschränkung, falls der Zeiger eine nennt. Fremde Einträge und Links
222
- ohne bekannte Quelle sind eigens gekennzeichnet, dazu kommen die Plugins des Ziels. Am Ende stehen
223
- die Harnesses, die auf diesem Rechner nicht liegen.
283
+ und Ref, davor die Versionseinschränkung, falls der Zeiger eine nennt. Dahinter steht der
284
+ abgeleitete Grund des Links: das Plugin, zu dem er gehört, oder „über Zeiger", von Hand gelegte
285
+ Links bleiben ohne Zusatz. Fremde Einträge und Links ohne bekannte Quelle sind eigens
286
+ gekennzeichnet, dazu kommen die Plugins des Ziels. Am Ende stehen die Harnesses, die auf diesem
287
+ Rechner nicht liegen.
224
288
 
225
289
  Schalter: keine.
226
290
 
@@ -235,6 +299,58 @@ Häufige Fälle:
235
299
  überspringt seine Ziele.
236
300
 
237
301
  Siehe auch: hmgr help adopt, hmgr help sync`;
302
+ const HARNESS = `hmgr harness list
303
+ hmgr harness enable <name>
304
+ hmgr harness disable <name>
305
+ hmgr harness skills-dir <name> [--profil <name>]
306
+ hmgr harness skills-dir <name> add <pfad> [--profil <name>]
307
+ hmgr harness skills-dir <name> remove [--profil <name>]
308
+
309
+ Welche Harness-Typen hmgr verwaltet, ist eine Einstellung, kein gescannter Zustand: nur für aktive
310
+ Harnesses richten dir add und sync Ziele ein, standardmäßig nur claude-code (E38). harness list zeigt
311
+ alle bekannten Harness-Typen mit ihrem Stand: aktiv oder inaktiv, dazu vorhanden oder nicht vorhanden
312
+ (auf diesem Rechner installiert oder nicht). enable und disable sind idempotent, ein zweites Mal
313
+ ändert nichts und ist kein Fehler.
314
+
315
+ disable säubert nicht von selbst: liegen noch eigene Links oder eine harness-eigene Konfiguration wie
316
+ Hermes' external_dirs-Eintrag, fragt hmgr, ob sie entfernt werden sollen. Ohne Terminal, etwa in einem
317
+ Script, gilt Nein und die Konfiguration bleibt liegen. Ein Link mit ausdrücklich genanntem --harness
318
+ (hmgr link, hmgr plugin add) legt weiterhin an jedem vorhandenen Harness an, aktiv oder nicht, das ist
319
+ Sache des Nutzers.
320
+
321
+ skills-dir zeigt, legt fest oder entfernt den Ordner, aus dem Hermes externe Skills liest
322
+ (skills.external_dirs in seiner config.yaml, E13, E56). hmgr verwaltet je Ziel (Home oder ein Profil
323
+ mit --profil) genau einen Eintrag, den ersten unterhalb des Hermes-Home, ohne Hermes' eigenen Ordner
324
+ „skills"; alle anderen Einträge der config.yaml rührt hmgr nicht an. add erlaubt nur einen Pfad
325
+ unterhalb des Hermes-Home und legt ihn an, falls er fehlt; ein zweiter add ohne vorheriges remove ist
326
+ ein Fehler. remove löscht nur den Eintrag aus der config.yaml, der Ordner bleibt liegen; danach fragt
327
+ hmgr (mit Inhaltsangabe), ob der Ordner samt Inhalt gelöscht werden soll. Ohne Terminal, etwa in einem
328
+ Script, gilt Nein und der Ordner bleibt liegen, ohne weitere Meldung. Ohne verwalteten Eintrag richtet
329
+ das nächste hmgr link wieder den Standard-Ordner external-skills ein (E13). Hermes übernimmt eine
330
+ Änderung an external_dirs ohne Neustart.
331
+
332
+ Schalter (nur skills-dir):
333
+ --profil <name> Ziel ist das genannte Profil statt Home
334
+
335
+ Beispiele:
336
+ hmgr harness list
337
+ hmgr harness enable hermes
338
+ hmgr harness disable hermes
339
+ hmgr harness skills-dir hermes
340
+ hmgr harness skills-dir hermes add ~/.hermes/external-skills
341
+ hmgr harness skills-dir hermes remove --profil researcher
342
+
343
+ Häufige Fälle:
344
+ „ist kein bekannter Harness-Typ": hmgr harness list zeigt die bekannten Namen.
345
+ Konfiguration bleibt nach disable liegen, wenn die Rückfrage mit Nein oder ohne Terminal beantwortet
346
+ wird. Ein erneutes hmgr harness disable <name> bietet das Säubern wieder an.
347
+ „kennt keinen verwalteten Skill-Ordner": skills-dir kennt heute nur hermes.
348
+ „ist schon ein Skill-Ordner verwaltet": erst hmgr harness skills-dir <name> remove, dann add.
349
+ „ist kein Skill-Ordner verwaltet": remove kennt nur einen vorhandenen Eintrag, add legt ihn fest.
350
+ „liegt nicht unterhalb des Hermes-Home" oder „ist Hermes' eigener Skill-Ordner": einen anderen
351
+ Pfad unter ~/.hermes (bzw. dem Profil-Ordner) nennen, nicht „skills" selbst.
352
+
353
+ Siehe auch: hmgr help dir, hmgr help sync, hmgr help status`;
238
354
  /** Ebene 2, je Kommandofamilie ein Text. Unterkommandos teilen sich die Hilfe ihrer Familie. */
239
355
  const THEMEN = new Map([
240
356
  ['source', SOURCE],
@@ -246,6 +362,7 @@ const THEMEN = new Map([
246
362
  ['sync', SYNC],
247
363
  ['update', UPDATE],
248
364
  ['status', STATUS],
365
+ ['harness', HARNESS],
249
366
  ['help', HILFE],
250
367
  ]);
251
368
  /** Hilfetext zu einem Kommando, undefined für ein unbekanntes Thema. */