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.
Files changed (41) hide show
  1. package/README.md +10 -0
  2. package/dist/adapter/checkout-pfad.js +10 -2
  3. package/dist/adapter/claude-code.js +251 -23
  4. package/dist/adapter/composer.js +4 -3
  5. package/dist/adapter/datei-text.js +18 -0
  6. package/dist/adapter/dateisystem.js +146 -8
  7. package/dist/adapter/git.js +129 -17
  8. package/dist/adapter/hermes.js +235 -10
  9. package/dist/adapter/marketplace-json.js +2 -1
  10. package/dist/adapter/mcp-katalog.js +124 -0
  11. package/dist/adapter/quelle-text.js +10 -1
  12. package/dist/adapter/skill-md.js +96 -19
  13. package/dist/adapter/terminal.js +1 -1
  14. package/dist/adapter/umgebung.js +43 -0
  15. package/dist/adapter/vergleich.js +20 -0
  16. package/dist/adapter/zustand-json.js +6 -1
  17. package/dist/cli/hilfe.js +227 -26
  18. package/dist/cli/main.js +362 -20
  19. package/dist/gui/beobachter.js +108 -0
  20. package/dist/gui/icons.js +114 -0
  21. package/dist/gui/login-pfad.js +120 -0
  22. package/dist/gui/main.js +823 -0
  23. package/dist/gui/mcp-ansicht.js +57 -0
  24. package/dist/gui/oeffnen.js +33 -0
  25. package/dist/gui/preload.cjs +70 -0
  26. package/dist/gui/renderer/fonts/Inter-LICENSE.txt +92 -0
  27. package/dist/gui/renderer/fonts/InterVariable.woff2 +0 -0
  28. package/dist/gui/renderer/fonts/JetBrainsMono-Medium.woff2 +0 -0
  29. package/dist/gui/renderer/fonts/JetBrainsMono-OFL.txt +93 -0
  30. package/dist/gui/renderer/fonts/JetBrainsMono-Regular.woff2 +0 -0
  31. package/dist/gui/renderer/index.html +581 -0
  32. package/dist/gui/renderer/renderer.js +3227 -0
  33. package/dist/gui/renderer/styles.css +701 -0
  34. package/dist/kern/anwendungsfaelle.js +859 -65
  35. package/dist/kern/modell.js +297 -0
  36. package/dist/kern/quelle-ort.js +14 -1
  37. package/dist/ports/harness.js +16 -0
  38. package/dist/ports/mcp-katalog.js +1 -0
  39. package/dist/ports/umgebung.js +1 -0
  40. package/dist/ports/zustand.js +1 -1
  41. package/package.json +47 -2
@@ -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
+ }
@@ -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('/'));
@@ -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 {};
@@ -1,3 +1,3 @@
1
1
  export function leererZustand() {
2
- return { quellen: [], arbeitsverzeichnisse: [], bindungen: [] };
2
+ return { quellen: [], arbeitsverzeichnisse: [], bindungen: [], aktiveHarnesses: ['claude-code'], ignorierteEintraege: [] };
3
3
  }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "hmgr",
3
- "version": "0.0.4",
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
  }