hmgr 0.0.24 → 0.0.26

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/cli/main.js CHANGED
@@ -990,7 +990,8 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
990
990
  // Säubern-Angebot nur, wenn eigene Konfiguration liegt, mit derselben Bedingung wie die GUI
991
991
  // (E38, GRILL.md Entwurfskritik 4): eigene Links oder eine liegen gebliebene Registrierung
992
992
  // ohne Link (E38 R7), sonst machten CLI und GUI verschiedene Angebote.
993
- const ziele = (await anwendungsfaelle.istZustand()).filter((z) => z.ziel.harness === name &&
993
+ // Ohne Plugin-Abfrage (E94): gelesen werden nur Harness, Einträge und Registrierung.
994
+ const ziele = (await anwendungsfaelle.istZustand({ plugins: false })).filter((z) => z.ziel.harness === name &&
994
995
  (z.eintraege.some((eintrag) => eintrag.art !== 'fremd') || z.istRegistriert));
995
996
  if (ziele.length === 0)
996
997
  return 0;
package/dist/gui/main.js CHANGED
@@ -187,7 +187,9 @@ function registriereIpc() {
187
187
  // Ebenfalls nur für die Anzeige (E68): der Renderer leitet daraus den Pfadtrenner ab, mit dem er
188
188
  // eigene Anzeigepfade zusammensetzt („~/.claude.json" bzw. „~\.claude.json").
189
189
  ipcMain.handle('hmgr:plattform', async () => auffangen(async () => process.platform));
190
- ipcMain.handle('hmgr:istZustand', async () => auffangen(async () => (await anwendungsfaelle.istZustand()).map(zielZustandSerialisieren)));
190
+ // Ohne Plugin-Abfrage (E93): der Renderer liest `plugins` aus dem Ziel-Zustand nirgends, und die
191
+ // Abfrage kostet einen claude-Prozess je Ziel.
192
+ ipcMain.handle('hmgr:istZustand', async () => auffangen(async () => (await anwendungsfaelle.istZustand({ plugins: false })).map(zielZustandSerialisieren)));
191
193
  ipcMain.handle('hmgr:marketplaces', async () => auffangen(async () => (await anwendungsfaelle.marketplaces()).map((m) => ({
192
194
  quelleText: quelleAlsText(m.quelle),
193
195
  name: m.name,
@@ -1,8 +1,11 @@
1
1
  // Anwendungsfälle. Pfade werden hier nur als Zeichenketten normalisiert, gelesen wird über die Ports.
2
2
  import path from 'node:path';
3
3
  import { AbhaengigkeitFehlt, BundleMehrdeutig, BundleMitgliederFehlen, BundleNamenskonflikte, BundleNichtInstalliert, BundleUnbekannt, EinschraenkungOhneZeiger, FremderEintragUnbekannt, HarnessInaktiv, HarnessNichtVorhanden, HarnessTypUnbekannt, HeimAdresseUngueltig, HeimBereitsVorhanden, HeimFaehigkeitFehlt, HeimNurUnterWindows, HeimUnbekannt, KeinPassenderRef, McpEbeneNichtUnterstuetzt, McpFaehigkeitFehlt, McpOrtUnterGitWurzel, McpUebernahmeNichtMoeglich, McpUnbekannt, OrtNichtUnterstuetzt, PluginMehrdeutig, PluginUnbekannt, QuelleAufraeumenLokaleAenderungen, QuelleNameBelegt, QuelleUnbekannt, SkillMehrdeutig, SkillOrdnerBereitsVerwaltet, SkillOrdnerFaehigkeitFehlt, SkillOrdnerNichtVerwaltet, SkillUnbekannt, ZeigerMehrdeutig, ZeigerSchreibenGesperrt, bundleSchluessel, heimAdresseAlsText, heimAdresseZerlegen, marketplaceUrl, pluginSchluessel, pluginSchluesselZerlegen, quelleAlsText, quelleMitNamen, quelleName, quelleNameAbleiten, quelleNameFrei, quelleSchluessel, zielSchluessel, } from "./modell.js";
4
+ import { mitBegrenzung } from "./begrenzung.js";
4
5
  import { passenderRef } from "./version.js";
5
6
  import { kannIstRegistriert, kannMcp, kannPlugins, kannProfile, kannSkillOrdner, kannZurueckbauen, kannZusaetzlicheHeime, } from "../ports/harness.js";
7
+ /** So viele `plugin list`-Prozesse laufen im Ist-Zustand höchstens gleichzeitig (E93). */
8
+ const PLUGIN_ABFRAGEN_GLEICHZEITIG = 6;
6
9
  /**
7
10
  * Frage vor dem Löschen, eine für alle Kandidaten (E28). Der Kern formuliert sie fertig, der
8
11
  * Adapter stellt sie: Anzahl und Pfade, damit der Nutzer sieht, worüber er entscheidet.
@@ -1995,10 +1998,11 @@ export class Anwendungsfaelle {
1995
1998
  * Pfade, in die ein Link zeigt. Gelesen wird wie beim Ist-Zustand (E19): die Ziele der
1996
1999
  * vorhandenen Harnesses, die Arbeitsverzeichnisse aus dem Zustand. Ein Link, dessen Quelle nicht
1997
2000
  * im Zustand steht, zeigt trotzdem in einen Checkout und hält ihn.
2001
+ * Gelesen werden nur die Einträge, deshalb ohne Plugin-Abfrage (ein Prozess je Ziel, E94).
1998
2002
  */
1999
2003
  async belegteCheckoutPfade() {
2000
2004
  const pfade = new Set();
2001
- for (const zielZustand of await this.istZustand())
2005
+ for (const zielZustand of await this.istZustand({ plugins: false }))
2002
2006
  for (const eintrag of zielZustand.eintraege) {
2003
2007
  if (eintrag.art === 'link')
2004
2008
  pfade.add(path.resolve(eintrag.link.checkout.pfad));
@@ -2314,8 +2318,13 @@ export class Anwendungsfaelle {
2314
2318
  gruende.set(name, zeiger?.skills.has(name) === true ? { art: 'zeiger' } : { art: 'manuell' });
2315
2319
  return gruende;
2316
2320
  }
2317
- /** Ist-Zustand: alle Ziele der vorhandenen Harnesses, aus dem Dateisystem gelesen. */
2318
- async istZustand() {
2321
+ /**
2322
+ * Ist-Zustand: alle Ziele der vorhandenen Harnesses, aus dem Dateisystem gelesen.
2323
+ * `plugins: false` lässt die Plugin-Abfrage aus (E93): sie ist ein Prozess je Ziel und für die
2324
+ * GUI, die das Feld nicht zeigt, nur Wartezeit. Dann fehlt `plugins` an allen Ziel-Zuständen.
2325
+ */
2326
+ async istZustand(optionen = {}) {
2327
+ const mitPlugins = optionen.plugins !== false;
2319
2328
  const zustand = await this.p.zustand.lesen();
2320
2329
  // Je Identität die Quelle, unter deren Schreibweise sie geführt wird (E36, E48), für die
2321
2330
  // Übernahme-Prognose. Bleibt über den ganzen Lauf gleich, die Prognose verändert nichts.
@@ -2340,6 +2349,8 @@ export class Anwendungsfaelle {
2340
2349
  return funde;
2341
2350
  };
2342
2351
  const ergebnis = [];
2352
+ // Zielliste einmal bilden: Vorabruf der Plugins und Hauptschleife gehen über dieselben Ziele.
2353
+ const plan = [];
2343
2354
  for (const harness of await this.vorhandeneHarnesses()) {
2344
2355
  const ziele = [...this.homeZiele(harness)];
2345
2356
  if (harness.orte().includes('profil') && kannProfile(harness))
@@ -2348,14 +2359,32 @@ export class Anwendungsfaelle {
2348
2359
  if (harness.orte().includes('arbeitsverzeichnis'))
2349
2360
  for (const pfad of zustand.arbeitsverzeichnisse)
2350
2361
  ziele.push(arbeitsverzeichnisZiel(harness.name, pfad));
2362
+ plan.push({ harness, ziele });
2363
+ }
2364
+ // Installierte Plugins je Ziel vorab, mit begrenzter Parallelität (E93): `claude plugin list`
2365
+ // ist ein Prozess je Ziel (cwd-gebunden, kein Sammelaufruf), nacheinander dauert das bei vielen
2366
+ // Arbeitsverzeichnissen Sekunden. Plugins nur am primären Homedir (E78): die claude-CLI dieses
2367
+ // Rechners kennt die Plugin-Konfiguration einer WSL-Distro nicht, ihre user-Liste gehörte zum
2368
+ // Windows-Home.
2369
+ const pluginListen = new Map();
2370
+ if (mitPlugins) {
2371
+ const abfragen = [];
2372
+ for (const { harness, ziele } of plan)
2373
+ if (kannPlugins(harness))
2374
+ for (const ziel of ziele)
2375
+ if (!(ziel.ort.art === 'home' && ziel.ort.heim !== undefined))
2376
+ abfragen.push({
2377
+ schluessel: zielSchluessel(ziel),
2378
+ abruf: () => harness.plugins.installierte(ziel).catch(() => []),
2379
+ });
2380
+ const listen = await mitBegrenzung(PLUGIN_ABFRAGEN_GLEICHZEITIG, abfragen.map((a) => a.abruf));
2381
+ abfragen.forEach((a, i) => pluginListen.set(a.schluessel, listen[i]));
2382
+ }
2383
+ for (const { harness, ziele } of plan) {
2351
2384
  for (const ziel of ziele) {
2352
2385
  const verzeichnis = harness.zielVerzeichnis(ziel.ort);
2353
2386
  const eintraege = await this.p.ziele.eintraege(ziel, verzeichnis).catch(() => []);
2354
- // Plugins nur am primären Homedir (E78): die claude-CLI dieses Rechners kennt die
2355
- // Plugin-Konfiguration einer WSL-Distro nicht, ihre user-Liste gehörte zum Windows-Home.
2356
- const plugins = kannPlugins(harness) && !(ziel.ort.art === 'home' && ziel.ort.heim !== undefined)
2357
- ? await harness.plugins.installierte(ziel).catch(() => [])
2358
- : undefined;
2387
+ const plugins = pluginListen.get(zielSchluessel(ziel));
2359
2388
  const zeiger = await this.zeigerFuerZiel(ziel);
2360
2389
  const istRegistriert = kannIstRegistriert(harness) ? await harness.istRegistriert(ziel.ort) : false;
2361
2390
  // Fähigkeit am Typwächter, nicht am Namen (E16), und nur an Orten, die sie kennt.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Führt Aufgaben mit höchstens `grenze` gleichzeitig laufenden aus (E93). Die Ergebnisse stehen
3
+ * in der Reihenfolge der Aufgaben, nicht der Fertigstellung. Kein Fehlerschutz: wirft eine
4
+ * Aufgabe, bricht der Aufruf mit diesem Fehler ab und startet keine weiteren Aufgaben. Aufrufer,
5
+ * die einzelne Ausfälle hinnehmen wollen, setzen das `.catch` in der Aufgabe selbst.
6
+ */
7
+ export async function mitBegrenzung(grenze, aufgaben) {
8
+ if (!Number.isInteger(grenze) || grenze < 1)
9
+ throw new RangeError(`Grenze muss eine ganze Zahl ab 1 sein, war ${grenze}`);
10
+ const ergebnisse = new Array(aufgaben.length);
11
+ let naechste = 0;
12
+ let abgebrochen = false;
13
+ const arbeiter = async () => {
14
+ while (!abgebrochen && naechste < aufgaben.length) {
15
+ const index = naechste++;
16
+ try {
17
+ ergebnisse[index] = await aufgaben[index]();
18
+ }
19
+ catch (fehler) {
20
+ abgebrochen = true;
21
+ throw fehler;
22
+ }
23
+ }
24
+ };
25
+ await Promise.all(Array.from({ length: Math.min(grenze, aufgaben.length) }, arbeiter));
26
+ return ergebnisse;
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hmgr",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "Harness Manager: Skills und Plugins für AI-Harnesses verwalten",
5
5
  "license": "MIT",
6
6
  "author": "Tim Kaufmann",