hmgr 0.0.30 → 0.0.35
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/adapter/claude-code.js +282 -78
- package/dist/adapter/wsl.js +83 -13
- package/dist/cli/hilfe.js +37 -8
- package/dist/cli/main.js +40 -11
- package/dist/gui/renderer/index.html +2 -2
- package/dist/gui/renderer/renderer.js +17 -0
- package/dist/kern/anwendungsfaelle.js +62 -26
- package/dist/kern/modell.js +36 -27
- package/package.json +1 -1
|
@@ -3,35 +3,27 @@ import spawn from 'cross-spawn';
|
|
|
3
3
|
import { realpathSync } from 'node:fs';
|
|
4
4
|
import { readFile, stat } from 'node:fs/promises';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import {
|
|
6
|
+
import { ClaudeCliNichtErreichbar, McpAbweichenderEintrag, McpNichtInGeteilterDatei, McpNurGeteilterFundort, McpUebernahmeNichtMoeglich, McpUebernahmeUnvollstaendig, OrtNichtUnterstuetzt, pluginSchluessel, wslDistroVonOrt, wslUncZerlegen, } from "../kern/modell.js";
|
|
7
7
|
import { ohneBom } from "./datei-text.js";
|
|
8
8
|
import { LokalerZugriff } from "./dateisystem.js";
|
|
9
9
|
import { gitignoreEintragen, istGitRepo } from "./gitignore.js";
|
|
10
10
|
import { arbeitsverzeichnisVerbinden, heimVerbinden, nurHome } from "./heime.js";
|
|
11
11
|
import { tiefGleich } from "./vergleich.js";
|
|
12
|
+
import { WslBruecke, wslAusfuehrerMit, wslClaudeAusfuehrer } from "./wsl.js";
|
|
12
13
|
import { mitZeitlimit } from "./zeitlimit.js";
|
|
13
14
|
const CLAUDE_VERZEICHNIS = '.claude';
|
|
14
15
|
const SKILLS_ORDNER = 'skills';
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* Rechner kennt die Konfiguration einer WSL-Distro nicht.
|
|
19
|
-
*/
|
|
20
|
-
function zusaetzlichesHeim(ort) {
|
|
21
|
-
return ort.art === 'home' ? ort.heim : undefined;
|
|
16
|
+
/** Der Pfad eines Arbeitsverzeichnisses in der Welt seiner CLI: in WSL der Linux-Pfad (E100). */
|
|
17
|
+
function cliPfad(pfad) {
|
|
18
|
+
return wslUncZerlegen(pfad)?.linuxPfad ?? pfad;
|
|
22
19
|
}
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*/
|
|
27
|
-
function ohneCliZugriffPruefen(harness, ort, was) {
|
|
28
|
-
const heim = zusaetzlichesHeim(ort);
|
|
29
|
-
if (heim !== undefined)
|
|
30
|
-
throw new HeimOhneCliZugriff(harness, heim, was);
|
|
31
|
-
if (ort.art === 'arbeitsverzeichnis' && wslUncZerlegen(ort.pfad) !== undefined)
|
|
32
|
-
throw new ArbeitsverzeichnisOhneCliZugriff(harness, ort.pfad, was);
|
|
20
|
+
/** Der Ort, unter dem `ClaudeCode` die claude-CLI einer Distro anspricht: ihr Heim. */
|
|
21
|
+
function distroOrt(distro) {
|
|
22
|
+
return { art: 'home', heim: `wsl:${distro}` };
|
|
33
23
|
}
|
|
34
24
|
const GITIGNORE_ZEILE = '.claude/';
|
|
25
|
+
/** Wie lange „keine claude-CLI erreichbar" gilt, bevor erneut gefragt wird (E103). */
|
|
26
|
+
const ERREICHBARKEIT_NEGATIV_MS = 60_000;
|
|
35
27
|
/**
|
|
36
28
|
* Standard-Ausführer: `claude` vom PATH. Ein Fehlschlag wird zum Code, nicht zur Ausnahme.
|
|
37
29
|
* stdin ist zu, sonst hängt eine CLI, die auf Eingabe wartet, den ganzen Ablauf; der Timeout
|
|
@@ -60,6 +52,21 @@ export const claudeAusfuehrer = (args, cwd) => new Promise((aufloesen) => {
|
|
|
60
52
|
kind.on('error', (fehler) => aufloesen({ stdout: fehler.message, code: 1 }));
|
|
61
53
|
kind.on('close', (code) => aufloesen({ stdout: code === 0 ? stdout : `${stdout}${stderr}`, code: code ?? 1 }));
|
|
62
54
|
});
|
|
55
|
+
/**
|
|
56
|
+
* Pfadvergleich für E102: abschließende Trenner zählen nicht; bei Windows-Pfaden (Laufwerk oder
|
|
57
|
+
* UNC) sind außerdem `\\` und `/` sowie Groß-/Kleinschreibung gleich.
|
|
58
|
+
*/
|
|
59
|
+
export function pfadGleich(a, b) {
|
|
60
|
+
const windows = (p) => /^[a-zA-Z]:[\\/]/.test(p) || p.startsWith('\\\\');
|
|
61
|
+
const norm = (p, win) => {
|
|
62
|
+
let t = win ? p.replaceAll('\\', '/').toLowerCase() : p;
|
|
63
|
+
while (t.length > 1 && t.endsWith('/'))
|
|
64
|
+
t = t.slice(0, -1);
|
|
65
|
+
return t;
|
|
66
|
+
};
|
|
67
|
+
const win = windows(a) || windows(b);
|
|
68
|
+
return norm(a, win) === norm(b, win);
|
|
69
|
+
}
|
|
63
70
|
export class PluginListeUnbekannteForm extends Error {
|
|
64
71
|
constructor(text) {
|
|
65
72
|
super(`claude plugin list --json: ${text}`);
|
|
@@ -96,7 +103,13 @@ export function pluginListeLesen(text) {
|
|
|
96
103
|
const scope = o['scope'];
|
|
97
104
|
if (!istScope(scope))
|
|
98
105
|
throw new PluginListeUnbekannteForm(`„${id}" hat keinen bekannten Scope`);
|
|
99
|
-
|
|
106
|
+
const projectPath = o['projectPath'];
|
|
107
|
+
return {
|
|
108
|
+
name: id.slice(0, trenner),
|
|
109
|
+
marketplace: id.slice(trenner + 1),
|
|
110
|
+
scope,
|
|
111
|
+
...(typeof projectPath === 'string' ? { projectPath } : {}),
|
|
112
|
+
};
|
|
100
113
|
});
|
|
101
114
|
}
|
|
102
115
|
/**
|
|
@@ -116,16 +129,25 @@ export function marketplaceListeLesen(text) {
|
|
|
116
129
|
/**
|
|
117
130
|
* Plugins über `claude plugin …`. Scope ist immer `local` mit dem Arbeitsverzeichnis selbst als
|
|
118
131
|
* cwd (E27). Nur das Home des Nutzers läuft als `--scope user` ohne cwd.
|
|
132
|
+
*
|
|
133
|
+
* Welche claude-CLI, entscheidet der Ort (E101, derselbe Weg wie MCP seit E100): ein WSL-Heim und
|
|
134
|
+
* ein Arbeitsverzeichnis unter `\\wsl$` gehen an die CLI der Distro, das cwd ist dort der
|
|
135
|
+
* Linux-Pfad. Jede Installation führt ihre eigene Marketplace-Liste, deshalb wirken auch die
|
|
136
|
+
* Marketplace-Operationen in der Welt des genannten Orts. Vor jedem Aufruf prüft die Fähigkeit, ob
|
|
137
|
+
* dort eine CLI antwortet, sonst `ClaudeCliNichtErreichbar`.
|
|
119
138
|
*/
|
|
120
139
|
class ClaudePlugins {
|
|
121
140
|
harness;
|
|
122
|
-
|
|
123
|
-
constructor(harness, ausfuehrer) {
|
|
141
|
+
constructor(harness) {
|
|
124
142
|
this.harness = harness;
|
|
125
|
-
this.ausfuehrer = ausfuehrer;
|
|
126
143
|
}
|
|
127
|
-
|
|
128
|
-
|
|
144
|
+
/** Die claude-CLI des Orts, nachdem feststeht, dass sie antwortet. */
|
|
145
|
+
async cli(ort) {
|
|
146
|
+
await this.harness.cliPruefen(ort);
|
|
147
|
+
return this.harness.ausfuehrerFuer(ort);
|
|
148
|
+
}
|
|
149
|
+
async rufen(ort, args, cwd) {
|
|
150
|
+
const { stdout, code } = await (await this.cli(ort))(args, cwd);
|
|
129
151
|
if (code !== 0)
|
|
130
152
|
throw new Error(`claude ${args.join(' ')} scheiterte (Code ${code}): ${stdout.trim()}`);
|
|
131
153
|
return stdout;
|
|
@@ -134,48 +156,80 @@ class ClaudePlugins {
|
|
|
134
156
|
* Scope und cwd eines Ortes (E27). Das cwd ist das Arbeitsverzeichnis selbst, nicht sein
|
|
135
157
|
* Zielverzeichnis: außerhalb eines Git-Repos legt Claude Code die `settings.local.json` im
|
|
136
158
|
* Startverzeichnis ab, mit dem Zielverzeichnis als cwd landete sie unter `.claude/skills/`.
|
|
159
|
+
* In WSL steht das cwd als Linux-Pfad da (E101).
|
|
137
160
|
*/
|
|
138
161
|
scope(ziel) {
|
|
139
|
-
ohneCliZugriffPruefen(this.harness.name, ziel.ort, 'Plugins');
|
|
140
162
|
if (ziel.ort.art === 'home')
|
|
141
163
|
return { scope: 'user' };
|
|
142
164
|
if (ziel.ort.art === 'arbeitsverzeichnis')
|
|
143
|
-
return { scope: 'local', cwd: ziel.ort.pfad };
|
|
165
|
+
return { scope: 'local', cwd: cliPfad(ziel.ort.pfad) };
|
|
144
166
|
throw new OrtNichtUnterstuetzt(this.harness.name, ziel.ort.art);
|
|
145
167
|
}
|
|
146
|
-
async marketplaceBekannt(name) {
|
|
147
|
-
const { stdout, code } = await this.
|
|
168
|
+
async marketplaceBekannt(name, ort) {
|
|
169
|
+
const { stdout, code } = await (await this.cli(ort))(['plugin', 'marketplace', 'list']);
|
|
148
170
|
if (code !== 0)
|
|
149
171
|
return false;
|
|
150
172
|
return marketplaceListeLesen(stdout).includes(name);
|
|
151
173
|
}
|
|
152
|
-
|
|
153
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Eine lokale Quelle steht mit ihrem Windows-Pfad da; die CLI einer Distro braucht ihn in deren
|
|
176
|
+
* Schreibweise (E101): `C:\…` unter der Automount-Wurzel, `\\wsl$\<dieselbe Distro>\…` als
|
|
177
|
+
* Linux-Pfad. URLs und alles andere gehen unverändert durch.
|
|
178
|
+
*/
|
|
179
|
+
async marketplaceHinzufuegen(_name, url, ort) {
|
|
180
|
+
await this.rufen(ort, ['plugin', 'marketplace', 'add', await this.urlInWelt(url, ort)]);
|
|
181
|
+
}
|
|
182
|
+
async urlInWelt(url, ort) {
|
|
183
|
+
const distro = wslDistroVonOrt(ort);
|
|
184
|
+
if (distro === undefined)
|
|
185
|
+
return url;
|
|
186
|
+
const unc = wslUncZerlegen(url);
|
|
187
|
+
if (unc !== undefined)
|
|
188
|
+
return unc.distro.toLowerCase() === distro.toLowerCase() ? unc.linuxPfad : url;
|
|
189
|
+
if (/^[a-zA-Z]:[\\/]/.test(url))
|
|
190
|
+
return await this.harness.bruecke(distro).vomHost(url);
|
|
191
|
+
return url;
|
|
154
192
|
}
|
|
155
193
|
async installieren(plugin, marketplace, ziel) {
|
|
156
194
|
const { scope, cwd } = this.scope(ziel);
|
|
157
|
-
await this.rufen(['plugin', 'install', pluginSchluessel({ name: plugin, marketplace }), '--scope', scope], cwd);
|
|
195
|
+
await this.rufen(ziel.ort, ['plugin', 'install', pluginSchluessel({ name: plugin, marketplace }), '--scope', scope], cwd);
|
|
158
196
|
}
|
|
159
197
|
async entfernen(plugin, marketplace, ziel) {
|
|
160
198
|
const { scope, cwd } = this.scope(ziel);
|
|
161
|
-
await this.rufen(['plugin', 'uninstall', pluginSchluessel({ name: plugin, marketplace }), '--scope', scope], cwd);
|
|
199
|
+
await this.rufen(ziel.ort, ['plugin', 'uninstall', pluginSchluessel({ name: plugin, marketplace }), '--scope', scope], cwd);
|
|
162
200
|
}
|
|
163
201
|
/** Ohne Namen aktualisiert die CLI alle Marketplaces. Je Plugin verlangt sie einen Namen, das übernimmt hmgr nicht (E14). */
|
|
164
|
-
async aktualisieren() {
|
|
165
|
-
await this.rufen(['plugin', 'marketplace', 'update']);
|
|
202
|
+
async aktualisieren(ort) {
|
|
203
|
+
await this.rufen(ort, ['plugin', 'marketplace', 'update']);
|
|
204
|
+
}
|
|
205
|
+
async welten() {
|
|
206
|
+
return await this.harness.erreichbareWelten();
|
|
166
207
|
}
|
|
167
208
|
/**
|
|
168
209
|
* Die Liste gilt nur für das cwd des Aufrufs und enthält dort alle Scopes. Zum Ort gehören im
|
|
169
210
|
* Home die Plugins mit Scope `user`, im Arbeitsverzeichnis die mit `local` und `project`.
|
|
211
|
+
* E102: Die CLI listet die local-Einträge aller Projekte, je mit `projectPath`; am
|
|
212
|
+
* Arbeitsverzeichnis zählen nur die eigenen (ohne `projectPath` wie bisher). Verglichen wird mit
|
|
213
|
+
* dem CLI-Pfad (in WSL der Linux-Pfad) und lokal zusätzlich mit der aufgelösten Form, weil
|
|
214
|
+
* Claude Code den cwd auflöst.
|
|
170
215
|
*/
|
|
171
216
|
async installierte(ziel) {
|
|
172
217
|
const { cwd } = this.scope(ziel);
|
|
173
|
-
const { stdout, code } = await this.
|
|
218
|
+
const { stdout, code } = await (await this.cli(ziel.ort))(['plugin', 'list', '--json'], cwd);
|
|
174
219
|
if (code !== 0)
|
|
175
220
|
return [];
|
|
176
221
|
const erlaubt = ziel.ort.art === 'home' ? ['user'] : ['local', 'project'];
|
|
222
|
+
const eigene = [];
|
|
223
|
+
if (ziel.ort.art === 'arbeitsverzeichnis') {
|
|
224
|
+
eigene.push(cwd ?? ziel.ort.pfad);
|
|
225
|
+
if (wslUncZerlegen(ziel.ort.pfad) === undefined)
|
|
226
|
+
eigene.push(aufgeloest(ziel.ort.pfad));
|
|
227
|
+
}
|
|
177
228
|
return pluginListeLesen(stdout)
|
|
178
229
|
.filter((eintrag) => erlaubt.includes(eintrag.scope))
|
|
230
|
+
.filter((eintrag) => ziel.ort.art !== 'arbeitsverzeichnis' ||
|
|
231
|
+
eintrag.projectPath === undefined ||
|
|
232
|
+
eigene.some((pfad) => pfadGleich(pfad, eintrag.projectPath)))
|
|
179
233
|
.map((eintrag) => pluginSchluessel(eintrag));
|
|
180
234
|
}
|
|
181
235
|
}
|
|
@@ -222,8 +276,12 @@ function aufgeloest(pfad) {
|
|
|
222
276
|
* Windows angelegte Datei leer.
|
|
223
277
|
*/
|
|
224
278
|
async function projektMcpJsonLesen(verzeichnis) {
|
|
279
|
+
const text = await readFile(path.join(verzeichnis, '.mcp.json'), 'utf8').catch(() => undefined);
|
|
280
|
+
return text === undefined ? {} : mcpServersAusText(text);
|
|
281
|
+
}
|
|
282
|
+
/** `mcpServers` aus dem Text einer `.mcp.json`; kaputtes JSON heißt nichts, eine BOM stört nicht. */
|
|
283
|
+
function mcpServersAusText(text) {
|
|
225
284
|
try {
|
|
226
|
-
const text = await readFile(path.join(verzeichnis, '.mcp.json'), 'utf8');
|
|
227
285
|
return mcpServersVon(JSON.parse(ohneBom(text)));
|
|
228
286
|
}
|
|
229
287
|
catch {
|
|
@@ -239,30 +297,60 @@ async function projektMcpJsonLesen(verzeichnis) {
|
|
|
239
297
|
* geschrieben wird sie einzig von `geteiltenUebernehmen` auf ausdrücklichen Auftrag (E64). Format
|
|
240
298
|
* und Verhalten der CLI stehen in HARNESS-FAKTEN.md, geprüft an Claude Code 2.1.261 gegen ein
|
|
241
299
|
* eigenes CLAUDE_CONFIG_DIR, nie an einer echten Nutzerdatei.
|
|
300
|
+
*
|
|
301
|
+
* Welche CLI und welche Dateien, entscheidet der Ort (E100): ein WSL-Heim und ein Arbeitsverzeichnis
|
|
302
|
+
* unter `\\wsl$` gehen an die claude-CLI der Distro (cwd als Linux-Pfad), und gelesen werden
|
|
303
|
+
* `~/.claude.json` und `.mcp.json` der Distro über die Bridge — die Windows-Dateien wären die
|
|
304
|
+
* falschen. Vor jedem schreibenden Aufruf prüft die Fähigkeit, ob am Ort eine CLI antwortet;
|
|
305
|
+
* sonst hielte das idempotente Aushängen ein „nicht gefunden" der fehlenden CLI für Erfolg.
|
|
242
306
|
*/
|
|
243
307
|
class ClaudeMcp {
|
|
244
308
|
harness;
|
|
245
|
-
ausfuehrer;
|
|
246
309
|
/** Das primäre Homedir, jedes Mal frisch: es lässt sich zur Laufzeit umstellen (E78, F89). */
|
|
247
310
|
home;
|
|
248
|
-
constructor(harness,
|
|
311
|
+
constructor(harness, home) {
|
|
249
312
|
this.harness = harness;
|
|
250
|
-
this.ausfuehrer = ausfuehrer;
|
|
251
313
|
this.home = home;
|
|
252
314
|
}
|
|
315
|
+
/** Die claude-CLI des Orts, nachdem feststeht, dass sie antwortet (E100). */
|
|
316
|
+
async cli(ort) {
|
|
317
|
+
await this.harness.cliPruefen(ort);
|
|
318
|
+
return this.harness.ausfuehrerFuer(ort);
|
|
319
|
+
}
|
|
320
|
+
/** Der Pfad eines Arbeitsverzeichnisses in der Welt seiner CLI: in WSL der Linux-Pfad (E100). */
|
|
321
|
+
cliPfad(pfad) {
|
|
322
|
+
return cliPfad(pfad);
|
|
323
|
+
}
|
|
324
|
+
/** Wo die geteilte `.mcp.json` eines Arbeitsverzeichnisses liegt, in WSL als Linux-Pfad (E100). */
|
|
325
|
+
mcpJsonPfad(pfad) {
|
|
326
|
+
const wsl = wslUncZerlegen(pfad);
|
|
327
|
+
return wsl === undefined ? path.join(pfad, '.mcp.json') : path.posix.join(wsl.linuxPfad, '.mcp.json');
|
|
328
|
+
}
|
|
329
|
+
/** Die geteilte `.mcp.json` eines Arbeitsverzeichnisses, in WSL über die Bridge gelesen (E100). */
|
|
330
|
+
async projektMcpJson(pfad) {
|
|
331
|
+
const wsl = wslUncZerlegen(pfad);
|
|
332
|
+
if (wsl === undefined)
|
|
333
|
+
return await projektMcpJsonLesen(pfad);
|
|
334
|
+
const text = await this.harness.bruecke(wsl.distro).dateiLesen(this.mcpJsonPfad(pfad)).catch(() => undefined);
|
|
335
|
+
return text === undefined ? {} : mcpServersAusText(text);
|
|
336
|
+
}
|
|
253
337
|
anGitWurzelGebunden = true;
|
|
254
338
|
ebenen() {
|
|
255
339
|
return ['home', 'arbeitsverzeichnis'];
|
|
256
340
|
}
|
|
341
|
+
/** Ob am Ort eine claude-CLI antwortet (E103): ohne sie bringt Einhängen oder Aushängen dort nichts. */
|
|
342
|
+
async ortBedienbar(ort) {
|
|
343
|
+
return await this.harness.cliErreichbar(ort);
|
|
344
|
+
}
|
|
257
345
|
hinweisNachAenderung() {
|
|
258
346
|
return 'Claude Code übernimmt die Änderung mit der nächsten Sitzung.';
|
|
259
347
|
}
|
|
348
|
+
/** Scope und cwd eines Ortes; das cwd steht in der Welt der zuständigen CLI (E100). */
|
|
260
349
|
scope(ort) {
|
|
261
|
-
ohneCliZugriffPruefen(this.harness.name, ort, 'MCP-Server');
|
|
262
350
|
if (ort.art === 'home')
|
|
263
351
|
return { scope: 'user' };
|
|
264
352
|
if (ort.art === 'arbeitsverzeichnis')
|
|
265
|
-
return { scope: 'local', cwd: ort.pfad };
|
|
353
|
+
return { scope: 'local', cwd: this.cliPfad(ort.pfad) };
|
|
266
354
|
throw new OrtNichtUnterstuetzt(this.harness.name, ort.art);
|
|
267
355
|
}
|
|
268
356
|
/**
|
|
@@ -273,8 +361,9 @@ class ClaudeMcp {
|
|
|
273
361
|
*/
|
|
274
362
|
async einhaengen(name, konfig, ort) {
|
|
275
363
|
const { scope, cwd } = this.scope(ort);
|
|
364
|
+
const cli = await this.cli(ort);
|
|
276
365
|
const json = claudeMcpJson(konfig);
|
|
277
|
-
const { stdout, code } = await
|
|
366
|
+
const { stdout, code } = await cli(['mcp', 'add-json', name, json, '--scope', scope], cwd);
|
|
278
367
|
if (code === 0)
|
|
279
368
|
return;
|
|
280
369
|
const bestehend = await this.eintrag(name, ort);
|
|
@@ -294,15 +383,16 @@ class ClaudeMcp {
|
|
|
294
383
|
*/
|
|
295
384
|
async aushaengen(name, ort) {
|
|
296
385
|
const { scope, cwd } = this.scope(ort);
|
|
297
|
-
const
|
|
386
|
+
const cli = await this.cli(ort);
|
|
387
|
+
const { stdout, code } = await cli(['mcp', 'remove', name, '--scope', scope], cwd);
|
|
298
388
|
if (code === 0)
|
|
299
389
|
return;
|
|
300
390
|
if ((await this.eintrag(name, ort)) !== undefined)
|
|
301
391
|
throw new Error(`claude mcp remove ${name} scheiterte (Code ${code}): ${stdout.trim()}`);
|
|
302
392
|
if (ort.art === 'arbeitsverzeichnis') {
|
|
303
|
-
const geteilt = await
|
|
393
|
+
const geteilt = await this.projektMcpJson(ort.pfad);
|
|
304
394
|
if (name in geteilt)
|
|
305
|
-
throw new McpNurGeteilterFundort({ harness: this.harness.name, ort }, name,
|
|
395
|
+
throw new McpNurGeteilterFundort({ harness: this.harness.name, ort }, name, this.mcpJsonPfad(ort.pfad));
|
|
306
396
|
}
|
|
307
397
|
}
|
|
308
398
|
async eingehaengte(ort) {
|
|
@@ -310,16 +400,14 @@ class ClaudeMcp {
|
|
|
310
400
|
}
|
|
311
401
|
/**
|
|
312
402
|
* Die geteilte `.mcp.json` eines Arbeitsverzeichnisses (E61-Nachtrag): im Alltag nur gelesen,
|
|
313
|
-
* weder beim Ein- noch beim Aushängen angefasst. Home kennt keine solche Datei.
|
|
403
|
+
* weder beim Ein- noch beim Aushängen angefasst. Home kennt keine solche Datei. In WSL liegt sie
|
|
404
|
+
* im Linux-Dateisystem und wird über die Bridge gelesen, der Pfad steht als Linux-Pfad da (E100).
|
|
314
405
|
*/
|
|
315
406
|
async geteilteFundorte(ort) {
|
|
316
407
|
if (ort.art !== 'arbeitsverzeichnis')
|
|
317
408
|
return [];
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
return [];
|
|
321
|
-
const datei = path.join(ort.pfad, '.mcp.json');
|
|
322
|
-
const server = await projektMcpJsonLesen(ort.pfad);
|
|
409
|
+
const datei = this.mcpJsonPfad(ort.pfad);
|
|
410
|
+
const server = await this.projektMcpJson(ort.pfad);
|
|
323
411
|
return Object.keys(server).map((name) => ({ name, datei, scope: 'project' }));
|
|
324
412
|
}
|
|
325
413
|
/**
|
|
@@ -341,12 +429,13 @@ class ClaudeMcp {
|
|
|
341
429
|
const ziel = { harness: this.harness.name, ort };
|
|
342
430
|
if (ort.art !== 'arbeitsverzeichnis')
|
|
343
431
|
throw new McpUebernahmeNichtMoeglich(ziel);
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
const block = (await projektMcpJsonLesen(ort.pfad))[name];
|
|
432
|
+
const datei = this.mcpJsonPfad(ort.pfad);
|
|
433
|
+
const block = (await this.projektMcpJson(ort.pfad))[name];
|
|
347
434
|
if (block === undefined)
|
|
348
435
|
throw new McpNichtInGeteilterDatei(ziel, name, datei);
|
|
349
|
-
const
|
|
436
|
+
const cli = await this.cli(ort);
|
|
437
|
+
const cwd = this.cliPfad(ort.pfad);
|
|
438
|
+
const hin = await cli(['mcp', 'add-json', name, JSON.stringify(block), '--scope', 'local'], cwd);
|
|
350
439
|
if (hin.code !== 0) {
|
|
351
440
|
// Der einzige harmlose Fehlschlag: derselbe Block liegt schon im privaten Scope, etwa weil ein
|
|
352
441
|
// früherer Lauf zwischen den beiden Schritten abgebrochen ist. Ein abweichender Block wird
|
|
@@ -357,10 +446,10 @@ class ClaudeMcp {
|
|
|
357
446
|
if (!tiefGleich(bestehend, block))
|
|
358
447
|
throw new McpAbweichenderEintrag(ziel, name);
|
|
359
448
|
}
|
|
360
|
-
const weg = await
|
|
449
|
+
const weg = await cli(['mcp', 'remove', name, '--scope', 'project'], cwd);
|
|
361
450
|
// Nicht der Code entscheidet, sondern die Datei danach: steht der Name nicht mehr drin, ist die
|
|
362
451
|
// Übernahme fertig, egal wie die CLI den Fall genannt hat.
|
|
363
|
-
if (weg.code !== 0 && name in (await
|
|
452
|
+
if (weg.code !== 0 && name in (await this.projektMcpJson(ort.pfad)))
|
|
364
453
|
throw new McpUebernahmeUnvollstaendig(ziel, name, datei, weg.stdout.trim());
|
|
365
454
|
return { datei };
|
|
366
455
|
}
|
|
@@ -375,23 +464,35 @@ class ClaudeMcp {
|
|
|
375
464
|
* Ordner in einem Git-Repo, ist der Schlüssel die Git-Wurzel, nicht der Ordner (E91, belegt an
|
|
376
465
|
* 2.1.285): für einen Unterordner liest diese Methode deshalb nichts, und der Kern sperrt dort
|
|
377
466
|
* die MCP-Operationen, bevor sie den Adapter erreichen.
|
|
467
|
+
*
|
|
468
|
+
* In WSL (E100) ist es die `~/.claude.json` der Distro, über die Bridge gelesen; der Schlüssel ist
|
|
469
|
+
* der Linux-Pfad, zusätzlich in der Distro aufgelöst (`realpath`).
|
|
378
470
|
*/
|
|
379
471
|
async mcpServersAm(ort) {
|
|
380
|
-
|
|
381
|
-
const daten = await this.claudeJsonLesen();
|
|
472
|
+
const distro = wslDistroVonOrt(ort);
|
|
473
|
+
const daten = await this.claudeJsonLesen(distro);
|
|
382
474
|
if (ort.art === 'home')
|
|
383
475
|
return mcpServersVon(daten);
|
|
384
476
|
if (ort.art === 'arbeitsverzeichnis') {
|
|
385
477
|
const projects = objektFeld(daten, 'projects');
|
|
386
|
-
const
|
|
387
|
-
|
|
478
|
+
const pfad = this.cliPfad(ort.pfad);
|
|
479
|
+
const direkt = objektFeld(projects, pfad);
|
|
480
|
+
if (direkt !== undefined)
|
|
481
|
+
return mcpServersVon(direkt);
|
|
482
|
+
const echt = distro === undefined
|
|
483
|
+
? aufgeloest(pfad)
|
|
484
|
+
: await this.harness.bruecke(distro).aufgeloest(pfad).catch(() => undefined);
|
|
485
|
+
return mcpServersVon(echt === undefined ? undefined : objektFeld(projects, echt));
|
|
388
486
|
}
|
|
389
487
|
throw new OrtNichtUnterstuetzt(this.harness.name, ort.art);
|
|
390
488
|
}
|
|
391
|
-
|
|
489
|
+
/** `~/.claude.json`: unter Windows die im primären Homedir, in WSL die der Distro (E100). */
|
|
490
|
+
async claudeJsonLesen(distro) {
|
|
392
491
|
try {
|
|
393
|
-
const text =
|
|
394
|
-
|
|
492
|
+
const text = distro === undefined
|
|
493
|
+
? await readFile(path.join(this.home(), '.claude.json'), 'utf8')
|
|
494
|
+
: await this.harness.bruecke(distro).claudeJsonLesen();
|
|
495
|
+
return text === undefined ? undefined : JSON.parse(ohneBom(text));
|
|
395
496
|
}
|
|
396
497
|
catch {
|
|
397
498
|
return undefined;
|
|
@@ -404,42 +505,145 @@ export class ClaudeCode {
|
|
|
404
505
|
ausfuehrer;
|
|
405
506
|
/** Zugriff je Ort (E78): lokal wie bisher, für ein WSL-Heim die Bridge. */
|
|
406
507
|
zugriffFuer;
|
|
407
|
-
/**
|
|
508
|
+
/** Die WSL-Anbindung (E100): claude-CLI je Ort, Bridge je Distro, Distros der eingetragenen Orte. */
|
|
509
|
+
wsl;
|
|
510
|
+
/**
|
|
511
|
+
* Plugins (E16) und MCP-Server (E61) setzt `vorhanden()`, sobald irgendwo eine claude-CLI
|
|
512
|
+
* antwortet (E100, E101): unter Windows oder in der Distro eines eingetragenen WSL-Orts. Ob es an
|
|
513
|
+
* einem bestimmten Ort geht, entscheidet der Ort (`cliPruefen`).
|
|
514
|
+
*/
|
|
408
515
|
plugins;
|
|
409
|
-
/** Wird von `vorhanden()` gesetzt, sobald die claude-CLI auf dem PATH liegt (E61). */
|
|
410
516
|
mcp;
|
|
411
|
-
|
|
517
|
+
/**
|
|
518
|
+
* Ob die claude-CLI antwortet, je Welt: Schlüssel '' für Windows, sonst die Distro. Ein positives
|
|
519
|
+
* Ergebnis gilt für die Lebenszeit des Adapters, ein negatives verfällt nach 60 Sekunden (E103), damit
|
|
520
|
+
* eine laufende GUI eine inzwischen installierte claude findet. Solange `bis` fehlt (die Antwort steht
|
|
521
|
+
* aus) oder noch nicht erreicht ist, teilen sich parallele Anfragen das Versprechen.
|
|
522
|
+
*/
|
|
523
|
+
erreichbarkeit = new Map();
|
|
412
524
|
/**
|
|
413
525
|
* `heime` sagt, wo die Homedirs liegen (E78): das primäre und, seit F87, zusätzliche in einer
|
|
414
526
|
* WSL-Distro. Ein blanker Pfad steht weiterhin für „nur dieses eine Home", damit Tests und
|
|
415
|
-
* Aufrufer ohne Zustand unverändert bleiben.
|
|
527
|
+
* Aufrufer ohne Zustand unverändert bleiben. `wsl` verdrahtet `bauen` (E100); ohne Angabe gehen
|
|
528
|
+
* die Aufrufe an das echte wsl.exe, und als WSL-Orte zählen nur die zusätzlichen Heime.
|
|
416
529
|
*/
|
|
417
|
-
constructor(heime, ausfuehrer = claudeAusfuehrer, zugriffFuer) {
|
|
530
|
+
constructor(heime, ausfuehrer = claudeAusfuehrer, zugriffFuer, wsl) {
|
|
418
531
|
this.heime = typeof heime === 'string' ? nurHome(heime) : heime;
|
|
419
532
|
this.ausfuehrer = ausfuehrer;
|
|
420
533
|
const lokal = new LokalerZugriff();
|
|
421
534
|
this.zugriffFuer = zugriffFuer ?? (() => lokal);
|
|
535
|
+
const bruecken = new Map();
|
|
536
|
+
const langeBridge = wslAusfuehrerMit(120_000);
|
|
537
|
+
this.wsl = {
|
|
538
|
+
ausfuehrerFuer: wsl?.ausfuehrerFuer ??
|
|
539
|
+
((ort) => {
|
|
540
|
+
const distro = wslDistroVonOrt(ort);
|
|
541
|
+
return distro === undefined ? this.ausfuehrer : wslClaudeAusfuehrer(distro, langeBridge);
|
|
542
|
+
}),
|
|
543
|
+
bruecke: wsl?.bruecke ??
|
|
544
|
+
((distro) => {
|
|
545
|
+
let b = bruecken.get(distro);
|
|
546
|
+
if (b === undefined)
|
|
547
|
+
bruecken.set(distro, (b = new WslBruecke(distro)));
|
|
548
|
+
return b;
|
|
549
|
+
}),
|
|
550
|
+
jetzt: wsl?.jetzt ?? (() => Date.now()),
|
|
551
|
+
distros: wsl?.distros ??
|
|
552
|
+
(async () => this.heime
|
|
553
|
+
.zusaetzliche(this.name)
|
|
554
|
+
.map((heim) => wslDistroVonOrt({ art: 'home', heim: heim.adresse }))
|
|
555
|
+
.filter((distro) => distro !== undefined)),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
/** Die claude-CLI, die für diesen Ort zuständig ist (E100): die des Rechners oder die der Distro. */
|
|
559
|
+
ausfuehrerFuer(ort) {
|
|
560
|
+
return this.wsl.ausfuehrerFuer(ort);
|
|
561
|
+
}
|
|
562
|
+
/** Die Bridge in eine Distro, für das Lesen ihrer Dateien (E100). */
|
|
563
|
+
bruecke(distro) {
|
|
564
|
+
return this.wsl.bruecke(distro);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Ob an diesem Ort eine claude-CLI antwortet (`claude --version`), je Welt: ein „ja" bleibt, ein
|
|
568
|
+
* „nein" gilt 60 Sekunden (E103).
|
|
569
|
+
*/
|
|
570
|
+
async erreichbar(ort) {
|
|
571
|
+
const schluessel = wslDistroVonOrt(ort) ?? '';
|
|
572
|
+
const bekannt = this.erreichbarkeit.get(schluessel);
|
|
573
|
+
if (bekannt !== undefined && (bekannt.bis === undefined || this.wsl.jetzt() < bekannt.bis))
|
|
574
|
+
return await bekannt.versprechen;
|
|
575
|
+
const cli = schluessel === '' ? this.ausfuehrer : this.ausfuehrerFuer(ort);
|
|
576
|
+
const eintrag = {
|
|
577
|
+
versprechen: cli(['--version'])
|
|
578
|
+
.then(({ code }) => code === 0)
|
|
579
|
+
.catch(() => false)
|
|
580
|
+
.then((ok) => {
|
|
581
|
+
if (!ok)
|
|
582
|
+
eintrag.bis = this.wsl.jetzt() + ERREICHBARKEIT_NEGATIV_MS;
|
|
583
|
+
return ok;
|
|
584
|
+
}),
|
|
585
|
+
};
|
|
586
|
+
this.erreichbarkeit.set(schluessel, eintrag);
|
|
587
|
+
return await eintrag.versprechen;
|
|
588
|
+
}
|
|
589
|
+
/** Ob an diesem Ort eine claude-CLI antwortet, ohne zu werfen (E103): für Plätze, die ohne CLI nichts bringen. */
|
|
590
|
+
async cliErreichbar(ort) {
|
|
591
|
+
return await this.erreichbar(ort);
|
|
592
|
+
}
|
|
593
|
+
/** Wirft `ClaudeCliNichtErreichbar`, wenn an diesem Ort keine claude-CLI antwortet (E100). */
|
|
594
|
+
async cliPruefen(ort) {
|
|
595
|
+
if (!(await this.erreichbar(ort)))
|
|
596
|
+
throw new ClaudeCliNichtErreichbar({ harness: this.name, ort }, wslDistroVonOrt(ort));
|
|
422
597
|
}
|
|
423
598
|
/**
|
|
424
599
|
* Claude Code liegt auf dem Rechner, wenn es sein Home angelegt hat — oder wenn ein zusätzliches
|
|
425
600
|
* Homedir eingetragen ist (E78): wer eines eingetragen hat, hat Claude Code dort laufen, und ein
|
|
426
|
-
* Windows-Home ohne `.claude` soll die WSL-Ziele nicht mit verschwinden lassen.
|
|
427
|
-
*
|
|
601
|
+
* Windows-Home ohne `.claude` soll die WSL-Ziele nicht mit verschwinden lassen.
|
|
602
|
+
*
|
|
603
|
+
* Die Plugin- und die MCP-Fähigkeit (E100, E101) kommen dazu, sobald `claude --version` unter
|
|
604
|
+
* Windows durchläuft — oder, wenn dort keine CLI liegt, sobald sie in der Distro eines
|
|
605
|
+
* eingetragenen WSL-Orts antwortet. Die Distros werden erst dann befragt, nacheinander bis zur
|
|
606
|
+
* ersten Antwort, damit ein Rechner mit Windows-claude keinen wsl.exe-Start zahlt. Jede Welt wird
|
|
607
|
+
* einmal geprüft (ein „ja" bleibt); in der GUI kommt ein später eingetragener WSL-Ort beim nächsten Aufruf
|
|
608
|
+
* dazu, ohne die schon geprüften erneut zu fragen. Ein negatives Ergebnis verfällt nach 60 Sekunden
|
|
609
|
+
* (E103): ein erneuter Aufruf danach fragt noch einmal und setzt die Fähigkeiten, sobald claude
|
|
610
|
+
* inzwischen da ist.
|
|
428
611
|
*/
|
|
429
612
|
async vorhanden() {
|
|
430
613
|
const primaer = this.heime.primaer(this.name);
|
|
431
614
|
const da = (await stat(path.join(primaer, CLAUDE_VERZEICHNIS)).catch(() => undefined)) !== undefined ||
|
|
432
615
|
this.heime.zusaetzliche(this.name).length > 0;
|
|
433
|
-
if (
|
|
434
|
-
this.
|
|
435
|
-
|
|
436
|
-
if (code === 0) {
|
|
437
|
-
this.plugins = new ClaudePlugins(this, this.ausfuehrer);
|
|
438
|
-
this.mcp = new ClaudeMcp(this, this.ausfuehrer, () => this.heime.primaer(this.name));
|
|
439
|
-
}
|
|
616
|
+
if (this.mcp === undefined && (await this.irgendwoErreichbar())) {
|
|
617
|
+
this.plugins = new ClaudePlugins(this);
|
|
618
|
+
this.mcp = new ClaudeMcp(this, () => this.heime.primaer(this.name));
|
|
440
619
|
}
|
|
441
620
|
return da;
|
|
442
621
|
}
|
|
622
|
+
/** Antwortet irgendwo eine claude-CLI: unter Windows oder in einer Distro der WSL-Orte (E100). */
|
|
623
|
+
async irgendwoErreichbar() {
|
|
624
|
+
if (await this.erreichbar({ art: 'home' }))
|
|
625
|
+
return true;
|
|
626
|
+
const distros = await this.wsl.distros().catch(() => []);
|
|
627
|
+
for (const distro of new Set(distros))
|
|
628
|
+
if (await this.erreichbar(distroOrt(distro)))
|
|
629
|
+
return true;
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Je Welt mit antwortender claude-CLI ein Ort, der für sie steht (E101): das Windows-Home und je
|
|
634
|
+
* Distro der eingetragenen WSL-Orte ihr Heim. Anders als `irgendwoErreichbar` fragt es alle
|
|
635
|
+
* Distros, auch wenn Windows schon antwortet — jede Welt hat ihre eigenen Marketplaces.
|
|
636
|
+
*/
|
|
637
|
+
async erreichbareWelten() {
|
|
638
|
+
const welten = [];
|
|
639
|
+
if (await this.erreichbar({ art: 'home' }))
|
|
640
|
+
welten.push({ art: 'home' });
|
|
641
|
+
const distros = await this.wsl.distros().catch(() => []);
|
|
642
|
+
for (const distro of new Set(distros))
|
|
643
|
+
if (await this.erreichbar(distroOrt(distro)))
|
|
644
|
+
welten.push(distroOrt(distro));
|
|
645
|
+
return welten;
|
|
646
|
+
}
|
|
443
647
|
orte() {
|
|
444
648
|
return ['home', 'arbeitsverzeichnis'];
|
|
445
649
|
}
|
package/dist/adapter/wsl.js
CHANGED
|
@@ -6,20 +6,51 @@ import path from 'node:path';
|
|
|
6
6
|
import { WslBrueckeFehlgeschlagen } from "../kern/modell.js";
|
|
7
7
|
import { mitZeitlimit } from "./zeitlimit.js";
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Fehlschlag wird zum Code, nicht zur Ausnahme.
|
|
9
|
+
* Ein Ausführer für `wsl.exe` vom PATH, über cross-spawn wie die claude-CLI (E65/B7). stdin ist zu,
|
|
10
|
+
* sonst hängt eine Distro, die auf Eingabe wartet; das Zeitlimit (E98, `mitZeitlimit`) fängt alles
|
|
11
|
+
* Übrige. Ein Fehlschlag wird zum Code, nicht zur Ausnahme.
|
|
12
12
|
*/
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
13
|
+
export function wslAusfuehrerMit(zeitlimitMs) {
|
|
14
|
+
return (args) => new Promise((aufloesen) => {
|
|
15
|
+
const kind = spawn('wsl.exe', args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
16
|
+
mitZeitlimit(kind, zeitlimitMs);
|
|
17
|
+
const teile = [];
|
|
18
|
+
const fehlerTeile = [];
|
|
19
|
+
kind.stdout.on('data', (d) => teile.push(d));
|
|
20
|
+
kind.stderr.on('data', (d) => fehlerTeile.push(d));
|
|
21
|
+
kind.on('error', (fehler) => aufloesen({ ausgabe: Buffer.from(fehler.message, 'utf8'), code: 1 }));
|
|
22
|
+
kind.on('close', (code) => aufloesen({ ausgabe: Buffer.concat(code === 0 ? teile : [...teile, ...fehlerTeile]), code: code ?? 1 }));
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** Standard-Ausführer der Bridge: 60 s je Aufruf. */
|
|
26
|
+
export const wslAusfuehrer = wslAusfuehrerMit(60_000);
|
|
27
|
+
/**
|
|
28
|
+
* Sucht die claude-CLI in der Distro und ruft sie mit den übrigen Argumenten auf (E100). `sh -lc`
|
|
29
|
+
* liest das Login-Profil, damit `~/.local/bin` des Native-Installers im PATH steht; der Rückfall auf
|
|
30
|
+
* `$HOME/.local/bin/claude` greift, wenn das Profil ihn nicht einträgt. Gemessen in Ubuntu (dash) und
|
|
31
|
+
* Alpine (busybox), HARNESS-FAKTEN „claude-CLI in einer WSL-Distro aufrufen".
|
|
32
|
+
*/
|
|
33
|
+
export const CLAUDE_IN_WSL_SKRIPT = 'c=$(command -v claude || echo "$HOME/.local/bin/claude"); exec "$c" "$@"';
|
|
34
|
+
/**
|
|
35
|
+
* Die Argumente an wsl.exe für ein Programm in der Distro (E100). Nur `--exec` reicht jedes Argument
|
|
36
|
+
* unverändert durch; ohne (und mit `--`) setzt wsl.exe die Zeile zusammen und lässt sie von der
|
|
37
|
+
* Distro-Shell neu zerlegen, Positionsargumente gingen verloren (gemessen).
|
|
38
|
+
*/
|
|
39
|
+
export function wslExecArgs(distro, programm, cwd) {
|
|
40
|
+
return ['-d', distro, ...(cwd === undefined ? [] : ['--cd', cwd]), '--exec', ...programm];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Die claude-CLI einer Distro als Ausführer (E100), in derselben Form wie der Windows-Ausführer:
|
|
44
|
+
* `cwd` ist hier ein Linux-Pfad der Distro und geht als `--cd` an wsl.exe. Ausgabe UTF-8 (Ausgabe
|
|
45
|
+
* eines Linux-Programms über die Bridge, gemessen). Liegt keine claude in der Distro, endet das
|
|
46
|
+
* Skript mit Code 127.
|
|
47
|
+
*/
|
|
48
|
+
export function wslClaudeAusfuehrer(distro, ausfuehren) {
|
|
49
|
+
return async (args, cwd) => {
|
|
50
|
+
const { ausgabe, code } = await ausfuehren(wslExecArgs(distro, ['sh', '-lc', CLAUDE_IN_WSL_SKRIPT, 'claude', ...args], cwd));
|
|
51
|
+
return { stdout: ausgabe.toString('utf8'), code };
|
|
52
|
+
};
|
|
53
|
+
}
|
|
23
54
|
/**
|
|
24
55
|
* Eine wsl.exe-eigene Ausgabe als Text (E78, gemessen): UTF-16LE, mit BOM. Node kennt die Kodierung
|
|
25
56
|
* als „utf16le"; die BOM bleibt beim Dekodieren als `\uFEFF` stehen und wird hier entfernt. Ein
|
|
@@ -96,6 +127,45 @@ export class WslBruecke {
|
|
|
96
127
|
throw new WslBrueckeFehlgeschlagen(this.distro, text);
|
|
97
128
|
return text;
|
|
98
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Ein Programm in der Distro über `--exec` (E100): Argumente kommen unverändert an, `cwd` ist ein
|
|
132
|
+
* Linux-Pfad. Ausgabe UTF-8; ein Fehlschlag ist kein Fehler, sondern ein Code.
|
|
133
|
+
*/
|
|
134
|
+
async exec(programm, cwd) {
|
|
135
|
+
const { ausgabe, code } = await this.ausfuehren(wslExecArgs(this.distro, programm, cwd));
|
|
136
|
+
return { text: ausgabe.toString('utf8'), code };
|
|
137
|
+
}
|
|
138
|
+
/** Eine Datei der Distro als Text; `undefined`, wenn sie fehlt oder die Bridge schweigt. */
|
|
139
|
+
async dateiLesen(pfad) {
|
|
140
|
+
const { text, code } = await this.exec(['cat', pfad]);
|
|
141
|
+
return code === 0 ? text : undefined;
|
|
142
|
+
}
|
|
143
|
+
/** `~/.claude.json` des Standardbenutzers der Distro (E100); `undefined` ohne Datei. */
|
|
144
|
+
async claudeJsonLesen() {
|
|
145
|
+
const { text, code } = await this.exec(['sh', '-c', 'cat "$HOME/.claude.json"']);
|
|
146
|
+
return code === 0 ? text : undefined;
|
|
147
|
+
}
|
|
148
|
+
/** Der in der Distro aufgelöste Pfad (`realpath`); `undefined`, wenn es ihn nicht gibt. */
|
|
149
|
+
async aufgeloest(pfad) {
|
|
150
|
+
const { text, code } = await this.exec(['realpath', pfad]);
|
|
151
|
+
const echt = text.trim();
|
|
152
|
+
return code === 0 && echt.length > 0 ? echt : undefined;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Die Git-Wurzel, in der ein Linux-Pfad liegt, und der Pfad relativ dazu (E91, E100). Über die
|
|
156
|
+
* Bridge, weil git für Windows ein Repo unter `\\wsl$` als „dubious ownership" ablehnt (gemessen).
|
|
157
|
+
* `--show-prefix` ist an der Wurzel selbst leer; ein Vergleich der Pfade wäre an einem Symlink im
|
|
158
|
+
* Pfad falsch. Kein Repo, kein git in der Distro oder kein Ordner: `null`.
|
|
159
|
+
*/
|
|
160
|
+
async gitWurzel(pfad) {
|
|
161
|
+
const { text, code } = await this.exec(['git', 'rev-parse', '--show-toplevel', '--show-prefix'], pfad);
|
|
162
|
+
if (code !== 0)
|
|
163
|
+
return null;
|
|
164
|
+
const [wurzel, prefix] = text.split('\n').map((zeile) => zeile.replace(/\r$/, '').trim());
|
|
165
|
+
if (wurzel === undefined || wurzel.length === 0)
|
|
166
|
+
return null;
|
|
167
|
+
return { wurzel, relativerPfad: (prefix ?? '').replace(/\/+$/, '') };
|
|
168
|
+
}
|
|
99
169
|
/** Dasselbe, aber ein Fehlschlag ist kein Fehler: `undefined`. Für lesende Abfragen. */
|
|
100
170
|
async shWeich(skript) {
|
|
101
171
|
const { ausgabe, code } = await this.ausfuehren(['-d', this.distro, '/bin/sh', '-c', skript]);
|
package/dist/cli/hilfe.js
CHANGED
|
@@ -106,8 +106,9 @@ Harnesses, die hmgr verwaltet: standardmäßig nur claude-code (E38).
|
|
|
106
106
|
|
|
107
107
|
Ein Arbeitsverzeichnis unter Windows, das in einer WSL-Distro liegt (\\\\wsl$\\<distro>\\… oder
|
|
108
108
|
\\\\wsl.localhost\\<distro>\\…, E96), bedient hmgr über die Bridge dieser Distro, wie ein Homedir in
|
|
109
|
-
WSL: der Link liegt im Linux-Dateisystem und zeigt auf den Windows-Checkout (/mnt/c). MCP-Server
|
|
110
|
-
|
|
109
|
+
WSL: der Link liegt im Linux-Dateisystem und zeigt auf den Windows-Checkout (/mnt/c). MCP-Server
|
|
110
|
+
hängt hmgr dort über die claude-CLI der Distro ein (hmgr help mcp, E100). Plugins gibt es dort noch
|
|
111
|
+
nicht, die claude-CLI dieses Rechners schreibt sie nur in die Windows-Konfiguration.
|
|
111
112
|
|
|
112
113
|
Schalter:
|
|
113
114
|
--composer <pfad> nur bei add: composer.json wählen, wenn unter dem Verzeichnis mehrere in
|
|
@@ -228,11 +229,21 @@ Das Schreiben in einen Zeiger ist gesperrt, bis das Composer-Thema entschieden i
|
|
|
228
229
|
Arbeitsverzeichnis mit Zeiger brechen add und remove ohne --local ab, nichts wird installiert,
|
|
229
230
|
verlinkt oder entfernt.
|
|
230
231
|
|
|
232
|
+
Unter Windows gehen Plugins auch in WSL (E101): im Homedir einer Distro (--ort wsl:<distro>,
|
|
233
|
+
eingetragen mit hmgr harness home claude-code add wsl:<distro>) und in einem Arbeitsverzeichnis
|
|
234
|
+
unter \\\\wsl$\\<distro>\\… oder \\\\wsl.localhost\\<distro>\\…. hmgr ruft dann die claude-CLI der Distro
|
|
235
|
+
über wsl.exe auf, wie bei MCP-Servern. Jede Claude-Installation führt ihre eigene Marketplace-Liste:
|
|
236
|
+
hmgr trägt den Marketplace in der Distro ein, und hmgr update zieht die Marketplaces unter Windows
|
|
237
|
+
und in jeder Distro der eingetragenen WSL-Orte nach, sofern dort eine claude antwortet. Der
|
|
238
|
+
Marketplace wird in der Distro geklont: eine SSH-URL oder ein privates HTTPS-Repo braucht dort
|
|
239
|
+
eigene Schlüssel bzw. Zugangsdaten, die von Windows gelten nicht. Welche claude zuständig ist,
|
|
240
|
+
entscheidet der Ort: liegt sie nur in WSL, gehen das Windows-Home und Windows-Arbeitsverzeichnisse
|
|
241
|
+
nicht, und umgekehrt.
|
|
242
|
+
|
|
231
243
|
Schalter (add und remove):
|
|
232
244
|
--harness <name> claude-code, hermes oder agents, Standard claude-code
|
|
233
|
-
--ort <ort> home, dir:<pfad> oder profil:<name>, Standard home. profil
|
|
234
|
-
|
|
235
|
-
gibt es keine Plugins (E78)
|
|
245
|
+
--ort <ort> home, wsl:<distro>, dir:<pfad> oder profil:<name>, Standard home. profil
|
|
246
|
+
kennt nur hermes
|
|
236
247
|
--composer <pfad> composer.json des Arbeitsverzeichnisses, wenn mehrere in Frage kommen
|
|
237
248
|
--local Plugin nicht in den Zeiger schreiben
|
|
238
249
|
--quelle <teil> nur bei add: Quelle eingrenzen, wenn der Name in mehreren Marketplaces steht
|
|
@@ -240,6 +251,7 @@ Schalter (add und remove):
|
|
|
240
251
|
Beispiele:
|
|
241
252
|
hmgr plugin list
|
|
242
253
|
hmgr plugin add superpowers --ort dir:.
|
|
254
|
+
hmgr plugin add superpowers --ort wsl:Ubuntu
|
|
243
255
|
hmgr plugin remove superpowers
|
|
244
256
|
|
|
245
257
|
Häufige Fälle:
|
|
@@ -394,6 +406,17 @@ mcp remove und mcp migrate: die Meldung nennt den Wurzelordner, dort gehören di
|
|
|
394
406
|
noch kein Arbeitsverzeichnis: hmgr dir add <wurzel>). Die Wurzel selbst und Ordner ohne Repo gehen
|
|
395
407
|
wie gewohnt.
|
|
396
408
|
|
|
409
|
+
Unter Windows geht MCP auch in WSL (E100): im Homedir einer Distro (--ort wsl:<distro>, eingetragen
|
|
410
|
+
mit hmgr harness home claude-code add wsl:<distro>) und in einem Arbeitsverzeichnis unter
|
|
411
|
+
\\\\wsl$\\<distro>\\… oder \\\\wsl.localhost\\<distro>\\…. hmgr ruft dann die claude-CLI der Distro über
|
|
412
|
+
wsl.exe auf und liest deren ~/.claude.json, nicht die von Windows; gesucht wird claude in einer
|
|
413
|
+
Login-Shell der Distro (command -v claude), sonst unter ~/.local/bin/claude. Die .mcp.json eines
|
|
414
|
+
solchen Projekts nennt hmgr mit ihrem Linux-Pfad. Die Git-Wurzel (siehe oben) ermittelt git in der
|
|
415
|
+
Distro, die Meldung nennt sie als \\\\wsl$-Pfad. Welche claude
|
|
416
|
+
zuständig ist, entscheidet der Ort: liegt sie nur in WSL, gehen das Windows-Home und
|
|
417
|
+
Windows-Arbeitsverzeichnisse nicht, und umgekehrt. Plugins gehen in WSL auf demselben Weg (E101,
|
|
418
|
+
hmgr help plugin).
|
|
419
|
+
|
|
397
420
|
mcp migrate ist der einzige Weg, auf dem hmgr die geteilte .mcp.json anfasst (E64): der Eintrag
|
|
398
421
|
wandert 1:1 aus der Datei in deinen privaten Scope am selben Ort und wird dort gelöscht. Auch ein
|
|
399
422
|
Eintrag, der nicht im Katalog steht, lässt sich so übernehmen — der Konfigblock kommt aus der Datei,
|
|
@@ -411,11 +434,13 @@ und, bei einem Hermes-Ziel, zusätzlich gegen <hermes-home>/.env.
|
|
|
411
434
|
|
|
412
435
|
Schalter (add, remove und migrate):
|
|
413
436
|
--harness <name> claude-code oder hermes, Standard claude-code
|
|
414
|
-
--ort <ort> home, dir:<pfad> oder profil:<name>, Standard home
|
|
437
|
+
--ort <ort> home, wsl:<distro>, dir:<pfad> oder profil:<name>, Standard home
|
|
415
438
|
|
|
416
439
|
Beispiele:
|
|
417
440
|
hmgr mcp list
|
|
418
441
|
hmgr mcp add teamwork --ort dir:.
|
|
442
|
+
hmgr mcp add teamwork --ort wsl:Ubuntu
|
|
443
|
+
hmgr mcp add teamwork --ort dir:\\\\wsl$\\Ubuntu\\home\\kollege\\proj
|
|
419
444
|
hmgr mcp add context7 --harness hermes --ort profil:researcher
|
|
420
445
|
hmgr mcp remove teamwork --ort dir:.
|
|
421
446
|
hmgr mcp migrate kollegen-tool --ort dir:.
|
|
@@ -431,6 +456,9 @@ Häufige Fälle:
|
|
|
431
456
|
den Eintrag in den privaten Scope und löscht ihn aus der Datei.
|
|
432
457
|
„gibt es keine geteilte Konfigurationsdatei": mcp migrate geht nur mit --harness claude-code und
|
|
433
458
|
--ort dir:<pfad>.
|
|
459
|
+
„ist keine claude-CLI erreichbar": an diesem Ort antwortet keine claude. In WSL Claude Code in der
|
|
460
|
+
Distro installieren und mit wsl -d <distro> --exec sh -lc "command -v claude" prüfen; unter
|
|
461
|
+
Windows muss claude im PATH liegen.
|
|
434
462
|
|
|
435
463
|
Siehe auch: hmgr help harness, hmgr help status`;
|
|
436
464
|
const SYNC = `hmgr sync [pfad]
|
|
@@ -625,8 +653,9 @@ dort nutzt. Das Home wird einmal ermittelt (wsl -d <distro> /bin/sh -c 'echo $HO
|
|
|
625
653
|
gespeichert. Jedes Homedir ist danach ein eigenständiges Ziel: es steht als eigene Zeile im status und
|
|
626
654
|
wird mit --ort wsl:<distro> angesprochen. Gespiegelt wird nichts, jedes Ziel wird einzeln verlinkt.
|
|
627
655
|
Links dort legt hmgr über wsl.exe an; das Linkziel bleibt der vorhandene Windows-Checkout, übersetzt
|
|
628
|
-
nach /mnt/c. MCP-Server
|
|
629
|
-
|
|
656
|
+
nach /mnt/c. MCP-Server hängt hmgr dort über die claude-CLI der Distro ein (hmgr help mcp, E100).
|
|
657
|
+
Plugins gibt es dort noch nicht: die claude-CLI dieses Rechners bedient sie nur im primären Homedir.
|
|
658
|
+
remove lässt hmgr das Homedir vergessen, die Links dort bleiben liegen.
|
|
630
659
|
|
|
631
660
|
Ein Arbeitsverzeichnis unter \\\\wsl$\\<distro>\\… bedient hmgr ebenso über die Distro (hmgr help dir, E96).
|
|
632
661
|
|
package/dist/cli/main.js
CHANGED
|
@@ -4,15 +4,15 @@ import path from 'node:path';
|
|
|
4
4
|
import { realpathSync } from 'node:fs';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
|
-
import { Anwendungsfaelle, } from "../kern/anwendungsfaelle.js";
|
|
8
|
-
import { AbhaengigkeitFehlt, BundleDateiFehler, BundleMehrdeutig, BundleMitgliederFehlen, BundleNamenskonflikte, BundleNichtInstalliert, BundleUnbekannt, EinschraenkungOhneZeiger, HarnessInaktiv, HarnessNichtVorhanden, HarnessTypUnbekannt, HeimAdresseUngueltig, HeimBereitsVorhanden, HeimFaehigkeitFehlt, HeimNurUnterWindows,
|
|
7
|
+
import { Anwendungsfaelle, pluginWeltText, } from "../kern/anwendungsfaelle.js";
|
|
8
|
+
import { AbhaengigkeitFehlt, BundleDateiFehler, BundleMehrdeutig, BundleMitgliederFehlen, BundleNamenskonflikte, BundleNichtInstalliert, BundleUnbekannt, EinschraenkungOhneZeiger, HarnessInaktiv, HarnessNichtVorhanden, HarnessTypUnbekannt, HeimAdresseUngueltig, HeimBereitsVorhanden, HeimFaehigkeitFehlt, HeimNurUnterWindows, ClaudeCliNichtErreichbar, HeimUnbekannt, KeinPassenderRef, LinkAnlegenFehlgeschlagen, McpAbweichenderEintrag, McpEbeneNichtUnterstuetzt, McpFaehigkeitFehlt, McpKatalogFehler, McpNichtInGeteilterDatei, McpNurGeteilterFundort, McpOrtUnterGitWurzel, McpUebernahmeNichtMoeglich, McpUebernahmeUnvollstaendig, McpUnbekannt, NameBelegt, OrtNichtUnterstuetzt, PluginMehrdeutig, PluginUnbekannt, QuelleAufraeumenLokaleAenderungen, QuelleNameBelegt, QuelleUnbekannt, SkillMehrdeutig, SkillOrdnerBereitsVerwaltet, SkillOrdnerFaehigkeitFehlt, SkillOrdnerNichtVerwaltet, SkillOrdnerUngueltig, SkillUnbekannt, ZeigerMehrdeutig, WslBrueckeFehlgeschlagen, ZeigerSchreibenGesperrt, heimAdresseAlsText, heimAdresseZerlegen, wslDistroVonOrt, wslUncZerlegen, pluginSchluessel, quelleName, quelleSchluessel, } from "../kern/modell.js";
|
|
9
9
|
import { Agents } from "../adapter/agents.js";
|
|
10
10
|
import { ClaudeCode, claudeAusfuehrer } from "../adapter/claude-code.js";
|
|
11
11
|
import { Hermes } from "../adapter/hermes.js";
|
|
12
12
|
import { ComposerZeiger } from "../adapter/composer.js";
|
|
13
13
|
import { DateisystemZiele, LokalerZugriff } from "../adapter/dateisystem.js";
|
|
14
14
|
import { ZustandHeime } from "../adapter/heime.js";
|
|
15
|
-
import { WslBruecke, WslZugriff, wslAusfuehrer, wslDistros } from "../adapter/wsl.js";
|
|
15
|
+
import { WslBruecke, WslZugriff, wslAusfuehrer, wslAusfuehrerMit, wslClaudeAusfuehrer, wslDistros, } from "../adapter/wsl.js";
|
|
16
16
|
import { GitQuellen } from "../adapter/git.js";
|
|
17
17
|
import { DateiMcpKatalog } from "../adapter/mcp-katalog.js";
|
|
18
18
|
import { cliUpdateHinweis } from "../adapter/npm-registry.js";
|
|
@@ -58,7 +58,33 @@ export function bauen(umgebung, optionen = {}) {
|
|
|
58
58
|
const distro = wslDistroVonOrt(ort);
|
|
59
59
|
return distro === undefined ? lokal : new WslZugriff(bruecke(distro));
|
|
60
60
|
};
|
|
61
|
-
|
|
61
|
+
// Die claude-CLI je Ort (E100): für ein WSL-Heim und ein Arbeitsverzeichnis unter \\wsl$ die der
|
|
62
|
+
// Distro, über `wsl.exe --exec` mit eigenem Zeitlimit von 120 s wie unter Windows — nicht die 60 s
|
|
63
|
+
// der Bridge, `mcp add-json` startet in der Distro eine ganze Node-Laufzeit.
|
|
64
|
+
const windowsClaude = optionen.claudeAusfuehrer ?? claudeAusfuehrer;
|
|
65
|
+
const wslClaudeAusfuehren = optionen.wslAusfuehrer ?? wslAusfuehrerMit(120_000);
|
|
66
|
+
const claudeFuerOrt = (ort) => {
|
|
67
|
+
const distro = wslDistroVonOrt(ort);
|
|
68
|
+
return distro === undefined ? windowsClaude : wslClaudeAusfuehrer(distro, wslClaudeAusfuehren);
|
|
69
|
+
};
|
|
70
|
+
// Die Distros der eingetragenen WSL-Orte (E100): zusätzliche Heime und Arbeitsverzeichnisse unter
|
|
71
|
+
// \\wsl$. Hier statt im Adapter, weil nur der Zustand die Arbeitsverzeichnisse kennt. Außerhalb von
|
|
72
|
+
// Windows keine: ein von dort übernommener Zustand soll hier kein wsl.exe starten.
|
|
73
|
+
const wslDistrosDerOrte = async () => {
|
|
74
|
+
if (umgebungPort.plattform() !== 'win32')
|
|
75
|
+
return [];
|
|
76
|
+
const distros = heime
|
|
77
|
+
.zusaetzliche('claude-code')
|
|
78
|
+
.map((heim) => wslDistroVonOrt({ art: 'home', heim: heim.adresse }));
|
|
79
|
+
for (const pfad of (await zustand.lesen()).arbeitsverzeichnisse)
|
|
80
|
+
distros.push(wslUncZerlegen(pfad)?.distro);
|
|
81
|
+
return [...new Set(distros.filter((distro) => distro !== undefined))];
|
|
82
|
+
};
|
|
83
|
+
const claude = new ClaudeCode(heime, windowsClaude, zugriffFuerOrt, {
|
|
84
|
+
ausfuehrerFuer: claudeFuerOrt,
|
|
85
|
+
bruecke,
|
|
86
|
+
distros: wslDistrosDerOrte,
|
|
87
|
+
});
|
|
62
88
|
const ports = {
|
|
63
89
|
harnesses: [claude, new Hermes(heime, zugriffFuerOrt), new Agents(heime, zugriffFuerOrt)],
|
|
64
90
|
quellen: new GitQuellen(wurzel),
|
|
@@ -72,6 +98,7 @@ export function bauen(umgebung, optionen = {}) {
|
|
|
72
98
|
wsl: {
|
|
73
99
|
distros: async () => await wslDistros(wslAusfuehren),
|
|
74
100
|
home: async (distro) => await bruecke(distro).home(),
|
|
101
|
+
gitWurzel: async (distro, linuxPfad) => await bruecke(distro).gitWurzel(linuxPfad),
|
|
75
102
|
},
|
|
76
103
|
verlauf,
|
|
77
104
|
};
|
|
@@ -729,7 +756,7 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
729
756
|
: updateHinweis !== undefined
|
|
730
757
|
? await anwendungsfaelle.quelleZuHinweis(updateHinweis)
|
|
731
758
|
: undefined;
|
|
732
|
-
const { checkouts: liste, bundles, aufraeumen, doctor, } = await anwendungsfaelle.aktualisieren({ ausloeser: 'cli', ...(nurQuelle === undefined ? {} : { quelle: nurQuelle }) });
|
|
759
|
+
const { checkouts: liste, pluginFehler, bundles, aufraeumen, doctor, } = await anwendungsfaelle.aktualisieren({ ausloeser: 'cli', ...(nurQuelle === undefined ? {} : { quelle: nurQuelle }) });
|
|
733
760
|
if (liste.length === 0)
|
|
734
761
|
console.log('Keine Checkouts.');
|
|
735
762
|
for (const { checkout, aktualisierung, geaenderteSkills } of liste) {
|
|
@@ -748,6 +775,9 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
748
775
|
else
|
|
749
776
|
console.log(` fehlgeschlagen ${kopf} ${aktualisierung.fehler}`);
|
|
750
777
|
}
|
|
778
|
+
// Plugins je Welt (E101): genannt wird nur ein Fehlschlag, eine Änderung lässt sich nicht messen (E80).
|
|
779
|
+
for (const { ziel, fehler } of pluginFehler)
|
|
780
|
+
console.log(` fehlgeschlagen Plugins ${ziel.harness} ${pluginWeltText(ziel.ort)} ${fehler}`);
|
|
751
781
|
// Bundles im selben Lauf (E69, F64=O1): eine geänderte Definition wird hier nachgezogen.
|
|
752
782
|
for (const zeile of bundleBericht(bundles))
|
|
753
783
|
console.log(zeile);
|
|
@@ -1152,8 +1182,7 @@ function istModellFehler(fehler) {
|
|
|
1152
1182
|
fehler instanceof HeimBereitsVorhanden ||
|
|
1153
1183
|
fehler instanceof HeimFaehigkeitFehlt ||
|
|
1154
1184
|
fehler instanceof HeimNurUnterWindows ||
|
|
1155
|
-
fehler instanceof
|
|
1156
|
-
fehler instanceof ArbeitsverzeichnisOhneCliZugriff ||
|
|
1185
|
+
fehler instanceof ClaudeCliNichtErreichbar ||
|
|
1157
1186
|
fehler instanceof HeimUnbekannt ||
|
|
1158
1187
|
fehler instanceof WslBrueckeFehlgeschlagen);
|
|
1159
1188
|
}
|
|
@@ -1206,10 +1235,10 @@ function naechsterSchritt(fehler) {
|
|
|
1206
1235
|
return `hmgr harness home ${fehler.harness} zeigt die eingetragenen Homedirs.`;
|
|
1207
1236
|
if (fehler instanceof HeimUnbekannt)
|
|
1208
1237
|
return `hmgr harness home ${fehler.harness} zeigt die eingetragenen Homedirs, add trägt eines ein.`;
|
|
1209
|
-
if (fehler instanceof
|
|
1210
|
-
return
|
|
1211
|
-
|
|
1212
|
-
|
|
1238
|
+
if (fehler instanceof ClaudeCliNichtErreichbar)
|
|
1239
|
+
return fehler.distro === undefined
|
|
1240
|
+
? 'Claude Code hier installieren, oder an einem Ort arbeiten, an dem claude läuft, etwa --ort wsl:<distro>.'
|
|
1241
|
+
: `Claude Code in der Distro installieren, dann prüfen: wsl -d ${fehler.distro} --exec sh -lc "command -v claude"`;
|
|
1213
1242
|
if (fehler instanceof WslBrueckeFehlgeschlagen)
|
|
1214
1243
|
return 'wsl -l -q zeigt die installierten Distros. Startet die Distro nicht, hilft wsl --shutdown und ein erneuter Versuch.';
|
|
1215
1244
|
if (fehler instanceof QuelleUnbekannt)
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
<h1>MCP-Server</h1>
|
|
143
143
|
<!-- Der Pfad steht hier in POSIX-Schreibweise und wird beim Start in die Schreibweise
|
|
144
144
|
der Plattform gesetzt (E68, ladeUmgebung im Renderer). -->
|
|
145
|
-
<p>Dein Katalog aus <span class="mono" id="mcpkatalogpfad">~/.hmgr/mcp.yaml</span>. Häng einen Server dorthin ein, wo du ihn brauchst: in ein Arbeitsverzeichnis, damit er nur dort Kontext kostet, oder global. Den Katalog selbst pflegst du in der Datei, hmgr liest sie nur.
|
|
145
|
+
<p>Dein Katalog aus <span class="mono" id="mcpkatalogpfad">~/.hmgr/mcp.yaml</span>. Häng einen Server dorthin ein, wo du ihn brauchst: in ein Arbeitsverzeichnis, damit er nur dort Kontext kostet, oder global. Den Katalog selbst pflegst du in der Datei, hmgr liest sie nur. In WSL – im Homedir einer Distro und in Arbeitsverzeichnissen unter \\wsl$\… – hängt hmgr den Server über die claude-CLI der Distro ein. Plugins gehen dort auf demselben Weg.</p>
|
|
146
146
|
</div>
|
|
147
147
|
|
|
148
148
|
<div class="mcpliste" id="mcpliste"></div>
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
<section id="ansicht-plugins" hidden>
|
|
168
168
|
<div class="ansichtskopf">
|
|
169
169
|
<h1>Plugins</h1>
|
|
170
|
-
<p>Ein Marketplace ist eine Quelle mit einer <span class="mono">marketplace.json</span>. Die Plugins darin bündeln Skills und können voneinander abhängen. Harnesses ohne Plugin-Konzept bekommen von hmgr stattdessen die flache Skill-Liste verlinkt.</p>
|
|
170
|
+
<p>Ein Marketplace ist eine Quelle mit einer <span class="mono">marketplace.json</span>. Die Plugins darin bündeln Skills und können voneinander abhängen. Harnesses ohne Plugin-Konzept bekommen von hmgr stattdessen die flache Skill-Liste verlinkt. In WSL installiert hmgr Plugins über die claude-CLI der Distro; jede Claude-Installation führt ihre eigene Marketplace-Liste, hmgr trägt den Marketplace dort ein und zieht ihn beim Aktualisieren nach.</p>
|
|
171
171
|
</div>
|
|
172
172
|
<div id="pluginliste"></div>
|
|
173
173
|
</section>
|
|
@@ -1202,6 +1202,23 @@
|
|
|
1202
1202
|
var verzMcpZiele = MCP_PLAETZE.filter(function (p) {
|
|
1203
1203
|
return p.ort.art === 'arbeitsverzeichnis' && p.ort.pfad === pfad;
|
|
1204
1204
|
});
|
|
1205
|
+
// E103: mcpPlaetze() lässt Orte weg, an denen der Harness nicht bedienbar ist (keine CLI). Hängt
|
|
1206
|
+
// dort schon etwas, bleibt der Abschnitt samt Entfernen-Knopf trotzdem stehen; nur das
|
|
1207
|
+
// „hinzufügen" entfällt (mcpKannAn).
|
|
1208
|
+
var gesehen = {};
|
|
1209
|
+
verzMcpZiele.forEach(function (p) { gesehen[zielKey(p)] = true; });
|
|
1210
|
+
var vorhandene = [];
|
|
1211
|
+
MCP_KATALOG.forEach(function (k) {
|
|
1212
|
+
k.eingehaengt.forEach(function (z) { vorhandene.push(z); });
|
|
1213
|
+
k.geteilteFundorte.forEach(function (g) { vorhandene.push(g.ziel); });
|
|
1214
|
+
});
|
|
1215
|
+
MCP_FREMD.forEach(function (x) { vorhandene.push(x.ziel); });
|
|
1216
|
+
MCP_FREMD_GETEILT.forEach(function (x) { vorhandene.push(x.ziel); });
|
|
1217
|
+
vorhandene.forEach(function (z) {
|
|
1218
|
+
if (z.ort.art !== 'arbeitsverzeichnis' || z.ort.pfad !== pfad || gesehen[zielKey(z)]) return;
|
|
1219
|
+
gesehen[zielKey(z)] = true;
|
|
1220
|
+
verzMcpZiele.push(z);
|
|
1221
|
+
});
|
|
1205
1222
|
|
|
1206
1223
|
if (reihenfolge.length === 0) {
|
|
1207
1224
|
var leer = e('div', 'leerzeile');
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
// Anwendungsfälle. Pfade werden hier nur als Zeichenketten normalisiert, gelesen wird über die Ports.
|
|
2
2
|
import path from 'node:path';
|
|
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, wslDistroVonOrt, wslUncZerlegen, marketplaceUrl, pluginSchluessel, pluginSchluesselZerlegen, quelleAlsText, quelleMitNamen, quelleName, quelleNameAbleiten, quelleNameFrei, quelleSchluessel, zielSchluessel, } from "./modell.js";
|
|
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, wslDistroVonOrt, wslUncBilden, wslUncZerlegen, marketplaceUrl, pluginSchluessel, pluginSchluesselZerlegen, quelleAlsText, quelleMitNamen, quelleName, quelleNameAbleiten, quelleNameFrei, quelleSchluessel, zielSchluessel, } from "./modell.js";
|
|
4
4
|
import { mitBegrenzung } from "./begrenzung.js";
|
|
5
5
|
import { passenderRef } from "./version.js";
|
|
6
6
|
import { kannIstRegistriert, kannMcp, kannPlugins, kannProfile, kannSkillOrdner, kannZurueckbauen, kannZusaetzlicheHeime, } from "../ports/harness.js";
|
|
7
7
|
/** So viele `plugin list`-Prozesse laufen im Ist-Zustand höchstens gleichzeitig (E93). */
|
|
8
8
|
const PLUGIN_ABFRAGEN_GLEICHZEITIG = 6;
|
|
9
|
+
/**
|
|
10
|
+
* Die Welt eines Orts als Text für Bericht und Verlauf (E101), in der Schreibweise von `--ort`:
|
|
11
|
+
* „home" für die Installation dieses Rechners, „wsl:<distro>" für die einer Distro.
|
|
12
|
+
*/
|
|
13
|
+
export function pluginWeltText(ort) {
|
|
14
|
+
const distro = wslDistroVonOrt(ort);
|
|
15
|
+
return distro === undefined ? 'home' : `wsl:${distro}`;
|
|
16
|
+
}
|
|
9
17
|
/**
|
|
10
18
|
* Frage vor dem Löschen, eine für alle Kandidaten (E28). Der Kern formuliert sie fertig, der
|
|
11
19
|
* Adapter stellt sie: Anzahl und Pfade, damit der Nutzer sieht, worüber er entscheidet.
|
|
@@ -357,6 +365,15 @@ export class Anwendungsfaelle {
|
|
|
357
365
|
async mcpGitWurzelFuer(faehig, ziel) {
|
|
358
366
|
if (faehig.anGitWurzelGebunden !== true || ziel.ort.art !== 'arbeitsverzeichnis')
|
|
359
367
|
return null;
|
|
368
|
+
// In WSL fragt hmgr git in der Distro (E100); die Wurzel kommt in der Schreibweise des
|
|
369
|
+
// gespeicherten Pfads zurück, damit die Meldung einen Pfad nennt, den `hmgr dir add` nimmt.
|
|
370
|
+
const wsl = wslUncZerlegen(ziel.ort.pfad);
|
|
371
|
+
if (wsl !== undefined) {
|
|
372
|
+
const funde = await this.p.wsl.gitWurzel(wsl.distro, wsl.linuxPfad).catch(() => null);
|
|
373
|
+
if (funde === null || funde.relativerPfad === '')
|
|
374
|
+
return null;
|
|
375
|
+
return wslUncBilden(ziel.ort.pfad, funde.wurzel) ?? funde.wurzel;
|
|
376
|
+
}
|
|
360
377
|
const kopie = await this.p.quellen.arbeitskopie(ziel.ort.pfad).catch(() => null);
|
|
361
378
|
if (kopie === null || kopie.relativerPfad === '')
|
|
362
379
|
return null;
|
|
@@ -464,18 +481,17 @@ export class Anwendungsfaelle {
|
|
|
464
481
|
if (!kannMcp(harness))
|
|
465
482
|
continue;
|
|
466
483
|
const ebenen = harness.mcp.ebenen();
|
|
467
|
-
//
|
|
468
|
-
//
|
|
484
|
+
// Alle Homedirs samt WSL-Heimen und alle Arbeitsverzeichnisse samt denen unter \\wsl$ (E100):
|
|
485
|
+
// der Adapter spricht dort die claude-CLI der Distro an. Ob an einem Ort eine CLI antwortet,
|
|
486
|
+
// entscheidet er beim Schreiben; die Übersicht liest ohnehin nur.
|
|
469
487
|
if (ebenen.includes('home'))
|
|
470
|
-
ziele.push(
|
|
488
|
+
ziele.push(...this.homeZiele(harness));
|
|
471
489
|
if (ebenen.includes('profil') && kannProfile(harness))
|
|
472
490
|
for (const name of await harness.profile())
|
|
473
491
|
ziele.push({ harness: harness.name, ort: { art: 'profil', name } });
|
|
474
|
-
// Arbeitsverzeichnisse in WSL (E96) ebenso nicht: ihre Konfiguration liegt in der Distro.
|
|
475
492
|
if (ebenen.includes('arbeitsverzeichnis'))
|
|
476
493
|
for (const pfad of zustand.arbeitsverzeichnisse)
|
|
477
|
-
|
|
478
|
-
ziele.push(arbeitsverzeichnisZiel(harness.name, pfad));
|
|
494
|
+
ziele.push(arbeitsverzeichnisZiel(harness.name, pfad));
|
|
479
495
|
}
|
|
480
496
|
return ziele;
|
|
481
497
|
}
|
|
@@ -483,10 +499,15 @@ export class Anwendungsfaelle {
|
|
|
483
499
|
* Die Plätze für den Einhängen-Dialog der GUI (E61-Folgerunde, F42): dieselbe Liste wie in
|
|
484
500
|
* `mcpUebersicht`, aber roh, ohne den Abgleich mit Katalog-Einträgen. Die Fähigkeit hängt am
|
|
485
501
|
* Adapter, nicht am Harness-Namen (E16): agents kommt nie vor, weil es `mcp` nicht implementiert.
|
|
502
|
+
* Orte, an denen der Harness nicht bedienbar ist (E103, etwa keine claude-CLI), fehlen; die
|
|
503
|
+
* Übersicht lässt sie stehen, damit vorhandene Einträge sichtbar bleiben.
|
|
486
504
|
*/
|
|
487
505
|
async mcpPlaetze() {
|
|
488
506
|
const plaetze = [];
|
|
489
507
|
for (const ziel of await this.mcpZiele()) {
|
|
508
|
+
const mcp = this.harnessMitMcp(ziel.harness).mcp;
|
|
509
|
+
if (mcp.ortBedienbar !== undefined && !(await mcp.ortBedienbar(ziel.ort)))
|
|
510
|
+
continue;
|
|
490
511
|
const wurzel = await this.mcpGitWurzelFuer(this.harnessMitMcp(ziel.harness).mcp, ziel);
|
|
491
512
|
plaetze.push(wurzel === null ? ziel : { ...ziel, gitWurzel: wurzel });
|
|
492
513
|
}
|
|
@@ -1526,8 +1547,9 @@ export class Anwendungsfaelle {
|
|
|
1526
1547
|
if (!harness.orte().includes(ziel.ort.art))
|
|
1527
1548
|
throw new OrtNichtUnterstuetzt(harness.name, ziel.ort.art);
|
|
1528
1549
|
if (kannPlugins(harness)) {
|
|
1529
|
-
|
|
1530
|
-
|
|
1550
|
+
// Die Marketplace-Liste gehört zur Welt des Ziels (E101): ein WSL-Ziel trägt ihn in der Distro ein.
|
|
1551
|
+
if (!(await harness.plugins.marketplaceBekannt(fund.plugin.marketplace, ziel.ort)))
|
|
1552
|
+
await harness.plugins.marketplaceHinzufuegen(fund.plugin.marketplace, marketplaceUrl(fund.quelle), ziel.ort);
|
|
1531
1553
|
await harness.vorbereiten(ziel.ort);
|
|
1532
1554
|
await harness.plugins.installieren(fund.plugin.name, fund.plugin.marketplace, ziel);
|
|
1533
1555
|
return { plugin: fund.plugin, art: 'installiert', skills: [], imZeiger: false };
|
|
@@ -1860,13 +1882,26 @@ export class Anwendungsfaelle {
|
|
|
1860
1882
|
}
|
|
1861
1883
|
this.nachgezogen.add(quelleSchluessel(quelle));
|
|
1862
1884
|
}
|
|
1863
|
-
// Nach den Checkouts die Plugins nachziehen (E14)
|
|
1885
|
+
// Nach den Checkouts die Plugins nachziehen (E14), je Welt mit erreichbarer Installation
|
|
1886
|
+
// (E101): Windows und jede Distro der eingetragenen WSL-Orte haben ihre eigenen Marketplaces.
|
|
1887
|
+
// Ein Fehlschlag in einer Welt steht im Ergebnis und bricht den Lauf nicht ab.
|
|
1864
1888
|
const pluginHarnesses = [];
|
|
1889
|
+
const pluginFehler = [];
|
|
1865
1890
|
for (const harness of nurSchluessel !== undefined ? [] : await this.vorhandeneHarnesses()) {
|
|
1866
1891
|
if (!kannPlugins(harness))
|
|
1867
1892
|
continue;
|
|
1868
|
-
|
|
1869
|
-
|
|
1893
|
+
let erfolgreich = false;
|
|
1894
|
+
for (const ort of await harness.plugins.welten()) {
|
|
1895
|
+
try {
|
|
1896
|
+
await harness.plugins.aktualisieren(ort);
|
|
1897
|
+
erfolgreich = true;
|
|
1898
|
+
}
|
|
1899
|
+
catch (fehler) {
|
|
1900
|
+
pluginFehler.push({ ziel: { harness: harness.name, ort }, fehler: fehlertext(fehler) });
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
if (erfolgreich)
|
|
1904
|
+
pluginHarnesses.push(harness.name);
|
|
1870
1905
|
}
|
|
1871
1906
|
// Bundles vor dem Aufräumen (E69, F64=O1): der Abgleich legt und entfernt Links, und erst
|
|
1872
1907
|
// danach steht fest, in welchen Checkout noch ein Link zeigt. Ein Fehlschlag je Bundle steckt
|
|
@@ -1878,15 +1913,16 @@ export class Anwendungsfaelle {
|
|
|
1878
1913
|
const doctorErgebnis = nurSchluessel !== undefined
|
|
1879
1914
|
? { fehlendeArbeitsverzeichnisse: [], vergessen: [] }
|
|
1880
1915
|
: await this.doctor({ rueckfragen });
|
|
1881
|
-
await this.verlaufVermerken(ausloeser, this.aktualisierungsEreignisse(checkouts, bundles, aufraeumen, doctorErgebnis));
|
|
1882
|
-
return { checkouts, pluginHarnesses, bundles, aufraeumen, doctor: doctorErgebnis };
|
|
1916
|
+
await this.verlaufVermerken(ausloeser, this.aktualisierungsEreignisse(checkouts, pluginFehler, bundles, aufraeumen, doctorErgebnis));
|
|
1917
|
+
return { checkouts, pluginHarnesses, pluginFehler, bundles, aufraeumen, doctor: doctorErgebnis };
|
|
1883
1918
|
}
|
|
1884
1919
|
/**
|
|
1885
|
-
* Ereignisse eines Aktualisieren-Laufs für den Verlauf (E80):
|
|
1886
|
-
*
|
|
1887
|
-
* Auskunft) — die bisherige Zeile „Plugins aktualisiert" war deshalb geschönt.
|
|
1920
|
+
* Ereignisse eines Aktualisieren-Laufs für den Verlauf (E80): eine Plugin-Änderung bleibt
|
|
1921
|
+
* draußen, weil sie sich nicht messen lässt (`claude plugin marketplace update` liefert keine
|
|
1922
|
+
* Auskunft) — die bisherige Zeile „Plugins aktualisiert" war deshalb geschönt. Ein Fehlschlag
|
|
1923
|
+
* je Welt (E101) steht dagegen drin.
|
|
1888
1924
|
*/
|
|
1889
|
-
aktualisierungsEreignisse(checkouts, bundles, aufraeumen, doctorErgebnis) {
|
|
1925
|
+
aktualisierungsEreignisse(checkouts, pluginFehler, bundles, aufraeumen, doctorErgebnis) {
|
|
1890
1926
|
const ereignisse = [];
|
|
1891
1927
|
for (const { checkout, aktualisierung, geaenderteSkills } of checkouts) {
|
|
1892
1928
|
if (aktualisierung.ergebnis === 'aktualisiert')
|
|
@@ -1905,6 +1941,8 @@ export class Anwendungsfaelle {
|
|
|
1905
1941
|
fehler: aktualisierung.fehler,
|
|
1906
1942
|
});
|
|
1907
1943
|
}
|
|
1944
|
+
for (const { ziel, fehler } of pluginFehler)
|
|
1945
|
+
ereignisse.push({ art: 'fehler', betrifft: `Plugins ${ziel.harness} ${pluginWeltText(ziel.ort)}`, fehler });
|
|
1908
1946
|
for (const eintrag of bundles) {
|
|
1909
1947
|
if (eintrag.ergebnis === 'fehlgeschlagen')
|
|
1910
1948
|
ereignisse.push({ art: 'fehler', betrifft: eintrag.bundle, fehler: eintrag.fehler ?? '' });
|
|
@@ -2365,20 +2403,18 @@ export class Anwendungsfaelle {
|
|
|
2365
2403
|
}
|
|
2366
2404
|
// Installierte Plugins je Ziel vorab, mit begrenzter Parallelität (E93): `claude plugin list`
|
|
2367
2405
|
// ist ein Prozess je Ziel (cwd-gebunden, kein Sammelaufruf), nacheinander dauert das bei vielen
|
|
2368
|
-
// Arbeitsverzeichnissen Sekunden.
|
|
2369
|
-
//
|
|
2370
|
-
// WSL-Distro nicht, ihre user-Liste gehörte zum Windows-Home.
|
|
2406
|
+
// Arbeitsverzeichnissen Sekunden. WSL-Ziele fragen die claude-CLI ihrer Distro (E101); ein Ort
|
|
2407
|
+
// ohne erreichbare CLI ergibt eine leere Liste.
|
|
2371
2408
|
const pluginListen = new Map();
|
|
2372
2409
|
if (mitPlugins) {
|
|
2373
2410
|
const abfragen = [];
|
|
2374
2411
|
for (const { harness, ziele } of plan)
|
|
2375
2412
|
if (kannPlugins(harness))
|
|
2376
2413
|
for (const ziel of ziele)
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
});
|
|
2414
|
+
abfragen.push({
|
|
2415
|
+
schluessel: zielSchluessel(ziel),
|
|
2416
|
+
abruf: () => harness.plugins.installierte(ziel).catch(() => []),
|
|
2417
|
+
});
|
|
2382
2418
|
const listen = await mitBegrenzung(PLUGIN_ABFRAGEN_GLEICHZEITIG, abfragen.map((a) => a.abruf));
|
|
2383
2419
|
abfragen.forEach((a, i) => pluginListen.set(a.schluessel, listen[i]));
|
|
2384
2420
|
}
|
package/dist/kern/modell.js
CHANGED
|
@@ -83,6 +83,19 @@ export function wslUncZerlegen(pfad) {
|
|
|
83
83
|
const rest = (treffer[2] ?? '').split('\\').filter((teil) => teil.length > 0);
|
|
84
84
|
return { distro, linuxPfad: `/${rest.join('/')}` };
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Ein Linux-Pfad der Distro in der UNC-Schreibweise eines vorhandenen WSL-Pfads (E100): Präfix und
|
|
88
|
+
* Distro kommen aus `vorlage` (so, wie der Nutzer sie geschrieben hat), der Rest aus `linuxPfad`.
|
|
89
|
+
* So nennt etwa die E91-Meldung eine Git-Wurzel, die sich direkt an `hmgr dir add` geben lässt.
|
|
90
|
+
* Ist `vorlage` kein WSL-UNC-Pfad: undefined.
|
|
91
|
+
*/
|
|
92
|
+
export function wslUncBilden(vorlage, linuxPfad) {
|
|
93
|
+
const treffer = /^(\\\\(?:\?\\UNC\\)?(?:wsl\$|wsl\.localhost)\\[^\\]+)/i.exec(vorlage.trim().replaceAll('/', '\\'));
|
|
94
|
+
if (treffer === null)
|
|
95
|
+
return undefined;
|
|
96
|
+
const teile = linuxPfad.split('/').filter((teil) => teil.length > 0);
|
|
97
|
+
return `${treffer[1]}\\${teile.join('\\')}`;
|
|
98
|
+
}
|
|
86
99
|
/** Die WSL-Distro, über deren Bridge ein Ort bedient wird: ein `wsl:`-Heim (E78) oder ein WSL-UNC-Arbeitsverzeichnis (E96). */
|
|
87
100
|
export function wslDistroVonOrt(ort) {
|
|
88
101
|
if (ort.art === 'home')
|
|
@@ -146,34 +159,30 @@ export class HeimUnbekannt extends Error {
|
|
|
146
159
|
}
|
|
147
160
|
}
|
|
148
161
|
/**
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
162
|
+
* An einem Ort antwortet keine claude-CLI (E100, E101). MCP-Server und Plugins verwaltet hmgr
|
|
163
|
+
* ausschließlich über die CLI (E16, E61); welche CLI zuständig ist, entscheidet der Ort: unter
|
|
164
|
+
* Windows die vom PATH, in einem WSL-Heim oder WSL-Arbeitsverzeichnis die der Distro. Gibt es nur
|
|
165
|
+
* eine von beiden, gehen die Orte der anderen Welt nicht.
|
|
152
166
|
*/
|
|
153
|
-
export class
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
constructor(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
super(`${harness === 'claude-code' ? 'Claude Code' : harness} kann ${was} im Arbeitsverzeichnis „${pfad}" nicht verwalten: ` +
|
|
173
|
-
'es liegt in WSL, die claude-CLI dieses Rechners schreibt nur in die Windows-Konfiguration. ' +
|
|
174
|
-
'Skill-Links legt hmgr dort selbst.');
|
|
175
|
-
this.harness = harness;
|
|
176
|
-
this.pfad = pfad;
|
|
167
|
+
export class ClaudeCliNichtErreichbar extends Error {
|
|
168
|
+
ziel;
|
|
169
|
+
distro;
|
|
170
|
+
constructor(ziel, distro) {
|
|
171
|
+
const ort = ziel.ort.art === 'home'
|
|
172
|
+
? ziel.ort.heim === undefined
|
|
173
|
+
? 'dem Homedir'
|
|
174
|
+
: `dem Homedir ${ziel.ort.heim}`
|
|
175
|
+
: ziel.ort.art === 'arbeitsverzeichnis'
|
|
176
|
+
? `dem Arbeitsverzeichnis „${ziel.ort.pfad}"`
|
|
177
|
+
: `dem Profil ${ziel.ort.name}`;
|
|
178
|
+
super(distro === undefined
|
|
179
|
+
? `In ${ort} ist keine claude-CLI erreichbar: auf diesem Rechner liegt keine claude im PATH ` +
|
|
180
|
+
'(claude --version scheiterte). MCP-Server und Plugins lassen sich dort erst verwalten, wenn Claude Code hier installiert ist.'
|
|
181
|
+
: `In ${ort} ist keine claude-CLI erreichbar: in der WSL-Distro ${distro} fand hmgr keine ` +
|
|
182
|
+
'(gesucht mit command -v claude in einer Login-Shell, danach unter ~/.local/bin/claude). ' +
|
|
183
|
+
'MCP-Server und Plugins lassen sich dort erst verwalten, wenn Claude Code in der Distro installiert ist.');
|
|
184
|
+
this.ziel = ziel;
|
|
185
|
+
this.distro = distro;
|
|
177
186
|
}
|
|
178
187
|
}
|
|
179
188
|
/**
|