amicus 4.5.0 → 4.5.1

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amicus",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "description": "Multi-model LLM Council + parallel AI window for Claude Code. Run structured council reviews across Gemini, GPT, DeepSeek and more — or fork a conversation to any model and fold the results back.",
5
5
  "author": {
6
6
  "name": "Christian Wagner"
package/CHANGELOG.md CHANGED
@@ -3,6 +3,30 @@
3
3
  All notable changes to Amicus are documented here. Format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow semver.
5
5
 
6
+ ## [4.5.1] - 2026-07-30
7
+
8
+ ### Added
9
+
10
+ - **`electron-mcp` doctor check — doctor now validates Electron in the install the MCP actually
11
+ runs from (#76).** `amicus doctor` used to probe Electron only in the copy doctor itself runs
12
+ from (usually the global install), while `npx -y amicus@latest mcp` serves councils from an
13
+ npx-cache copy — so doctor could print `Electron: ok` while every `ui: true` run failed with
14
+ `electron-absent` (the electron-flavored recurrence of the engine's green-while-broken defect).
15
+ The new check enumerates running/global/npx-cache installs (reusing the engine scanner) and
16
+ probes Electron in each through a dual-root resolver — npm nests `electron` under
17
+ `amicus/node_modules` in a global install but hoists it to a sibling in the npx cache (the #69
18
+ layout lesson, now applied to Electron). With `--fix`, binary-missing npx copies are healed in
19
+ place via `repairElectron` under the #56 timeout guard; never-installed copies are reported,
20
+ not repaired.
21
+
22
+ ### Changed
23
+
24
+ - **`workspaceOpenReason` distinguishes a broken Electron from a missing one (#76).**
25
+ `electron-absent` now means the electron package was never installed; the new
26
+ `electron-broken: binary missing under <dir> — run `amicus doctor --fix`` covers the
27
+ package-present-but-binary-missing state (interrupted postinstall, AV quarantine) that the old
28
+ single reason conflated with it — naming the exact dir and the one-command fix.
29
+
6
30
  ## [4.5.0] - 2026-07-28
7
31
 
8
32
  "Save and share your councils" — complex run configurations become one command, repeatable and
package/README.md CHANGED
@@ -364,7 +364,7 @@ $ amicus status demo123 --json
364
364
  "taskId": "demo123",
365
365
  "status": "complete",
366
366
  "elapsed": "5m 0s",
367
- "version": "4.5.0",
367
+ "version": "4.5.1",
368
368
  "model": "google/gemini-2.5-flash",
369
369
  "phase": "terminal"
370
370
  }
package/docs/ROADMAP.md CHANGED
@@ -7,14 +7,12 @@ major jump, gated on funding/cofounder. The observability arc is split so the **
7
7
  first (v4.3)** and the **Electron "Council Workspace" (v4.4)** rides on top of it. `--dry-run` cost
8
8
  preview dropped to the backlog.
9
9
 
10
- Amicus is at **v4.4.1** (2026-07-27), the fast-follow patch on v4.4.0 (tagged 2026-07-26). Each
11
- 4.x rev below leads with the benefit, not the plumbing.
10
+ Amicus is at **v4.5.0** (tagged 2026-07-28). Each 4.x rev below leads with the benefit, not the
11
+ plumbing.
12
12
 
13
- **Status:** v4.0 through **v4.4.1** have **shipped** — everything down to the v4.5 heading is a
14
- record of what landed, not a plan. **v4.5 (packs + templates + auto-open) is the next rev**, with
15
- **v4.6 (composition + tagging + GUI ergonomics)** behind it — the former combined v4.5 scope,
16
- split 2026-07-27 (design: `docs/superpowers/specs/2026-07-27-v4.5-save-and-share-design.md`). v5.0
17
- remains forward-looking.
13
+ **Status:** v4.0 through **v4.5.0** have **shipped** — everything down to the v4.6 heading is a
14
+ record of what landed, not a plan. **v4.6 (composition + tagging + GUI ergonomics) is the next
15
+ rev.** v5.0 remains forward-looking.
18
16
 
19
17
  > 📁 **Reading this from an npm install?** Some references below point at working documents that
20
18
  > live in the git repository and are deliberately **not** in the published package — anything under
@@ -87,7 +85,7 @@ and a review that honestly finds nothing stops being an error.
87
85
  > repo's working notes (`.superpowers/sdd/v441/backlog-and-proposal.md`, local-only) and in the
88
86
  > repo's root `BACKLOG.md` — neither ships in the npm package; see the note at the top.
89
87
 
90
- ## v4.5 — "Save and share your councils" *(scope split 2026-07-27 — composition moved to v4.6)*
88
+ ## v4.5 — "Save and share your councils" *(scope split 2026-07-27 — composition moved to v4.6)* — ✅ SHIPPED v4.5.0, 2026-07-28
91
89
  **Benefit:** complex councils become one-command, repeatable, and shareable — and the flagship GUI
92
90
  stops hiding. Design: `docs/superpowers/specs/2026-07-27-v4.5-save-and-share-design.md`.
93
91
  - **★ Auto-open the Council Workspace on a council run (Christian, 2026-07-26)** — when a council is
package/docs/council.md CHANGED
@@ -413,8 +413,11 @@ bottom, first match wins:
413
413
  6. Otherwise → opens.
414
414
 
415
415
  **Response fields.** `amicus_council_run`'s result always carries `workspaceOpened: boolean`, and,
416
- **only when it did not open**, `workspaceOpenReason` — one of `param-suppressed`, `electron-absent`,
417
- `no-display`, `config-disabled`, `client-not-code-local`, or a `spawn-failed:`/`auto-open-failed:`
416
+ **only when it did not open**, `workspaceOpenReason` — one of `param-suppressed`, `electron-absent`
417
+ (the electron package was never installed for this copy), an `electron-broken:` detail (the package
418
+ is present but its binary never arrived — interrupted download or AV quarantine; the reason names
419
+ the electron dir and points at `amicus doctor --fix`), `no-display`, `config-disabled`,
420
+ `client-not-code-local`, or a `spawn-failed:`/`auto-open-failed:`
418
421
  detail if the decision said to open but the launch itself failed. The launch is fire-and-forget: it
419
422
  never blocks the tool's response, and a launch failure never fails the council run itself — the run
420
423
  proceeds exactly as it would with no Workspace at all, and `amicus watch <runId> --ui` still works
package/docs/usage.md CHANGED
@@ -443,7 +443,7 @@ $ amicus status demo123 --json
443
443
  "taskId": "demo123",
444
444
  "status": "complete",
445
445
  "elapsed": "5m 0s",
446
- "version": "4.5.0",
446
+ "version": "4.5.1",
447
447
  "model": "google/gemini-2.5-flash",
448
448
  "phase": "terminal"
449
449
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amicus",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "mcpName": "io.github.BourbonDog/amicus",
5
5
  "description": "Multi-model LLM Council + parallel AI window for Claude Code. Run structured council reviews across Gemini, GPT, DeepSeek and more — or fork a conversation to any model and fold the results back.",
6
6
  "keywords": [
@@ -7,6 +7,8 @@ const mcpChecks = require('./utils/doctor-mcp-checks');
7
7
  // engine-mcp check body — verifies the engine in the npx-cache copies the MCP
8
8
  // actually launches (bug report #1). Split out to keep this file under the gate.
9
9
  const engineCheck = require('./utils/doctor-engine-check');
10
+ // electron-mcp check body (#76) — same blind spot, electron-flavored.
11
+ const electronMcpCheck = require('./utils/doctor-electron-mcp-check');
10
12
  // local-providers check body (v4.2 §4.7 C8) — split out to keep this file
11
13
  // under the gate (mirrors the engineCheck/mcpChecks split above).
12
14
  const localProvidersCheck = require('./utils/doctor-local-providers-check');
@@ -47,6 +49,8 @@ function realDeps() {
47
49
  scanEngineInstalls: () => require('./utils/engine-install-scan').scanEngineInstalls(),
48
50
  // report #2: copy-from-sibling self-heal for `doctor --fix`.
49
51
  repairEngine: (o) => require('./utils/engine-repair').repairEngine(o),
52
+ // electron-mcp check (#76): same enumeration, electron probed per copy.
53
+ scanElectronInstalls: () => electronMcpCheck.scanElectronInstalls(),
50
54
  getElectronPath: () => require('./sidecar/interactive-process').getElectronPath(),
51
55
  // #56: self-heal primitive for `doctor --fix`. Pure probe (getElectronPath)
52
56
  // stays separate; repair only runs when fix is requested.
@@ -161,6 +165,12 @@ async function runDoctorChecks(depsOverride = {}) {
161
165
  // install) can't hide a broken copy the MCP would spawn (bug report #1/#4).
162
166
  checks.push(await guardAsync('engine-mcp', 'OpenCode engine (MCP launch path)', () => engineCheck.evaluateEngineMcp(d)));
163
167
 
168
+ // #76: same green-while-broken blind spot for Electron — probe the npx-cache
169
+ // copies `ui: true` actually depends on. fixTimeoutMs forwards the #56
170
+ // never-hang guard to the per-copy repairElectron calls.
171
+ checks.push(await guardAsync('electron-mcp', 'Electron (MCP launch path)',
172
+ () => electronMcpCheck.evaluateElectronMcp({ ...d, fixTimeoutMs: FIX_TIMEOUT_MS })));
173
+
164
174
  checks.push(await guardAsync('electron', 'Electron (interactive GUI)', async () => {
165
175
  if (d.getElectronPath()) {
166
176
  return { id: 'electron', name: 'Electron (interactive GUI)', status: 'ok', message: 'installed', hint: null };
@@ -241,11 +241,13 @@ async function handleCouncilRunTool(input, project, helpers) {
241
241
  let workspaceOpened = false;
242
242
  let workspaceOpenReason = null;
243
243
  try {
244
- const { isElectronUsable } = require('./sidecar/electron-install');
244
+ const { probeElectronState } = require('./sidecar/electron-state');
245
245
  const { getWorkspaceAutoOpen } = require('./utils/config');
246
+ const es = probeElectronState(); // 3-state (#76): splits absent vs broken
246
247
  const decision = ao.decide({
247
248
  client: helpers.clientName,
248
- electronUsable: isElectronUsable(),
249
+ electronState: es.state,
250
+ electronDir: es.electronDir,
249
251
  platform: process.platform,
250
252
  env: process.env,
251
253
  autoOpenConfig: getWorkspaceAutoOpen(),
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Electron install-state probes (#76).
3
+ *
4
+ * Distinguishes the two states isElectronUsable() collapses into one boolean:
5
+ * 'package-missing' — the electron optionalDependency was never installed
6
+ * (possibly deliberate: headless-only install)
7
+ * 'binary-missing' — package present but dist/<exe> absent (interrupted
8
+ * postinstall, AV quarantine) — repairElectron territory
9
+ * 'ok' — the resolved exe exists on disk
10
+ *
11
+ * Also owns electronDirFor(): the per-install dual-root layout probe. npm
12
+ * NESTS electron under amicus/node_modules in a global install but HOISTS it
13
+ * to a sibling in the npx cache — the same layout asymmetry that broke the
14
+ * engine probe (#69). In-process require.resolve handles hoisting via walk-up,
15
+ * which is why the running copy probes itself fine; only CROSS-install probing
16
+ * (doctor) needs this explicit dual-root check.
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ const path = require('path');
22
+ const fsDefault = require('fs');
23
+ const { isElectronUsable, defaultElectronDir } = require('./electron-install');
24
+
25
+ /**
26
+ * Locate the electron package dir serving a given amicus install. Checks the
27
+ * nested root first (require.resolve walk-up order), then the hoisted sibling.
28
+ * @param {string} pkgDir amicus package root
29
+ * @param {{fs?:object}} [deps]
30
+ * @returns {string|null} electron package dir, or null when not installed
31
+ */
32
+ function electronDirFor(pkgDir, { fs = fsDefault } = {}) {
33
+ const candidates = [
34
+ path.join(pkgDir, 'node_modules', 'electron'),
35
+ path.join(path.dirname(pkgDir), 'electron'),
36
+ ];
37
+ for (const dir of candidates) {
38
+ try { if (fs.existsSync(dir)) { return dir; } } catch { /* unreadable root */ }
39
+ }
40
+ return null;
41
+ }
42
+
43
+ /**
44
+ * Probe one electron package dir into the 3-state verdict.
45
+ * `electronDir: null` (electronDirFor found nothing) short-circuits to
46
+ * package-missing; an explicit dir without package.json reports the same.
47
+ * @param {{electronDir?:string|null, fs?:object, platform?:string, env?:object}} [opts]
48
+ * @returns {{state:'ok'|'package-missing'|'binary-missing', electronDir:string|null}}
49
+ */
50
+ function probeElectronState({
51
+ electronDir = defaultElectronDir(), fs = fsDefault, platform = process.platform, env = process.env,
52
+ } = {}) {
53
+ if (!electronDir) { return { state: 'package-missing', electronDir: null }; }
54
+ let hasPkg = false;
55
+ try { hasPkg = fs.existsSync(path.join(electronDir, 'package.json')); } catch { /* unreadable */ }
56
+ if (!hasPkg) { return { state: 'package-missing', electronDir }; }
57
+ const usable = isElectronUsable({ electronDir, fs, platform, env });
58
+ return { state: usable ? 'ok' : 'binary-missing', electronDir };
59
+ }
60
+
61
+ module.exports = { electronDirFor, probeElectronState };
@@ -7,7 +7,10 @@
7
7
  * Decision order (spec §6 guard 4):
8
8
  * 1. uiParam === false → 'param-suppressed' (explicit user request beats everything, checked first)
9
9
  * 2. Hard guards (always checked, beat even explicit true):
10
- * - !electronUsable → 'electron-absent'
10
+ * - electron package-missing → 'electron-absent'
11
+ * - electron binary-missing → 'electron-broken: …' (#76: package present but
12
+ * the exe never arrived — a repairable state the old boolean conflated
13
+ * with never-installed; the reason names the dir and the fix)
11
14
  * - platform === 'linux' && !env.DISPLAY → 'no-display'
12
15
  * 3. uiParam === true → 'ok' (explicit request overrides config and client gate, never hard guards)
13
16
  * 4. autoOpenConfig === false → 'config-disabled'
@@ -16,7 +19,9 @@
16
19
  *
17
20
  * @param {object} options
18
21
  * @param {string} options.client - The client type (e.g., 'code-local', 'cowork', 'code-web')
19
- * @param {boolean} options.electronUsable - Whether Electron is available
22
+ * @param {boolean} [options.electronUsable] - Legacy boolean probe (used only when electronState is absent)
23
+ * @param {'ok'|'package-missing'|'binary-missing'} [options.electronState] - 3-state probe (#76); takes precedence
24
+ * @param {string|null} [options.electronDir] - Resolved electron dir, named in the electron-broken reason
20
25
  * @param {string} options.platform - The platform (e.g., 'win32', 'darwin', 'linux')
21
26
  * @param {object} options.env - Environment variables object
22
27
  * @param {boolean} options.autoOpenConfig - The config.workspace.autoOpen setting
@@ -26,6 +31,8 @@
26
31
  function shouldAutoOpenWorkspace({
27
32
  client,
28
33
  electronUsable,
34
+ electronState,
35
+ electronDir,
29
36
  platform,
30
37
  env,
31
38
  autoOpenConfig,
@@ -36,10 +43,17 @@ function shouldAutoOpenWorkspace({
36
43
  return { open: false, reason: 'param-suppressed' };
37
44
  }
38
45
 
39
- // Step 2: Hard guards (always checked, beat even explicit uiParam === true)
40
- if (!electronUsable) {
46
+ // Step 2: Hard guards (always checked, beat even explicit uiParam === true).
47
+ // electronState (3-state, #76) wins over the legacy electronUsable boolean,
48
+ // whose false collapses to package-missing (the old 'electron-absent').
49
+ const state = electronState || (electronUsable ? 'ok' : 'package-missing');
50
+ if (state === 'package-missing') {
41
51
  return { open: false, reason: 'electron-absent' };
42
52
  }
53
+ if (state === 'binary-missing') {
54
+ const where = electronDir ? ` under ${electronDir}` : '';
55
+ return { open: false, reason: `electron-broken: binary missing${where} — run \`amicus doctor --fix\`` };
56
+ }
43
57
 
44
58
  if (platform === 'linux' && !env.DISPLAY) {
45
59
  return { open: false, reason: 'no-display' };
@@ -0,0 +1,150 @@
1
+ /**
2
+ * @module utils/doctor-electron-mcp-check
3
+ * The `electron-mcp` doctor check ("Electron (MCP launch path)"), split out of
4
+ * src/cli-handlers-doctor.js to keep that file under the 300-line gate
5
+ * (mirrors doctor-engine-check.js, which did the same for the engine — #76 is
6
+ * the electron-flavored recurrence of that check's bug report #1).
7
+ *
8
+ * The existing `electron` check verifies Electron in the RUNNING install. This
9
+ * one verifies the copies the MCP actually launches from — the npx-cache
10
+ * installs `npx -y amicus@latest mcp` resolves to — so a green doctor can no
11
+ * longer hide an npx copy whose `ui: true` will fail with electron-absent.
12
+ *
13
+ * Severity is WARN at worst (never error): a broken Electron only costs the
14
+ * GUI; headless councils still work — same tier the running-copy check uses.
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const HINTS = require('./remediation-hints');
20
+
21
+ const plural = (n, one, many) => (n === 1 ? one : many);
22
+
23
+ /** One-line detail for a broken copy, distinguishing the two states (#76). */
24
+ const describeBroken = (i) => (i.state === 'binary-missing'
25
+ ? `${i.pkgDir} (binary missing; electron dir: ${i.electronDir})`
26
+ : `${i.pkgDir} (not installed)`);
27
+
28
+ /**
29
+ * Enumerate the amicus installs that could serve the MCP and probe Electron in
30
+ * each via the dual-root resolver (#69 lesson: npx HOISTS electron to a
31
+ * sibling; a global install nests it).
32
+ * @param {object} [deps] engine-install-scan seams plus {readAmicusMcpConfig}
33
+ * @returns {{installs:Array<{kind,pkgDir,electronDir,state}>, mcpLaunch:string}}
34
+ */
35
+ function scanElectronInstalls(deps = {}) {
36
+ const fs = deps.fs || require('fs');
37
+ const platform = deps.platform || process.platform;
38
+ const readAmicusMcpConfig = deps.readAmicusMcpConfig
39
+ || (() => require('./mcp-discovery').readAmicusMcpConfig());
40
+ const { listAmicusInstalls, classifyLaunch } = require('./engine-install-scan');
41
+ const { electronDirFor, probeElectronState } = require('../sidecar/electron-state');
42
+
43
+ const installs = listAmicusInstalls(deps).map((i) => {
44
+ const probe = probeElectronState({ electronDir: electronDirFor(i.pkgDir, { fs }), fs, platform });
45
+ return { ...i, electronDir: probe.electronDir, state: probe.state };
46
+ });
47
+ let config = null;
48
+ try { config = readAmicusMcpConfig(); } catch { /* unreadable config */ }
49
+ return { installs, mcpLaunch: classifyLaunch(config) };
50
+ }
51
+
52
+ /**
53
+ * @param {{scanElectronInstalls: () => {installs:Array, mcpLaunch:string}}} d
54
+ * @returns {{id,name,status,message,hint}}
55
+ */
56
+ function evaluateElectronInstalls(d) {
57
+ const id = 'electron-mcp';
58
+ const name = 'Electron (MCP launch path)';
59
+ const { installs, mcpLaunch } = d.scanElectronInstalls();
60
+
61
+ if (mcpLaunch === 'none') {
62
+ return { id, name, status: 'ok', message: 'no amicus MCP registered — not checked', hint: null };
63
+ }
64
+ if (mcpLaunch === 'path') {
65
+ return {
66
+ id, name, status: 'ok',
67
+ message: 'MCP launches from a fixed path — covered by the Electron (interactive GUI) check', hint: null,
68
+ };
69
+ }
70
+
71
+ // 'npx' (and the 'unknown' fallback): verify the npx-cache copies, the ones
72
+ // whose optional-dependency electron install can silently half-complete.
73
+ const npxCopies = installs.filter((i) => i.kind === 'npx');
74
+ if (npxCopies.length === 0) {
75
+ return {
76
+ id, name, status: 'warn',
77
+ message: 'MCP launches via npx; no cached copy to inspect yet — run one council, then re-run doctor',
78
+ hint: null,
79
+ };
80
+ }
81
+
82
+ const broken = npxCopies.filter((i) => i.state !== 'ok');
83
+ if (broken.length === 0) {
84
+ return {
85
+ id, name, status: 'ok',
86
+ message: `electron present in ${npxCopies.length} npx-cache ${plural(npxCopies.length, 'copy', 'copies')}`,
87
+ hint: null,
88
+ };
89
+ }
90
+
91
+ // `doctor --fix` can heal binary-missing (repairElectron); a never-installed
92
+ // package it cannot — keep the hint honest about which state is fixable.
93
+ const anyRepairable = broken.some((i) => i.state === 'binary-missing');
94
+ if (npxCopies.length === 1) {
95
+ const [only] = broken;
96
+ const message = only.state === 'binary-missing'
97
+ ? `electron binary missing in the npx-cache copy the MCP launches: ${only.pkgDir} (electron dir: ${only.electronDir})`
98
+ : `electron not installed in the npx-cache copy the MCP launches: ${only.pkgDir} — GUI auto-open unavailable; headless still works`;
99
+ return { id, name, status: 'warn', message, hint: anyRepairable ? HINTS.doctorFix : null };
100
+ }
101
+ const detail = broken.map(describeBroken).join('; ');
102
+ return {
103
+ id, name, status: 'warn',
104
+ message: `electron unavailable in ${broken.length}/${npxCopies.length} npx-cache copies: ${detail}`,
105
+ hint: anyRepairable ? HINTS.doctorFix : null,
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Fix-aware wrapper. When d.fix and the scan shows binary-missing npx copies,
111
+ * heal each in place via d.repairElectron({electronDir}) — the package dir is
112
+ * already on disk, so repairElectron can read its version and provision the
113
+ * exe — then re-report from a fresh scan. package-missing copies are never
114
+ * repaired (no package to repair into). Mirrors evaluateEngineMcp.
115
+ * @param {object} d doctor deps (scanElectronInstalls, fix?, repairElectron?, fixTimeoutMs?)
116
+ * @returns {Promise<{id,name,status,message,hint}>}
117
+ */
118
+ async function evaluateElectronMcp(d) {
119
+ const verdict = evaluateElectronInstalls(d);
120
+ if (!d.fix || verdict.status === 'ok') { return verdict; }
121
+
122
+ const { installs } = d.scanElectronInstalls();
123
+ const repairable = installs.filter((i) => i.kind === 'npx' && i.state === 'binary-missing');
124
+ if (repairable.length === 0) { return verdict; }
125
+
126
+ const results = [];
127
+ for (const b of repairable) {
128
+ let r;
129
+ try {
130
+ r = await d.repairElectron({
131
+ electronDir: b.electronDir,
132
+ ...(d.fixTimeoutMs ? { timeoutMs: d.fixTimeoutMs } : {}),
133
+ });
134
+ } catch (e) { r = { repaired: false, reason: e && e.message }; }
135
+ results.push({ electronDir: b.electronDir, ...r });
136
+ }
137
+
138
+ const after = evaluateElectronInstalls(d); // fresh scan reflects the repairs
139
+ if (after.status === 'ok') {
140
+ const n = results.length;
141
+ return { ...after, message: `${after.message} (self-healed ${n} npx-cache ${plural(n, 'copy', 'copies')})` };
142
+ }
143
+ const failed = results.filter((r) => !r.repaired)
144
+ .map((r) => `${r.electronDir}${r.reason ? ` — ${r.reason}` : ''}`).join('; ');
145
+ return failed
146
+ ? { ...after, message: `${after.message}; self-heal incomplete: ${failed}` }
147
+ : after;
148
+ }
149
+
150
+ module.exports = { scanElectronInstalls, evaluateElectronInstalls, evaluateElectronMcp };