hmgr 0.0.4 → 0.0.6
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/README.md +10 -0
- package/dist/adapter/checkout-pfad.js +10 -2
- package/dist/adapter/claude-code.js +251 -23
- package/dist/adapter/composer.js +4 -3
- package/dist/adapter/datei-text.js +18 -0
- package/dist/adapter/dateisystem.js +146 -8
- package/dist/adapter/git.js +129 -17
- package/dist/adapter/hermes.js +235 -10
- package/dist/adapter/marketplace-json.js +2 -1
- package/dist/adapter/mcp-katalog.js +124 -0
- package/dist/adapter/quelle-text.js +10 -1
- package/dist/adapter/skill-md.js +96 -19
- package/dist/adapter/terminal.js +1 -1
- package/dist/adapter/umgebung.js +43 -0
- package/dist/adapter/vergleich.js +20 -0
- package/dist/adapter/zustand-json.js +6 -1
- package/dist/cli/hilfe.js +227 -26
- package/dist/cli/main.js +362 -20
- package/dist/gui/beobachter.js +108 -0
- package/dist/gui/icons.js +114 -0
- package/dist/gui/login-pfad.js +120 -0
- package/dist/gui/main.js +823 -0
- package/dist/gui/mcp-ansicht.js +57 -0
- package/dist/gui/oeffnen.js +33 -0
- package/dist/gui/preload.cjs +70 -0
- package/dist/gui/renderer/fonts/Inter-LICENSE.txt +92 -0
- package/dist/gui/renderer/fonts/InterVariable.woff2 +0 -0
- package/dist/gui/renderer/fonts/JetBrainsMono-Medium.woff2 +0 -0
- package/dist/gui/renderer/fonts/JetBrainsMono-OFL.txt +93 -0
- package/dist/gui/renderer/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/dist/gui/renderer/index.html +581 -0
- package/dist/gui/renderer/renderer.js +3227 -0
- package/dist/gui/renderer/styles.css +701 -0
- package/dist/kern/anwendungsfaelle.js +859 -65
- package/dist/kern/modell.js +297 -0
- package/dist/kern/quelle-ort.js +14 -1
- package/dist/ports/harness.js +16 -0
- package/dist/ports/mcp-katalog.js +1 -0
- package/dist/ports/umgebung.js +1 -0
- package/dist/ports/zustand.js +1 -1
- package/package.json +47 -2
package/dist/kern/modell.js
CHANGED
|
@@ -63,6 +63,69 @@ export function quelleSchluessel(quelle) {
|
|
|
63
63
|
const ort = urlOrt(quelle.url);
|
|
64
64
|
return ort === undefined ? quelle.url : ortSchluessel(ort);
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Letztes nicht leeres Segment eines Pfads oder einer URL, ohne abschließende Schrägstriche.
|
|
68
|
+
* Trenner sind `/` und `\` (E65/B7-Nachbar B8): unter Windows kommt der Pfad als `C:\work\quelle`
|
|
69
|
+
* herein und ergab sonst den Namen „\work\quelle". Ein POSIX-Ordnername mit Backslash wird dadurch
|
|
70
|
+
* zerteilt — akzeptiert, das hier ist reine Anzeige-Ableitung und geht in keinen Schlüssel ein.
|
|
71
|
+
*/
|
|
72
|
+
function letztesSegment(text) {
|
|
73
|
+
const bereinigt = text.replace(/[/\\]+$/, '');
|
|
74
|
+
const segmente = bereinigt.split(/[/\\]/).filter((s) => s.length > 0);
|
|
75
|
+
return segmente[segmente.length - 1] ?? bereinigt;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Abgeleiteter Anzeigename einer Quelle (E59), ohne Rücksicht auf einen gespeicherten Namen: bei
|
|
79
|
+
* einer Git-Quelle das letzte Pfadsegment der URL ohne `.git`-Suffix (über `urlOrt`, wie E36 den
|
|
80
|
+
* Repo-Namen schon für die Identität liest), bei einer lokalen Quelle ihr Ordnername.
|
|
81
|
+
*/
|
|
82
|
+
export function quelleNameAbleiten(quelle) {
|
|
83
|
+
if (quelle.art === 'lokal')
|
|
84
|
+
return letztesSegment(quelle.pfad);
|
|
85
|
+
const ort = urlOrt(quelle.url);
|
|
86
|
+
return ort !== undefined ? ort.repo : letztesSegment(quelle.url);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Anzeigename einer Quelle (E59): der gespeicherte Name, sonst der abgeleitete. Reine Anzeige,
|
|
90
|
+
* geht nicht in `quelleSchluessel` ein. CLI und GUI nutzen ausschließlich diesen Helfer.
|
|
91
|
+
*/
|
|
92
|
+
export function quelleName(quelle) {
|
|
93
|
+
if (quelle.name !== undefined && quelle.name.trim().length > 0)
|
|
94
|
+
return quelle.name;
|
|
95
|
+
return quelleNameAbleiten(quelle);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Ein Anzeigename, den noch keine der genannten Quellen trägt (E59): der Wunschname, sonst mit
|
|
99
|
+
* angehängter Zahl. Nur für Wege, die eine Quelle nebenbei anlegen (Übernahme, E31): dort gibt es
|
|
100
|
+
* niemanden, der eine Kollision auflösen könnte, und ein Abbruch ließe die Übernahme ohne Ausweg
|
|
101
|
+
* scheitern. Wo der Nutzer den Namen selbst nennt, bleibt eine Kollision ein Fehler.
|
|
102
|
+
*/
|
|
103
|
+
export function quelleNameFrei(wunsch, vorhandene) {
|
|
104
|
+
const belegt = new Set(vorhandene.map(quelleName));
|
|
105
|
+
if (!belegt.has(wunsch))
|
|
106
|
+
return wunsch;
|
|
107
|
+
for (let n = 2;; n += 1) {
|
|
108
|
+
const kandidat = `${wunsch}-${n}`;
|
|
109
|
+
if (!belegt.has(kandidat))
|
|
110
|
+
return kandidat;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Dieselbe Quelle mit gesetztem Anzeigenamen (E59). `undefined` lässt sie unverändert. */
|
|
114
|
+
export function quelleMitNamen(quelle, name) {
|
|
115
|
+
if (name === undefined)
|
|
116
|
+
return quelle;
|
|
117
|
+
return quelle.art === 'git' ? { art: 'git', url: quelle.url, name } : { art: 'lokal', pfad: quelle.pfad, name };
|
|
118
|
+
}
|
|
119
|
+
/** Ein Anzeigename, den schon eine andere geführte Quelle trägt (E59): Namen sind eindeutig. */
|
|
120
|
+
export class QuelleNameBelegt extends Error {
|
|
121
|
+
angefragterName;
|
|
122
|
+
andere;
|
|
123
|
+
constructor(angefragterName, andere) {
|
|
124
|
+
super(`Name „${angefragterName}" ist schon vergeben an ${quelleAlsText(andere)}. Einen anderen Namen wählen.`);
|
|
125
|
+
this.angefragterName = angefragterName;
|
|
126
|
+
this.andere = andere;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
66
129
|
export class NameBelegt extends Error {
|
|
67
130
|
ziel;
|
|
68
131
|
skillName;
|
|
@@ -72,6 +135,25 @@ export class NameBelegt extends Error {
|
|
|
72
135
|
this.skillName = skillName;
|
|
73
136
|
}
|
|
74
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Der Link am Ziel ließ sich im Dateisystem nicht anlegen (E66). Ohne diesen Fehler käme ein roher
|
|
140
|
+
* Node-Stack beim Nutzer an, der weder Pfad noch Ausweg nennt. Der Handlungshinweis steht mit in
|
|
141
|
+
* der Meldung statt in `naechsterSchritt` der CLI: er nennt kein hmgr-Kommando, sondern eine
|
|
142
|
+
* Einstellung des Betriebssystems, und die GUI zeigt nur die Meldung.
|
|
143
|
+
*/
|
|
144
|
+
export class LinkAnlegenFehlgeschlagen extends Error {
|
|
145
|
+
linkPfad;
|
|
146
|
+
zielPfad;
|
|
147
|
+
ursache;
|
|
148
|
+
hinweis;
|
|
149
|
+
constructor(linkPfad, zielPfad, ursache, hinweis) {
|
|
150
|
+
super(`Der Link „${linkPfad}" auf „${zielPfad}" ließ sich nicht anlegen: ${ursache}\n${hinweis}`);
|
|
151
|
+
this.linkPfad = linkPfad;
|
|
152
|
+
this.zielPfad = zielPfad;
|
|
153
|
+
this.ursache = ursache;
|
|
154
|
+
this.hinweis = hinweis;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
75
157
|
export class SkillMehrdeutig extends Error {
|
|
76
158
|
name;
|
|
77
159
|
quellen;
|
|
@@ -88,6 +170,14 @@ export class SkillUnbekannt extends Error {
|
|
|
88
170
|
this.name = name;
|
|
89
171
|
}
|
|
90
172
|
}
|
|
173
|
+
/** Eine Quelle, die im Zustand nicht geführt wird (E54): Entfernen kennt nur bekannte Quellen. */
|
|
174
|
+
export class QuelleUnbekannt extends Error {
|
|
175
|
+
quelle;
|
|
176
|
+
constructor(quelle) {
|
|
177
|
+
super(`Quelle „${quelleAlsText(quelle)}" ist nicht bekannt`);
|
|
178
|
+
this.quelle = quelle;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
91
181
|
export class PluginMehrdeutig extends Error {
|
|
92
182
|
name;
|
|
93
183
|
quellen;
|
|
@@ -134,6 +224,36 @@ export class EinschraenkungOhneZeiger extends Error {
|
|
|
134
224
|
this.einschraenkung = einschraenkung;
|
|
135
225
|
}
|
|
136
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Schreiben in einen composer.json-Zeiger ist gesperrt, bis das Composer-Thema (E41, B13) gesondert
|
|
229
|
+
* entschieden ist (E57): der Zeiger ist ein geteiltes Team-Artefakt, ein Schreiben ohne Rücksprache
|
|
230
|
+
* beträfe Kollegen. Lesen bleibt erlaubt, nur link, unlink und plugin add/remove ohne `--local`
|
|
231
|
+
* treffen diese Sperre.
|
|
232
|
+
*/
|
|
233
|
+
export class ZeigerSchreibenGesperrt extends Error {
|
|
234
|
+
ziel;
|
|
235
|
+
constructor(ziel) {
|
|
236
|
+
super(`Schreiben in den Zeiger von ${zielSchluessel(ziel)} ist gesperrt, bis das Composer-Thema entschieden ist – ` +
|
|
237
|
+
'der Zeiger ist ein geteiltes Team-Artefakt. Nutze --local.');
|
|
238
|
+
this.ziel = ziel;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* `quelleAufraeumen` bricht ab, wenn ein Checkout der Quelle lokale Änderungen hat (E15, F31a):
|
|
243
|
+
* geprüft VOR der ersten Löschung, nichts wird gelöscht, kein stilles Teil-Überspringen. Der
|
|
244
|
+
* Aufrufer lässt bei diesem Fehler auch das Vergessen der Quelle unterbleiben, alles bleibt liegen.
|
|
245
|
+
*/
|
|
246
|
+
export class QuelleAufraeumenLokaleAenderungen extends Error {
|
|
247
|
+
checkouts;
|
|
248
|
+
constructor(checkouts) {
|
|
249
|
+
const kopf = checkouts.length === 1
|
|
250
|
+
? 'Ein Checkout hat lokale Änderungen, nichts wurde gelöscht:'
|
|
251
|
+
: `${checkouts.length} Checkouts haben lokale Änderungen, nichts wurde gelöscht:`;
|
|
252
|
+
const pfade = checkouts.map((c) => ` ${c.pfad}`).join('\n');
|
|
253
|
+
super(`${kopf}\n${pfade}\nCommitte/pushe die Änderungen oder verwirf sie, danach lässt sich das Entfernen wiederholen.`);
|
|
254
|
+
this.checkouts = checkouts;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
137
257
|
export class OrtNichtUnterstuetzt extends Error {
|
|
138
258
|
harness;
|
|
139
259
|
ort;
|
|
@@ -159,3 +279,180 @@ export class HarnessNichtVorhanden extends Error {
|
|
|
159
279
|
this.harness = harness;
|
|
160
280
|
}
|
|
161
281
|
}
|
|
282
|
+
/** Ein Name, den kein Harness-Typ trägt: Aktivieren, Deaktivieren und Säubern kennen nur bekannte Typen (E38). */
|
|
283
|
+
export class HarnessTypUnbekannt extends Error {
|
|
284
|
+
harness;
|
|
285
|
+
constructor(harness) {
|
|
286
|
+
super(`„${harness}" ist kein bekannter Harness-Typ`);
|
|
287
|
+
this.harness = harness;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Ein genannter Name, unter dem am Ziel kein fremder Eintrag liegt (E46): weder Übernehmen mit
|
|
292
|
+
* Eintrag-Auswahl noch Löschen kennen einen anderen als einen tatsächlich fremden Eintrag. Ein
|
|
293
|
+
* hmgr-eigener Link zählt hier ebenso wenig wie ein Name, der gar nicht existiert.
|
|
294
|
+
*/
|
|
295
|
+
export class FremderEintragUnbekannt extends Error {
|
|
296
|
+
ziel;
|
|
297
|
+
name;
|
|
298
|
+
constructor(ziel, name) {
|
|
299
|
+
super(`Im Ziel ${zielSchluessel(ziel)} liegt kein fremder Eintrag „${name}"`);
|
|
300
|
+
this.ziel = ziel;
|
|
301
|
+
this.name = name;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/** Ein Harness-Typ ohne die Fähigkeit „verwalteter Skill-Ordner" (E56): heute nur hermes. */
|
|
305
|
+
export class SkillOrdnerFaehigkeitFehlt extends Error {
|
|
306
|
+
harness;
|
|
307
|
+
constructor(harness) {
|
|
308
|
+
super(`Harness ${harness} kennt keinen verwalteten Skill-Ordner`);
|
|
309
|
+
this.harness = harness;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/** Für ein Ziel ist schon ein Skill-Ordner verwaltet (E56): Festlegen erlaubt nur genau einen. */
|
|
313
|
+
export class SkillOrdnerBereitsVerwaltet extends Error {
|
|
314
|
+
ziel;
|
|
315
|
+
pfad;
|
|
316
|
+
constructor(ziel, pfad) {
|
|
317
|
+
super(`Für ${zielSchluessel(ziel)} ist schon ein Skill-Ordner verwaltet: ${pfad}`);
|
|
318
|
+
this.ziel = ziel;
|
|
319
|
+
this.pfad = pfad;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/** Für ein Ziel ist kein Skill-Ordner verwaltet (E56): Entfernen kennt nur einen vorhandenen Eintrag. */
|
|
323
|
+
export class SkillOrdnerNichtVerwaltet extends Error {
|
|
324
|
+
ziel;
|
|
325
|
+
constructor(ziel) {
|
|
326
|
+
super(`Für ${zielSchluessel(ziel)} ist kein Skill-Ordner verwaltet`);
|
|
327
|
+
this.ziel = ziel;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Ein Pfad, der als verwalteter Skill-Ordner nicht in Frage kommt (E56): außerhalb des
|
|
332
|
+
* Hermes-Home oder Hermes' eigener Ordner `skills` (oder darunter).
|
|
333
|
+
*/
|
|
334
|
+
export class SkillOrdnerUngueltig extends Error {
|
|
335
|
+
pfad;
|
|
336
|
+
grund;
|
|
337
|
+
constructor(pfad, grund) {
|
|
338
|
+
super(grund === 'ausserhalb'
|
|
339
|
+
? `${pfad} liegt nicht unterhalb des Hermes-Home`
|
|
340
|
+
: `${pfad} ist Hermes' eigener Skill-Ordner`);
|
|
341
|
+
this.pfad = pfad;
|
|
342
|
+
this.grund = grund;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/** Ein Name, der im MCP-Katalog nicht steht (E61): mcp add und mcp remove kennen nur Katalog-Einträge. */
|
|
346
|
+
export class McpUnbekannt extends Error {
|
|
347
|
+
mcpName;
|
|
348
|
+
constructor(mcpName) {
|
|
349
|
+
super(`MCP „${mcpName}" steht nicht im Katalog`);
|
|
350
|
+
this.mcpName = mcpName;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/** Ein Harness-Typ ohne die Fähigkeit MCP-Server (E61): heute nur claude-code und hermes. */
|
|
354
|
+
export class McpFaehigkeitFehlt extends Error {
|
|
355
|
+
harness;
|
|
356
|
+
constructor(harness) {
|
|
357
|
+
super(`Harness ${harness} kennt keine MCP-Server`);
|
|
358
|
+
this.harness = harness;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Ein Ort, an dem dieser Harness-Typ keine MCP-Server verwaltet (E61): die Fähigkeit hängt am
|
|
363
|
+
* Adapter, nicht am Harness-Namen (E16), aber Hermes kennt grundsätzlich keine Projektebene für
|
|
364
|
+
* MCPs, das bekommt eine eigene Erklärung.
|
|
365
|
+
*/
|
|
366
|
+
export class McpEbeneNichtUnterstuetzt extends Error {
|
|
367
|
+
harness;
|
|
368
|
+
ort;
|
|
369
|
+
constructor(harness, ort) {
|
|
370
|
+
const erklaerung = harness === 'hermes' && ort === 'arbeitsverzeichnis' ? ' – Hermes kennt keine Projektebene für MCPs' : '';
|
|
371
|
+
super(`${harness} kennt für MCP-Server den Ort „${ort}" nicht${erklaerung}`);
|
|
372
|
+
this.harness = harness;
|
|
373
|
+
this.ort = ort;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Am Ziel liegt unter diesem Namen schon ein MCP-Server, den hmgr nicht selbst gelegt hat oder der
|
|
378
|
+
* vom Katalogblock abweicht (E61). hmgr überschreibt so etwas nicht stillschweigend.
|
|
379
|
+
*/
|
|
380
|
+
export class McpAbweichenderEintrag extends Error {
|
|
381
|
+
ziel;
|
|
382
|
+
mcpName;
|
|
383
|
+
constructor(ziel, mcpName) {
|
|
384
|
+
super(`In ${zielSchluessel(ziel)} liegt schon ein MCP-Server „${mcpName}", der nicht von hmgr stammt oder vom ` +
|
|
385
|
+
'Katalogblock abweicht. hmgr überschreibt ihn nicht stillschweigend.');
|
|
386
|
+
this.ziel = ziel;
|
|
387
|
+
this.mcpName = mcpName;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* `aushaengen` trifft einen Namen, der nicht im schreibbaren Bereich liegt, aber in einem geteilten
|
|
392
|
+
* Fundort steht (E61-Nachtrag), etwa der `.mcp.json` einer Projektwurzel bei Claude Code. Anders als
|
|
393
|
+
* der reguläre idempotente Fall (nirgends eingehängt) bleibt hier tatsächlich ein Eintrag liegen:
|
|
394
|
+
* die Meldung darf das nicht als Erfolg verkaufen. Seit E64 gibt es dafür einen Weg in hmgr selbst,
|
|
395
|
+
* die Meldung nennt ihn statt „von Hand ändern".
|
|
396
|
+
*/
|
|
397
|
+
export class McpNurGeteilterFundort extends Error {
|
|
398
|
+
ziel;
|
|
399
|
+
mcpName;
|
|
400
|
+
datei;
|
|
401
|
+
constructor(ziel, mcpName, datei) {
|
|
402
|
+
super(`„${mcpName}" steht in der geteilten ${datei}, nicht in deiner privaten Konfiguration. `
|
|
403
|
+
+ `hmgr mcp migrate ${mcpName} übernimmt ihn in den privaten Scope und entfernt ihn aus der Datei.`);
|
|
404
|
+
this.ziel = ziel;
|
|
405
|
+
this.mcpName = mcpName;
|
|
406
|
+
this.datei = datei;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Übernehmen (E64) an einem Ort, der keine geteilte Konfigurationsdatei kennt: ein Harness ohne
|
|
411
|
+
* diese Fähigkeit (Hermes kennt keine geteilte Datei) oder eine Ebene ohne sie (bei Claude Code hat
|
|
412
|
+
* nur ein Arbeitsverzeichnis eine `.mcp.json`, das Home nicht).
|
|
413
|
+
*/
|
|
414
|
+
export class McpUebernahmeNichtMoeglich extends Error {
|
|
415
|
+
ziel;
|
|
416
|
+
constructor(ziel) {
|
|
417
|
+
super(`In ${zielSchluessel(ziel)} gibt es keine geteilte Konfigurationsdatei, aus der hmgr einen MCP-Server übernehmen könnte.`);
|
|
418
|
+
this.ziel = ziel;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/** Übernehmen (E64) eines Namens, der in der geteilten Datei gar nicht steht. */
|
|
422
|
+
export class McpNichtInGeteilterDatei extends Error {
|
|
423
|
+
ziel;
|
|
424
|
+
mcpName;
|
|
425
|
+
datei;
|
|
426
|
+
constructor(ziel, mcpName, datei) {
|
|
427
|
+
super(`„${mcpName}" steht nicht in ${datei}, da ist nichts zu übernehmen.`);
|
|
428
|
+
this.ziel = ziel;
|
|
429
|
+
this.mcpName = mcpName;
|
|
430
|
+
this.datei = datei;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Teilerfolg beim Übernehmen (E64): der Eintrag steht schon im privaten Scope, ließ sich aber nicht
|
|
435
|
+
* aus der geteilten Datei entfernen. Die Reihenfolge ist bewusst so gewählt (erst eintragen, dann
|
|
436
|
+
* entfernen), damit in diesem Fall nichts verloren ist. Die Meldung sagt, was der Nutzer jetzt
|
|
437
|
+
* vorfindet, statt nur den Fehlschlag zu nennen.
|
|
438
|
+
*/
|
|
439
|
+
export class McpUebernahmeUnvollstaendig extends Error {
|
|
440
|
+
ziel;
|
|
441
|
+
mcpName;
|
|
442
|
+
datei;
|
|
443
|
+
constructor(ziel, mcpName, datei, grund) {
|
|
444
|
+
super(`„${mcpName}" steht jetzt in deiner privaten Konfiguration, ließ sich aber nicht aus ${datei} entfernen: ${grund}. `
|
|
445
|
+
+ 'Der Eintrag steht damit doppelt; wirksam ist der private. Entferne ihn aus der Datei von Hand, dann ist die Übernahme fertig.');
|
|
446
|
+
this.ziel = ziel;
|
|
447
|
+
this.mcpName = mcpName;
|
|
448
|
+
this.datei = datei;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/** Der MCP-Katalog ~/.hmgr/mcp.yaml lässt sich nicht lesen (E61): kaputtes YAML oder falsches Format. */
|
|
452
|
+
export class McpKatalogFehler extends Error {
|
|
453
|
+
pfad;
|
|
454
|
+
constructor(pfad, grund) {
|
|
455
|
+
super(`MCP-Katalog ${pfad}: ${grund}`);
|
|
456
|
+
this.pfad = pfad;
|
|
457
|
+
}
|
|
458
|
+
}
|
package/dist/kern/quelle-ort.js
CHANGED
|
@@ -12,9 +12,20 @@ export function ortAusSegmenten(host, segmente) {
|
|
|
12
12
|
const besitzer = gefiltert[gefiltert.length - 2] ?? '_';
|
|
13
13
|
return { host: host.toLowerCase(), besitzer, repo };
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Ein Windows-Laufwerkspfad (E65/B12): ein einzelner ASCII-Buchstabe, ein Doppelpunkt und dann ein
|
|
17
|
+
* Trenner — `C:\work\quelle` oder `C:/work/quelle`. Genau diese Form sieht sonst aus wie eine
|
|
18
|
+
* scp-Kurzform `host:pfad` und wurde als Host „C" mit dem Pfad „\work\quelle" gelesen. Ein Hostname
|
|
19
|
+
* aus einem einzelnen Buchstaben ist theoretisch erlaubt, kommt als Git-Remote aber nicht vor; der
|
|
20
|
+
* Windows-Pfad kommt vor.
|
|
21
|
+
*/
|
|
22
|
+
export function istWindowsLaufwerkspfad(text) {
|
|
23
|
+
return /^[A-Za-z]:[\\/]/.test(text.trim());
|
|
24
|
+
}
|
|
15
25
|
/**
|
|
16
26
|
* Zerlegt eine Git-URL: `git@host:besitzer/repo.git`, `ssh://git@host/besitzer/repo`,
|
|
17
|
-
* `https://host/besitzer/repo.git`, `file:///pfad`. Ein Text, der keine URL ist, gibt undefined
|
|
27
|
+
* `https://host/besitzer/repo.git`, `file:///pfad`. Ein Text, der keine URL ist, gibt undefined —
|
|
28
|
+
* darunter jeder nackte Pfad, der lokale POSIX-Pfad ebenso wie der Windows-Laufwerkspfad (E65/B12).
|
|
18
29
|
*/
|
|
19
30
|
export function urlOrt(url) {
|
|
20
31
|
const text = url.trim().replace(/\/+$/, '');
|
|
@@ -32,6 +43,8 @@ export function urlOrt(url) {
|
|
|
32
43
|
return ortAusSegmenten('local', segmente);
|
|
33
44
|
return ortAusSegmenten(zerlegt.hostname, segmente);
|
|
34
45
|
}
|
|
46
|
+
if (istWindowsLaufwerkspfad(text))
|
|
47
|
+
return undefined;
|
|
35
48
|
const scp = /^(?:[^@/]+@)?([^@/:]+):(.+)$/.exec(text);
|
|
36
49
|
if (scp)
|
|
37
50
|
return ortAusSegmenten(scp[1], scp[2].split('/'));
|
package/dist/ports/harness.js
CHANGED
|
@@ -4,3 +4,19 @@ export function kannProfile(h) {
|
|
|
4
4
|
export function kannPlugins(h) {
|
|
5
5
|
return h.plugins !== undefined;
|
|
6
6
|
}
|
|
7
|
+
export function kannMcp(h) {
|
|
8
|
+
return h.mcp !== undefined;
|
|
9
|
+
}
|
|
10
|
+
export function kannZurueckbauen(h) {
|
|
11
|
+
return typeof h.zurueckbauen === 'function';
|
|
12
|
+
}
|
|
13
|
+
export function kannIstRegistriert(h) {
|
|
14
|
+
return typeof h.istRegistriert === 'function';
|
|
15
|
+
}
|
|
16
|
+
export function kannSkillOrdner(h) {
|
|
17
|
+
return (typeof h.verwalteterSkillOrdner === 'function' &&
|
|
18
|
+
typeof h.skillOrdnerOrte === 'function' &&
|
|
19
|
+
typeof h.skillOrdnerFestlegen === 'function' &&
|
|
20
|
+
typeof h.skillOrdnerEntfernen === 'function' &&
|
|
21
|
+
typeof h.skillOrdnerLoeschen === 'function');
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/ports/zustand.js
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hmgr",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Harness Manager: Skills und Plugins für AI-Harnesses verwalten",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Tim Kaufmann",
|
|
7
7
|
"type": "module",
|
|
8
|
+
"main": "dist/gui/main.js",
|
|
8
9
|
"bin": {
|
|
9
10
|
"hmgr": "dist/cli/main.js"
|
|
10
11
|
},
|
|
@@ -18,22 +19,66 @@
|
|
|
18
19
|
},
|
|
19
20
|
"scripts": {
|
|
20
21
|
"dev": "node src/cli/main.ts",
|
|
21
|
-
"build": "tsc -p tsconfig.build.json",
|
|
22
|
+
"build": "tsc -p tsconfig.build.json && node scripts/copy-gui-assets.mjs",
|
|
23
|
+
"gui": "npm run build && electron dist/gui/main.js",
|
|
24
|
+
"gui:smoke": "npm run build && node scripts/gui-smoke-test.ts",
|
|
25
|
+
"gui:app": "npm run build && electron-builder --mac --arm64",
|
|
26
|
+
"gui:app:win": "npm run build && electron-builder --win --x64",
|
|
27
|
+
"gui:install": "npm run build && electron-builder --mac dir --arm64 && ditto release/mac-arm64/hmgr.app /Applications/hmgr.app",
|
|
22
28
|
"test": "vitest run",
|
|
23
29
|
"test:watch": "vitest",
|
|
24
30
|
"type-check": "tsc --noEmit",
|
|
25
31
|
"prepack": "npm run build"
|
|
26
32
|
},
|
|
27
33
|
"dependencies": {
|
|
34
|
+
"cross-spawn": "^7.0.6",
|
|
28
35
|
"yaml": "^2.8.0"
|
|
29
36
|
},
|
|
30
37
|
"devDependencies": {
|
|
38
|
+
"@types/cross-spawn": "^6.0.6",
|
|
31
39
|
"@types/node": "^24.0.0",
|
|
40
|
+
"electron": "^44.0.0",
|
|
41
|
+
"electron-builder": "^26.15.3",
|
|
32
42
|
"typescript": "^5.9.0",
|
|
33
43
|
"vitest": "^3.2.0"
|
|
34
44
|
},
|
|
35
45
|
"repository": {
|
|
36
46
|
"type": "git",
|
|
37
47
|
"url": "git+https://gitea.timkaufmann.de/tim/hmgr.git"
|
|
48
|
+
},
|
|
49
|
+
"build": {
|
|
50
|
+
"appId": "de.timkaufmann.hmgr",
|
|
51
|
+
"productName": "hmgr",
|
|
52
|
+
"directories": {
|
|
53
|
+
"output": "release",
|
|
54
|
+
"buildResources": "build"
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"dist/**/*",
|
|
58
|
+
"package.json"
|
|
59
|
+
],
|
|
60
|
+
"asar": true,
|
|
61
|
+
"mac": {
|
|
62
|
+
"category": "public.app-category.developer-tools",
|
|
63
|
+
"icon": "build/icon.icns",
|
|
64
|
+
"target": [
|
|
65
|
+
{ "target": "dir", "arch": ["arm64"] },
|
|
66
|
+
{ "target": "zip", "arch": ["arm64"] }
|
|
67
|
+
],
|
|
68
|
+
"identity": null
|
|
69
|
+
},
|
|
70
|
+
"win": {
|
|
71
|
+
"icon": "build/icon.ico",
|
|
72
|
+
"target": [
|
|
73
|
+
{ "target": "nsis", "arch": ["x64"] }
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"nsis": {
|
|
77
|
+
"oneClick": false,
|
|
78
|
+
"perMachine": false,
|
|
79
|
+
"allowElevation": false,
|
|
80
|
+
"allowToChangeInstallationDirectory": true,
|
|
81
|
+
"artifactName": "${productName}-Setup-${version}-${arch}.${ext}"
|
|
82
|
+
}
|
|
38
83
|
}
|
|
39
84
|
}
|