hmgr 0.0.23 → 0.0.25
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/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
|
-
|
|
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,
|
|
@@ -819,10 +821,40 @@ function fensterOeffnen() {
|
|
|
819
821
|
// fällt, was ins Ziel kommt, und zeigt je Bundle Beschreibung und Herkunft.
|
|
820
822
|
var dskBundleBoxSichtbar = false;
|
|
821
823
|
var dskBundleText = null;
|
|
824
|
+
// Aufklappbereich je Zeile (E92): zu vor dem Klick, danach sichtbar mit der ungekürzten
|
|
825
|
+
// Beschreibung; die Checkbox bleibt unberührt, der Dialog offen.
|
|
826
|
+
var dskKoerperVorher = null;
|
|
827
|
+
var dskKoerperNachher = null;
|
|
828
|
+
var dskKoerperText = null;
|
|
829
|
+
var dskAufklappZeileAngehakt = null;
|
|
830
|
+
var dskAufklappZeileGesperrt = null;
|
|
831
|
+
var dskAufklappAriaNachher = null;
|
|
832
|
+
var dskDialogOffenNachAufklappen = null;
|
|
833
|
+
var dskAufklappFreiAngehakt = null;
|
|
822
834
|
if (dskKnopf) {
|
|
823
835
|
dskKnopf.click();
|
|
824
836
|
dskMarken = document.querySelector('#dsk-liste .wahlzeile .marken')?.textContent
|
|
825
837
|
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
838
|
+
var dskZeile = document.querySelector('#dsk-liste .wahlzeile:has(.aufklappknopf)');
|
|
839
|
+
var dskAufklapp = dskZeile?.querySelector('.aufklappknopf');
|
|
840
|
+
var dskKoerper = dskZeile?.querySelector('.wahlkoerper');
|
|
841
|
+
dskKoerperVorher = dskKoerper?.hidden ?? null;
|
|
842
|
+
dskAufklapp?.click();
|
|
843
|
+
dskKoerperNachher = dskKoerper?.hidden ?? null;
|
|
844
|
+
dskAufklappAriaNachher = dskAufklapp?.getAttribute('aria-expanded') ?? null;
|
|
845
|
+
dskKoerperText = dskKoerper?.textContent?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
846
|
+
dskKoerper?.querySelector('p')?.click();
|
|
847
|
+
dskAufklappZeileAngehakt = dskZeile?.querySelector('input')?.checked ?? null;
|
|
848
|
+
dskAufklappZeileGesperrt = dskZeile?.querySelector('input')?.disabled ?? null;
|
|
849
|
+
dskDialogOffenNachAufklappen = document.getElementById('d-skills')?.open ?? null;
|
|
850
|
+
// Die Fixture-Zeile ist gesperrt (Link liegt schon), das beweist nichts über das Label:
|
|
851
|
+
// darum dieselbe Zeile entsperren, zu- und wieder aufklappen und in den Text klicken.
|
|
852
|
+
var dskEingabe = dskZeile?.querySelector('input');
|
|
853
|
+
if (dskEingabe) dskEingabe.disabled = false;
|
|
854
|
+
dskAufklapp?.click();
|
|
855
|
+
dskAufklapp?.click();
|
|
856
|
+
dskKoerper?.querySelector('p')?.click();
|
|
857
|
+
dskAufklappFreiAngehakt = dskEingabe?.checked ?? null;
|
|
826
858
|
dskBundleBoxSichtbar = document.getElementById('dsk-bundlebox')?.hidden === false;
|
|
827
859
|
dskBundleText = document.querySelector('#dsk-bundles .wahlzeile')?.textContent
|
|
828
860
|
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
@@ -1126,6 +1158,14 @@ function fensterOeffnen() {
|
|
|
1126
1158
|
katalogDetails,
|
|
1127
1159
|
katalogMarkenWarnend,
|
|
1128
1160
|
dskMarken,
|
|
1161
|
+
dskKoerperVorher,
|
|
1162
|
+
dskKoerperNachher,
|
|
1163
|
+
dskKoerperText,
|
|
1164
|
+
dskAufklappZeileAngehakt,
|
|
1165
|
+
dskAufklappZeileGesperrt,
|
|
1166
|
+
dskAufklappAriaNachher,
|
|
1167
|
+
dskDialogOffenNachAufklappen,
|
|
1168
|
+
dskAufklappFreiAngehakt,
|
|
1129
1169
|
dskBundleBoxSichtbar,
|
|
1130
1170
|
dskBundleText,
|
|
1131
1171
|
dzZieleText,
|
|
@@ -2300,6 +2300,53 @@
|
|
|
2300
2300
|
return teile.join(' · ');
|
|
2301
2301
|
}
|
|
2302
2302
|
|
|
2303
|
+
/** Aufklappbereich einer Wahlzeile im Skills-hinzufügen-Dialog (E92): ungekürzte description und
|
|
2304
|
+
die Frontmatter-Angaben aus E55. Der Knopf hängt am Label (absolut oben rechts, `txt` bekommt
|
|
2305
|
+
Platz dafür), der Körper als drittes Kind des Labels, damit er als Grid-Zeile über die volle
|
|
2306
|
+
Breite läuft. Der Zustand ist der Zustand von aria-expanded: er lebt nur, solange die Zeile
|
|
2307
|
+
lebt (je Dialog-Öffnung neu, immer zugeklappt), nicht in SKILL_OFFEN. Das Label schaltet
|
|
2308
|
+
sonst die Checkbox um, deshalb preventDefault auf Knopf und Körper. Ohne Beschreibung und
|
|
2309
|
+
ohne Details entsteht nichts. */
|
|
2310
|
+
function skillAufklappbereich(skill, label, txt, index) {
|
|
2311
|
+
var details = skillDetails(skill);
|
|
2312
|
+
if (!skill.beschreibung && details.length === 0) return;
|
|
2313
|
+
var koerperId = 'dsk-koerper-' + index;
|
|
2314
|
+
var knopf = e('button', 'aufklappknopf');
|
|
2315
|
+
knopf.type = 'button';
|
|
2316
|
+
knopf.setAttribute('aria-expanded', 'false');
|
|
2317
|
+
knopf.setAttribute('aria-controls', koerperId);
|
|
2318
|
+
knopf.setAttribute('aria-label', 'Beschreibung und Details zu ' + skill.name + ' ein- oder ausklappen');
|
|
2319
|
+
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
2320
|
+
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
2321
|
+
pfeil.setAttribute('class', 'akkordeonpfeil dunkel');
|
|
2322
|
+
pfeil.setAttribute('aria-hidden', 'true');
|
|
2323
|
+
pfeil.innerHTML = '<path d="M4 6.5 8 10.5 12 6.5"/>';
|
|
2324
|
+
knopf.appendChild(pfeil);
|
|
2325
|
+
|
|
2326
|
+
var koerper = e('div', 'wahlkoerper');
|
|
2327
|
+
koerper.id = koerperId;
|
|
2328
|
+
koerper.hidden = true;
|
|
2329
|
+
if (skill.beschreibung) koerper.appendChild(e('p', 'skilldetail skillbeschreibung', skill.beschreibung));
|
|
2330
|
+
details.forEach(function (d) {
|
|
2331
|
+
var zeile = e('p', 'skilldetail');
|
|
2332
|
+
zeile.appendChild(e('b', null, d.label + ': '));
|
|
2333
|
+
zeile.appendChild(document.createTextNode(d.text));
|
|
2334
|
+
koerper.appendChild(zeile);
|
|
2335
|
+
});
|
|
2336
|
+
|
|
2337
|
+
knopf.addEventListener('click', function (ev) {
|
|
2338
|
+
ev.preventDefault();
|
|
2339
|
+
var war = knopf.getAttribute('aria-expanded') === 'true';
|
|
2340
|
+
knopf.setAttribute('aria-expanded', war ? 'false' : 'true');
|
|
2341
|
+
koerper.hidden = war;
|
|
2342
|
+
});
|
|
2343
|
+
koerper.addEventListener('click', function (ev) { ev.preventDefault(); });
|
|
2344
|
+
|
|
2345
|
+
txt.classList.add('mitknopf');
|
|
2346
|
+
label.appendChild(knopf);
|
|
2347
|
+
label.appendChild(koerper);
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2303
2350
|
function oeffneSkillsHinzufuegen(z) {
|
|
2304
2351
|
skillsKontext = { modus: 'einzeln', z: z };
|
|
2305
2352
|
$('dsk-ziel').textContent = zielTitel(z);
|
|
@@ -2329,6 +2376,7 @@
|
|
|
2329
2376
|
txt.appendChild(quelleSpanEl);
|
|
2330
2377
|
if (schon) txt.appendChild(e('span', 'notiz', 'Hängt hier schon.'));
|
|
2331
2378
|
label.appendChild(txt);
|
|
2379
|
+
skillAufklappbereich(skill, label, txt, i);
|
|
2332
2380
|
box.appendChild(label);
|
|
2333
2381
|
});
|
|
2334
2382
|
fuelleBundleSektion(function (bundle) { return bundleHaengtIn([z], bundle); }, 1);
|
|
@@ -2374,6 +2422,7 @@
|
|
|
2374
2422
|
txt.appendChild(e('span', 'notiz', 'Hängt schon in ' + (teile.length - fehltIn.length) + ' von ' + teile.length + ' Harnesses.'));
|
|
2375
2423
|
}
|
|
2376
2424
|
label.appendChild(txt);
|
|
2425
|
+
skillAufklappbereich(skill, label, txt, i);
|
|
2377
2426
|
box.appendChild(label);
|
|
2378
2427
|
});
|
|
2379
2428
|
fuelleBundleSektion(function (bundle) { return bundleHaengtIn(teile, bundle); }, teile.length);
|
|
@@ -539,6 +539,15 @@ dialog::backdrop{background:rgba(9,45,37,.62)}
|
|
|
539
539
|
.wahlzeile b{font-family:var(--schrift);font-weight:700;font-size:17px;letter-spacing:-.012em}
|
|
540
540
|
.wahlzeile .mono{display:block;color:var(--tinte-weich);margin-top:3px}
|
|
541
541
|
.wahlzeile .notiz{display:block;margin-top:8px;font-size:16px;color:var(--messing-satt);background:var(--messing-blass);border-left:3px solid var(--messing);padding:7px 10px;border-radius:0 2px 2px 0}
|
|
542
|
+
/* Aufklappbereich einer Wahlzeile im Skills-Dialog (E92). Der Knopf sitzt absolut oben rechts im Label
|
|
543
|
+
(die Zeile ist position:relative), die Textspalte lässt ihm Platz; der Körper ist ein eigenes
|
|
544
|
+
Grid-Kind über beide Spalten. Wie bei .zeile .skillkoerper braucht hidden eine eigene Regel. */
|
|
545
|
+
.wahlzeile .aufklappknopf{position:absolute;top:10px;right:10px}
|
|
546
|
+
.wahlzeile .mitknopf{padding-right:40px}
|
|
547
|
+
.wahlzeile .wahlkoerper{grid-column:1/-1;margin-top:2px;padding-top:12px;border-top:1px solid var(--strich-zart);cursor:text}
|
|
548
|
+
.wahlzeile .wahlkoerper[hidden]{display:none}
|
|
549
|
+
.wahlzeile .wahlkoerper b{font-size:inherit;font-weight:600;letter-spacing:normal}
|
|
550
|
+
.skillbeschreibung{white-space:pre-wrap}
|
|
542
551
|
.nummer{
|
|
543
552
|
font-family:var(--mono);font-size:16px;font-weight:500;width:28px;height:28px;flex:none;display:grid;
|
|
544
553
|
place-items:center;border:1.5px solid var(--tinte);border-radius:50%;margin-top:1px;
|
|
@@ -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.
|
|
@@ -2314,8 +2317,13 @@ export class Anwendungsfaelle {
|
|
|
2314
2317
|
gruende.set(name, zeiger?.skills.has(name) === true ? { art: 'zeiger' } : { art: 'manuell' });
|
|
2315
2318
|
return gruende;
|
|
2316
2319
|
}
|
|
2317
|
-
/**
|
|
2318
|
-
|
|
2320
|
+
/**
|
|
2321
|
+
* Ist-Zustand: alle Ziele der vorhandenen Harnesses, aus dem Dateisystem gelesen.
|
|
2322
|
+
* `plugins: false` lässt die Plugin-Abfrage aus (E93): sie ist ein Prozess je Ziel und für die
|
|
2323
|
+
* GUI, die das Feld nicht zeigt, nur Wartezeit. Dann fehlt `plugins` an allen Ziel-Zuständen.
|
|
2324
|
+
*/
|
|
2325
|
+
async istZustand(optionen = {}) {
|
|
2326
|
+
const mitPlugins = optionen.plugins !== false;
|
|
2319
2327
|
const zustand = await this.p.zustand.lesen();
|
|
2320
2328
|
// Je Identität die Quelle, unter deren Schreibweise sie geführt wird (E36, E48), für die
|
|
2321
2329
|
// Übernahme-Prognose. Bleibt über den ganzen Lauf gleich, die Prognose verändert nichts.
|
|
@@ -2340,6 +2348,8 @@ export class Anwendungsfaelle {
|
|
|
2340
2348
|
return funde;
|
|
2341
2349
|
};
|
|
2342
2350
|
const ergebnis = [];
|
|
2351
|
+
// Zielliste einmal bilden: Vorabruf der Plugins und Hauptschleife gehen über dieselben Ziele.
|
|
2352
|
+
const plan = [];
|
|
2343
2353
|
for (const harness of await this.vorhandeneHarnesses()) {
|
|
2344
2354
|
const ziele = [...this.homeZiele(harness)];
|
|
2345
2355
|
if (harness.orte().includes('profil') && kannProfile(harness))
|
|
@@ -2348,14 +2358,32 @@ export class Anwendungsfaelle {
|
|
|
2348
2358
|
if (harness.orte().includes('arbeitsverzeichnis'))
|
|
2349
2359
|
for (const pfad of zustand.arbeitsverzeichnisse)
|
|
2350
2360
|
ziele.push(arbeitsverzeichnisZiel(harness.name, pfad));
|
|
2361
|
+
plan.push({ harness, ziele });
|
|
2362
|
+
}
|
|
2363
|
+
// Installierte Plugins je Ziel vorab, mit begrenzter Parallelität (E93): `claude plugin list`
|
|
2364
|
+
// ist ein Prozess je Ziel (cwd-gebunden, kein Sammelaufruf), nacheinander dauert das bei vielen
|
|
2365
|
+
// Arbeitsverzeichnissen Sekunden. Plugins nur am primären Homedir (E78): die claude-CLI dieses
|
|
2366
|
+
// Rechners kennt die Plugin-Konfiguration einer WSL-Distro nicht, ihre user-Liste gehörte zum
|
|
2367
|
+
// Windows-Home.
|
|
2368
|
+
const pluginListen = new Map();
|
|
2369
|
+
if (mitPlugins) {
|
|
2370
|
+
const abfragen = [];
|
|
2371
|
+
for (const { harness, ziele } of plan)
|
|
2372
|
+
if (kannPlugins(harness))
|
|
2373
|
+
for (const ziel of ziele)
|
|
2374
|
+
if (!(ziel.ort.art === 'home' && ziel.ort.heim !== undefined))
|
|
2375
|
+
abfragen.push({
|
|
2376
|
+
schluessel: zielSchluessel(ziel),
|
|
2377
|
+
abruf: () => harness.plugins.installierte(ziel).catch(() => []),
|
|
2378
|
+
});
|
|
2379
|
+
const listen = await mitBegrenzung(PLUGIN_ABFRAGEN_GLEICHZEITIG, abfragen.map((a) => a.abruf));
|
|
2380
|
+
abfragen.forEach((a, i) => pluginListen.set(a.schluessel, listen[i]));
|
|
2381
|
+
}
|
|
2382
|
+
for (const { harness, ziele } of plan) {
|
|
2351
2383
|
for (const ziel of ziele) {
|
|
2352
2384
|
const verzeichnis = harness.zielVerzeichnis(ziel.ort);
|
|
2353
2385
|
const eintraege = await this.p.ziele.eintraege(ziel, verzeichnis).catch(() => []);
|
|
2354
|
-
|
|
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;
|
|
2386
|
+
const plugins = pluginListen.get(zielSchluessel(ziel));
|
|
2359
2387
|
const zeiger = await this.zeigerFuerZiel(ziel);
|
|
2360
2388
|
const istRegistriert = kannIstRegistriert(harness) ? await harness.istRegistriert(ziel.ort) : false;
|
|
2361
2389
|
// 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
|
+
}
|