@mindexec/cli 0.2.478 → 0.2.479
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/README.md +323 -323
- package/codex-model-catalog.js +202 -202
- package/codex-runtime.js +1608 -1608
- package/desktop-workspace-state.cjs +120 -120
- package/electron/main.cjs +517 -517
- package/electron/preload.cjs +17 -17
- package/electron/recurring-update-owner-smoke.mjs +55 -55
- package/electron/recurring-update-owner.cjs +97 -97
- package/electron/source-smoke.mjs +108 -108
- package/electron/update-feed-resolver.cjs +79 -79
- package/electron/update-feed.json +10 -10
- package/electron/update-manager-smoke.mjs +146 -146
- package/electron/update-manager.cjs +297 -297
- package/electron/windows-package-smoke.mjs +156 -156
- package/launch-bridge.cjs +236 -236
- package/lzo1x.js +110 -110
- package/mode4-atlas-worker.js +440 -440
- package/mode4-atlas.js +130 -130
- package/package.json +1 -1
- package/port-guard.cjs +285 -285
- package/remote-fast/osx-arm64/MacScreenCaptureKitH264.swift +339 -339
- package/remote-fast/osx-arm64/MacSystemAudioCapture.swift +214 -214
- package/remote-fast/osx-arm64/UnixRawCaptureHelper.mjs +80 -80
- package/remote-fast/osx-arm64/livedesk-raw-capture-resampler.mjs +73 -73
- package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/osx-x64/MacScreenCaptureKitH264.swift +339 -339
- package/remote-fast/osx-x64/MacSystemAudioCapture.swift +214 -214
- package/remote-fast/osx-x64/UnixRawCaptureHelper.mjs +80 -80
- package/remote-fast/osx-x64/livedesk-raw-capture-resampler.mjs +73 -73
- package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
- package/remote-hub.js +3698 -3698
- package/scripts/auth-session-smoke.mjs +262 -262
- package/scripts/board-render-ownership-smoke.mjs +85 -85
- package/scripts/codex-model-catalog-smoke.mjs +150 -150
- package/scripts/codex-runtime-home-smoke.mjs +58 -58
- package/scripts/codex-sdk-image-live-smoke.mjs +43 -43
- package/scripts/codex-sdk-image-runtime-smoke.mjs +122 -122
- package/scripts/codex-sdk-runtime-smoke.mjs +89 -89
- package/scripts/desktop-update-publisher-smoke.mjs +89 -89
- package/scripts/desktop-update-worker-smoke.mjs +72 -72
- package/scripts/desktop-workspace-state-smoke.mjs +81 -81
- package/scripts/drive-installed-update-smoke.mjs +67 -67
- package/scripts/npm-fresh-install-smoke.mjs +216 -216
- package/scripts/publish-mindexec-desktop-updates.mjs +224 -224
- package/scripts/remote-agent-managed-smoke.mjs +318 -318
- package/scripts/remote-agent-package-smoke.mjs +64 -64
- package/scripts/remote-agent-ws-smoke.mjs +269 -269
- package/scripts/remote-fast-live-rate-smoke.mjs +343 -343
- package/scripts/remote-fast-mdm-browser-smoke.mjs +453 -453
- package/scripts/remote-fleet-render-smoke.mjs +1350 -1350
- package/scripts/remote-frame-ws-smoke.mjs +234 -234
- package/scripts/remote-http-smoke.mjs +592 -592
- package/scripts/remote-hub-identity-smoke.mjs +146 -146
- package/scripts/remote-hub-scale-smoke.mjs +124 -124
- package/scripts/remote-hub-smoke.mjs +699 -699
- package/scripts/remote-input-ws-smoke.mjs +263 -263
- package/scripts/remote-registry-follower-smoke.mjs +815 -815
- package/scripts/setup-tree-sitter-grammars.mjs +59 -59
- package/server.js +7510 -7510
- package/start-bridge.bat +32 -32
- package/start-bridge.sh +81 -81
- package/tree-sitter-grammars/README.md +18 -0
- package/tree-sitter-grammars/tree-sitter-c_sharp.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-go.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-java.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-javascript.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-python.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-rust.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-tsx.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-typescript.wasm +0 -0
- package/wwwroot/_content/MindExecution.Plugins.Admin/css/admin-dashboard.css +546 -546
- package/wwwroot/_content/MindExecution.Plugins.Directory/background.png +0 -0
- package/wwwroot/_content/MindExecution.Plugins.YouTube/css/youtube-search.css +251 -251
- package/wwwroot/_content/MindExecution.Shared/css/mind-map-overrides.css +3789 -3789
- package/wwwroot/_content/MindExecution.Shared/fonts/NotoSansKR-Bold.ttf +0 -0
- package/wwwroot/_content/MindExecution.Shared/fonts/NotoSansKR-Regular.ttf +0 -0
- package/wwwroot/_content/MindExecution.Shared/js/agent-visualization.js +27 -27
- package/wwwroot/_content/MindExecution.Shared/js/background-themes.js +1449 -1449
- package/wwwroot/_content/MindExecution.Shared/js/code-master.js +8316 -8316
- package/wwwroot/_content/MindExecution.Shared/js/file-system-helper.js +338 -338
- package/wwwroot/_content/MindExecution.Shared/js/marked.min.js +69 -69
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-animated-image-preview.js +270 -270
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-automation-edge-motion.js +170 -170
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-board-render-ownership.js +81 -81
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +10506 -10506
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js.backup +1059 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-hotpath.js +902 -902
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +24637 -24637
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-dev-guards.js +325 -325
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js +1491 -1491
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js.bak +434 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-glow-shader.js +70 -70
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-image-policy.js +218 -218
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +7078 -7078
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-plan-worker.js +160 -160
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +9958 -9958
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-video-placeholder.js +107 -107
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-logic-workers.js +1004 -1004
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-menu-manager.js +1351 -1351
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-multi-select.js +865 -865
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-node-search-worker.js +554 -554
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +4744 -4744
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-normal-boundary-optimizer.js +624 -624
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-object-manager.js +241 -241
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-object-manager.js.backup +372 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-pipeline.js +2290 -2290
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-remote-codecs.js +202 -202
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-render-plan.js +1971 -1971
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-text-lod-system.js +93 -93
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js +2391 -2391
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js.backup +1258 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-video-playback-controls.js +95 -95
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-visibility-worker.js +895 -895
- package/wwwroot/_content/MindExecution.Shared/js/mindmap-toolbar.js +636 -636
- package/wwwroot/_content/MindExecution.Shared/js/native-drop-handler.js +45 -45
- package/wwwroot/_content/MindExecution.Shared/js/plan-master.js +117 -117
- package/wwwroot/_content/MindExecution.Shared/js/renderers/CSS3DRenderer.js +114 -114
- package/wwwroot/_content/MindExecution.Shared/js/texture-worker-manager.js +10 -10
- package/wwwroot/_content/MindExecution.Shared/js/three.min.js +6 -6
- package/wwwroot/_content/MindExecution.Shared/js/token-manager.js +7 -7
- package/wwwroot/_content/MindExecution.Shared/js/troika-bundle.js +6 -6
- package/wwwroot/_content/MindExecution.Shared/js/troika-bundle.js.map +2 -2
- package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/css/all.min.css +8 -8
- package/wwwroot/_content/MindExecution.Shared/models/vocab.txt +30522 -30522
- package/wwwroot/_framework/{Google.Protobuf.33g2gb5is0.dll → Google.Protobuf.9h59ukbel7.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Core.z100xo4mg0.dll → MindExecution.Core.xxsj2s456b.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.i9cldrx8yc.dll → MindExecution.Kernel.5jofnc0wrd.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.48lzqddb0a.dll → MindExecution.Plugins.Admin.t7m4jnqjg3.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.7m8u1q142p.dll → MindExecution.Plugins.Business.6yjlhhavap.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.cl5wmr53od.dll → MindExecution.Plugins.Concept.iikqq5zxu0.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.slipi39041.dll → MindExecution.Plugins.Directory.c5ribyne4m.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.qfge05l671.dll → MindExecution.Plugins.PlanMaster.365bbuifr9.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.tn7v29ut77.dll → MindExecution.Plugins.YouTube.ax415cilpk.dll} +0 -0
- package/wwwroot/_framework/MindExecution.Shared.21e2nvf86u.dll +0 -0
- package/wwwroot/_framework/{MindExecution.Web.rwawrxxk38.dll → MindExecution.Web.ppgm8rey59.dll} +0 -0
- package/wwwroot/_framework/{System.Collections.23yxgetbju.dll → System.Collections.zyvbaq6mpd.dll} +0 -0
- package/wwwroot/_framework/blazor.boot.json +25 -25
- package/wwwroot/_headers +40 -40
- package/wwwroot/_redirects +1 -1
- package/wwwroot/index.html +772 -772
- package/wwwroot/js/marketing-tool.js +180 -180
- package/wwwroot/manifest.webmanifest +23 -23
- package/wwwroot/service-worker-assets.js +109 -81
- package/wwwroot/service-worker.js +33 -33
- package/wwwroot/_framework/MindExecution.Shared.rjqlf0umjn.dll +0 -0
package/port-guard.cjs
CHANGED
|
@@ -1,285 +1,285 @@
|
|
|
1
|
-
const { execFile } = require('child_process');
|
|
2
|
-
const http = require('http');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const { promisify } = require('util');
|
|
5
|
-
|
|
6
|
-
const execFileAsync = promisify(execFile);
|
|
7
|
-
const DEFAULT_WAIT_MS = 3500;
|
|
8
|
-
const STATUS_PROBE_TIMEOUT_MS = 650;
|
|
9
|
-
|
|
10
|
-
function normalizePort(value, fallback = 5147) {
|
|
11
|
-
const parsed = Number.parseInt(String(value || '').trim(), 10);
|
|
12
|
-
return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535
|
|
13
|
-
? parsed
|
|
14
|
-
: fallback;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function normalizePath(value) {
|
|
18
|
-
return String(value || '').replace(/\\/g, '/').toLowerCase();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function isTruthy(value) {
|
|
22
|
-
return /^(1|true|yes|on)$/i.test(String(value || '').trim());
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function endpointUsesPort(endpoint, port) {
|
|
26
|
-
const text = String(endpoint || '').trim();
|
|
27
|
-
return text.endsWith(`:${port}`)
|
|
28
|
-
|| text.endsWith(`.${port}`)
|
|
29
|
-
|| text.includes(`:${port} `)
|
|
30
|
-
|| text.includes(`]:${port}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function getWindowsListeners(port) {
|
|
34
|
-
const { stdout } = await execFileAsync('netstat.exe', ['-ano', '-p', 'tcp'], {
|
|
35
|
-
windowsHide: true,
|
|
36
|
-
maxBuffer: 1024 * 1024
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const pids = new Set();
|
|
40
|
-
for (const rawLine of String(stdout || '').split(/\r?\n/)) {
|
|
41
|
-
const line = rawLine.trim();
|
|
42
|
-
if (!line || !/\bLISTENING\b/i.test(line)) {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const parts = line.split(/\s+/);
|
|
47
|
-
if (parts.length < 5 || !endpointUsesPort(parts[1], port)) {
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const pid = Number.parseInt(parts[parts.length - 1], 10);
|
|
52
|
-
if (Number.isInteger(pid) && pid > 0 && pid !== process.pid) {
|
|
53
|
-
pids.add(pid);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return [...pids];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function getUnixListeners(port) {
|
|
61
|
-
try {
|
|
62
|
-
const { stdout } = await execFileAsync('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-t'], {
|
|
63
|
-
maxBuffer: 256 * 1024
|
|
64
|
-
});
|
|
65
|
-
return String(stdout || '')
|
|
66
|
-
.split(/\r?\n/)
|
|
67
|
-
.map(line => Number.parseInt(line.trim(), 10))
|
|
68
|
-
.filter(pid => Number.isInteger(pid) && pid > 0 && pid !== process.pid);
|
|
69
|
-
} catch {
|
|
70
|
-
return [];
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function getPortListeners(port) {
|
|
75
|
-
try {
|
|
76
|
-
return process.platform === 'win32'
|
|
77
|
-
? await getWindowsListeners(port)
|
|
78
|
-
: await getUnixListeners(port);
|
|
79
|
-
} catch {
|
|
80
|
-
return [];
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async function getWindowsProcessDetails(pid) {
|
|
85
|
-
const script = [
|
|
86
|
-
'$ErrorActionPreference = "SilentlyContinue";',
|
|
87
|
-
`$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}";`,
|
|
88
|
-
'if ($p) {',
|
|
89
|
-
' [pscustomobject]@{ ProcessId = $p.ProcessId; ExecutablePath = $p.ExecutablePath; CommandLine = $p.CommandLine } | ConvertTo-Json -Compress',
|
|
90
|
-
'}'
|
|
91
|
-
].join(' ');
|
|
92
|
-
const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-Command', script], {
|
|
93
|
-
windowsHide: true,
|
|
94
|
-
maxBuffer: 256 * 1024
|
|
95
|
-
});
|
|
96
|
-
const parsed = JSON.parse(String(stdout || '{}').trim() || '{}');
|
|
97
|
-
return {
|
|
98
|
-
pid,
|
|
99
|
-
executablePath: parsed.ExecutablePath || '',
|
|
100
|
-
commandLine: parsed.CommandLine || ''
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function getUnixProcessDetails(pid) {
|
|
105
|
-
const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-o', 'comm=', '-o', 'args='], {
|
|
106
|
-
maxBuffer: 256 * 1024
|
|
107
|
-
});
|
|
108
|
-
const text = String(stdout || '').trim();
|
|
109
|
-
return {
|
|
110
|
-
pid,
|
|
111
|
-
executablePath: text.split(/\s+/)[0] || '',
|
|
112
|
-
commandLine: text
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function getProcessDetails(pid) {
|
|
117
|
-
try {
|
|
118
|
-
return process.platform === 'win32'
|
|
119
|
-
? await getWindowsProcessDetails(pid)
|
|
120
|
-
: await getUnixProcessDetails(pid);
|
|
121
|
-
} catch {
|
|
122
|
-
return {
|
|
123
|
-
pid,
|
|
124
|
-
executablePath: '',
|
|
125
|
-
commandLine: ''
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function looksLikeMindExecBridge(details, bridgeRoot) {
|
|
131
|
-
const text = normalizePath(`${details.executablePath || ''} ${details.commandLine || ''}`);
|
|
132
|
-
const root = normalizePath(bridgeRoot);
|
|
133
|
-
const packageRoot = normalizePath(path.dirname(bridgeRoot));
|
|
134
|
-
|
|
135
|
-
return (root && text.includes(root))
|
|
136
|
-
|| (packageRoot && text.includes(`${packageRoot}/localbridge/`))
|
|
137
|
-
|| text.includes('localbridge/server.js')
|
|
138
|
-
|| text.includes('localbridge/launch-bridge.cjs')
|
|
139
|
-
|| text.includes('/localbridge/server.js')
|
|
140
|
-
|| text.includes('/localbridge/launch-bridge.cjs')
|
|
141
|
-
|| text.includes('@mindexec/cli')
|
|
142
|
-
|| text.includes('mind-bridge')
|
|
143
|
-
|| /\bmindexec(\.cmd|\.ps1|\.exe)?\b/i.test(text);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async function probeMindExecStatus(port) {
|
|
147
|
-
return await new Promise(resolve => {
|
|
148
|
-
const request = http.request({
|
|
149
|
-
hostname: '127.0.0.1',
|
|
150
|
-
port,
|
|
151
|
-
path: '/api/status',
|
|
152
|
-
method: 'GET',
|
|
153
|
-
timeout: STATUS_PROBE_TIMEOUT_MS,
|
|
154
|
-
headers: {
|
|
155
|
-
Accept: 'application/json'
|
|
156
|
-
}
|
|
157
|
-
}, response => {
|
|
158
|
-
let body = '';
|
|
159
|
-
response.setEncoding('utf8');
|
|
160
|
-
response.on('data', chunk => {
|
|
161
|
-
body += chunk;
|
|
162
|
-
if (body.length > 64 * 1024) {
|
|
163
|
-
request.destroy();
|
|
164
|
-
resolve(false);
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
response.on('end', () => {
|
|
168
|
-
if (response.statusCode !== 200) {
|
|
169
|
-
resolve(false);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
const payload = JSON.parse(body || '{}');
|
|
175
|
-
resolve(
|
|
176
|
-
payload?.app === 'MindExec' &&
|
|
177
|
-
payload?.packageName === '@mindexec/cli' &&
|
|
178
|
-
payload?.status === 'ok'
|
|
179
|
-
);
|
|
180
|
-
} catch {
|
|
181
|
-
resolve(false);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
request.on('timeout', () => {
|
|
187
|
-
request.destroy();
|
|
188
|
-
resolve(false);
|
|
189
|
-
});
|
|
190
|
-
request.on('error', () => resolve(false));
|
|
191
|
-
request.end();
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function waitForPortRelease(port, timeoutMs = DEFAULT_WAIT_MS) {
|
|
196
|
-
const started = Date.now();
|
|
197
|
-
while (Date.now() - started < timeoutMs) {
|
|
198
|
-
const listeners = await getPortListeners(port);
|
|
199
|
-
if (listeners.length === 0) {
|
|
200
|
-
return true;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
await new Promise(resolve => setTimeout(resolve, 120));
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function killPid(pid, signal) {
|
|
210
|
-
try {
|
|
211
|
-
process.kill(pid, signal);
|
|
212
|
-
return true;
|
|
213
|
-
} catch {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async function releaseBridgePort(options = {}) {
|
|
219
|
-
const port = normalizePort(options.port || process.env.BRIDGE_PORT);
|
|
220
|
-
const bridgeRoot = options.bridgeRoot || __dirname;
|
|
221
|
-
const log = typeof options.log === 'function' ? options.log : () => {};
|
|
222
|
-
const force = isTruthy(process.env.BRIDGE_FORCE_KILL_PORT_OWNER)
|
|
223
|
-
|| isTruthy(process.env.BRIDGE_KILL_ANY_PORT_OWNER);
|
|
224
|
-
const disabled = isTruthy(process.env.BRIDGE_SKIP_PORT_REAP);
|
|
225
|
-
|
|
226
|
-
if (disabled) {
|
|
227
|
-
return { port, killed: [], skipped: [], disabled: true };
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const pids = await getPortListeners(port);
|
|
231
|
-
const killed = [];
|
|
232
|
-
const skipped = [];
|
|
233
|
-
|
|
234
|
-
for (const pid of pids) {
|
|
235
|
-
const details = await getProcessDetails(pid);
|
|
236
|
-
const canKill = force ||
|
|
237
|
-
looksLikeMindExecBridge(details, bridgeRoot) ||
|
|
238
|
-
await probeMindExecStatus(port);
|
|
239
|
-
if (!canKill) {
|
|
240
|
-
skipped.push(details);
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (killPid(pid, 'SIGTERM')) {
|
|
245
|
-
killed.push(details);
|
|
246
|
-
log(`stopping existing LocalBridge listener on port ${port} (pid ${pid})`);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (killed.length > 0) {
|
|
251
|
-
const released = await waitForPortRelease(port);
|
|
252
|
-
if (!released) {
|
|
253
|
-
const remaining = await getPortListeners(port);
|
|
254
|
-
for (const details of killed) {
|
|
255
|
-
if (remaining.includes(details.pid)) {
|
|
256
|
-
killPid(details.pid, 'SIGKILL');
|
|
257
|
-
log(`force-stopping LocalBridge listener on port ${port} (pid ${details.pid})`);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
await waitForPortRelease(port, 1200);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (skipped.length > 0) {
|
|
266
|
-
const owners = skipped
|
|
267
|
-
.map(item => `pid ${item.pid}${item.commandLine ? ` (${item.commandLine})` : ''}`)
|
|
268
|
-
.join('; ');
|
|
269
|
-
const error = new Error(
|
|
270
|
-
`Port ${port} is already in use by a non-MindExec process: ${owners}. ` +
|
|
271
|
-
'Close it or set BRIDGE_FORCE_KILL_PORT_OWNER=1 to terminate any owner of this bridge port.'
|
|
272
|
-
);
|
|
273
|
-
error.code = 'BRIDGE_PORT_IN_USE';
|
|
274
|
-
error.port = port;
|
|
275
|
-
error.skipped = skipped;
|
|
276
|
-
throw error;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return { port, killed, skipped, disabled: false };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
module.exports = {
|
|
283
|
-
normalizePort,
|
|
284
|
-
releaseBridgePort
|
|
285
|
-
};
|
|
1
|
+
const { execFile } = require('child_process');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { promisify } = require('util');
|
|
5
|
+
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const DEFAULT_WAIT_MS = 3500;
|
|
8
|
+
const STATUS_PROBE_TIMEOUT_MS = 650;
|
|
9
|
+
|
|
10
|
+
function normalizePort(value, fallback = 5147) {
|
|
11
|
+
const parsed = Number.parseInt(String(value || '').trim(), 10);
|
|
12
|
+
return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535
|
|
13
|
+
? parsed
|
|
14
|
+
: fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizePath(value) {
|
|
18
|
+
return String(value || '').replace(/\\/g, '/').toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isTruthy(value) {
|
|
22
|
+
return /^(1|true|yes|on)$/i.test(String(value || '').trim());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function endpointUsesPort(endpoint, port) {
|
|
26
|
+
const text = String(endpoint || '').trim();
|
|
27
|
+
return text.endsWith(`:${port}`)
|
|
28
|
+
|| text.endsWith(`.${port}`)
|
|
29
|
+
|| text.includes(`:${port} `)
|
|
30
|
+
|| text.includes(`]:${port}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function getWindowsListeners(port) {
|
|
34
|
+
const { stdout } = await execFileAsync('netstat.exe', ['-ano', '-p', 'tcp'], {
|
|
35
|
+
windowsHide: true,
|
|
36
|
+
maxBuffer: 1024 * 1024
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const pids = new Set();
|
|
40
|
+
for (const rawLine of String(stdout || '').split(/\r?\n/)) {
|
|
41
|
+
const line = rawLine.trim();
|
|
42
|
+
if (!line || !/\bLISTENING\b/i.test(line)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const parts = line.split(/\s+/);
|
|
47
|
+
if (parts.length < 5 || !endpointUsesPort(parts[1], port)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const pid = Number.parseInt(parts[parts.length - 1], 10);
|
|
52
|
+
if (Number.isInteger(pid) && pid > 0 && pid !== process.pid) {
|
|
53
|
+
pids.add(pid);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return [...pids];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function getUnixListeners(port) {
|
|
61
|
+
try {
|
|
62
|
+
const { stdout } = await execFileAsync('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-t'], {
|
|
63
|
+
maxBuffer: 256 * 1024
|
|
64
|
+
});
|
|
65
|
+
return String(stdout || '')
|
|
66
|
+
.split(/\r?\n/)
|
|
67
|
+
.map(line => Number.parseInt(line.trim(), 10))
|
|
68
|
+
.filter(pid => Number.isInteger(pid) && pid > 0 && pid !== process.pid);
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function getPortListeners(port) {
|
|
75
|
+
try {
|
|
76
|
+
return process.platform === 'win32'
|
|
77
|
+
? await getWindowsListeners(port)
|
|
78
|
+
: await getUnixListeners(port);
|
|
79
|
+
} catch {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function getWindowsProcessDetails(pid) {
|
|
85
|
+
const script = [
|
|
86
|
+
'$ErrorActionPreference = "SilentlyContinue";',
|
|
87
|
+
`$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}";`,
|
|
88
|
+
'if ($p) {',
|
|
89
|
+
' [pscustomobject]@{ ProcessId = $p.ProcessId; ExecutablePath = $p.ExecutablePath; CommandLine = $p.CommandLine } | ConvertTo-Json -Compress',
|
|
90
|
+
'}'
|
|
91
|
+
].join(' ');
|
|
92
|
+
const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-Command', script], {
|
|
93
|
+
windowsHide: true,
|
|
94
|
+
maxBuffer: 256 * 1024
|
|
95
|
+
});
|
|
96
|
+
const parsed = JSON.parse(String(stdout || '{}').trim() || '{}');
|
|
97
|
+
return {
|
|
98
|
+
pid,
|
|
99
|
+
executablePath: parsed.ExecutablePath || '',
|
|
100
|
+
commandLine: parsed.CommandLine || ''
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function getUnixProcessDetails(pid) {
|
|
105
|
+
const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-o', 'comm=', '-o', 'args='], {
|
|
106
|
+
maxBuffer: 256 * 1024
|
|
107
|
+
});
|
|
108
|
+
const text = String(stdout || '').trim();
|
|
109
|
+
return {
|
|
110
|
+
pid,
|
|
111
|
+
executablePath: text.split(/\s+/)[0] || '',
|
|
112
|
+
commandLine: text
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function getProcessDetails(pid) {
|
|
117
|
+
try {
|
|
118
|
+
return process.platform === 'win32'
|
|
119
|
+
? await getWindowsProcessDetails(pid)
|
|
120
|
+
: await getUnixProcessDetails(pid);
|
|
121
|
+
} catch {
|
|
122
|
+
return {
|
|
123
|
+
pid,
|
|
124
|
+
executablePath: '',
|
|
125
|
+
commandLine: ''
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function looksLikeMindExecBridge(details, bridgeRoot) {
|
|
131
|
+
const text = normalizePath(`${details.executablePath || ''} ${details.commandLine || ''}`);
|
|
132
|
+
const root = normalizePath(bridgeRoot);
|
|
133
|
+
const packageRoot = normalizePath(path.dirname(bridgeRoot));
|
|
134
|
+
|
|
135
|
+
return (root && text.includes(root))
|
|
136
|
+
|| (packageRoot && text.includes(`${packageRoot}/localbridge/`))
|
|
137
|
+
|| text.includes('localbridge/server.js')
|
|
138
|
+
|| text.includes('localbridge/launch-bridge.cjs')
|
|
139
|
+
|| text.includes('/localbridge/server.js')
|
|
140
|
+
|| text.includes('/localbridge/launch-bridge.cjs')
|
|
141
|
+
|| text.includes('@mindexec/cli')
|
|
142
|
+
|| text.includes('mind-bridge')
|
|
143
|
+
|| /\bmindexec(\.cmd|\.ps1|\.exe)?\b/i.test(text);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function probeMindExecStatus(port) {
|
|
147
|
+
return await new Promise(resolve => {
|
|
148
|
+
const request = http.request({
|
|
149
|
+
hostname: '127.0.0.1',
|
|
150
|
+
port,
|
|
151
|
+
path: '/api/status',
|
|
152
|
+
method: 'GET',
|
|
153
|
+
timeout: STATUS_PROBE_TIMEOUT_MS,
|
|
154
|
+
headers: {
|
|
155
|
+
Accept: 'application/json'
|
|
156
|
+
}
|
|
157
|
+
}, response => {
|
|
158
|
+
let body = '';
|
|
159
|
+
response.setEncoding('utf8');
|
|
160
|
+
response.on('data', chunk => {
|
|
161
|
+
body += chunk;
|
|
162
|
+
if (body.length > 64 * 1024) {
|
|
163
|
+
request.destroy();
|
|
164
|
+
resolve(false);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
response.on('end', () => {
|
|
168
|
+
if (response.statusCode !== 200) {
|
|
169
|
+
resolve(false);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const payload = JSON.parse(body || '{}');
|
|
175
|
+
resolve(
|
|
176
|
+
payload?.app === 'MindExec' &&
|
|
177
|
+
payload?.packageName === '@mindexec/cli' &&
|
|
178
|
+
payload?.status === 'ok'
|
|
179
|
+
);
|
|
180
|
+
} catch {
|
|
181
|
+
resolve(false);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
request.on('timeout', () => {
|
|
187
|
+
request.destroy();
|
|
188
|
+
resolve(false);
|
|
189
|
+
});
|
|
190
|
+
request.on('error', () => resolve(false));
|
|
191
|
+
request.end();
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function waitForPortRelease(port, timeoutMs = DEFAULT_WAIT_MS) {
|
|
196
|
+
const started = Date.now();
|
|
197
|
+
while (Date.now() - started < timeoutMs) {
|
|
198
|
+
const listeners = await getPortListeners(port);
|
|
199
|
+
if (listeners.length === 0) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
await new Promise(resolve => setTimeout(resolve, 120));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function killPid(pid, signal) {
|
|
210
|
+
try {
|
|
211
|
+
process.kill(pid, signal);
|
|
212
|
+
return true;
|
|
213
|
+
} catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function releaseBridgePort(options = {}) {
|
|
219
|
+
const port = normalizePort(options.port || process.env.BRIDGE_PORT);
|
|
220
|
+
const bridgeRoot = options.bridgeRoot || __dirname;
|
|
221
|
+
const log = typeof options.log === 'function' ? options.log : () => {};
|
|
222
|
+
const force = isTruthy(process.env.BRIDGE_FORCE_KILL_PORT_OWNER)
|
|
223
|
+
|| isTruthy(process.env.BRIDGE_KILL_ANY_PORT_OWNER);
|
|
224
|
+
const disabled = isTruthy(process.env.BRIDGE_SKIP_PORT_REAP);
|
|
225
|
+
|
|
226
|
+
if (disabled) {
|
|
227
|
+
return { port, killed: [], skipped: [], disabled: true };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const pids = await getPortListeners(port);
|
|
231
|
+
const killed = [];
|
|
232
|
+
const skipped = [];
|
|
233
|
+
|
|
234
|
+
for (const pid of pids) {
|
|
235
|
+
const details = await getProcessDetails(pid);
|
|
236
|
+
const canKill = force ||
|
|
237
|
+
looksLikeMindExecBridge(details, bridgeRoot) ||
|
|
238
|
+
await probeMindExecStatus(port);
|
|
239
|
+
if (!canKill) {
|
|
240
|
+
skipped.push(details);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (killPid(pid, 'SIGTERM')) {
|
|
245
|
+
killed.push(details);
|
|
246
|
+
log(`stopping existing LocalBridge listener on port ${port} (pid ${pid})`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (killed.length > 0) {
|
|
251
|
+
const released = await waitForPortRelease(port);
|
|
252
|
+
if (!released) {
|
|
253
|
+
const remaining = await getPortListeners(port);
|
|
254
|
+
for (const details of killed) {
|
|
255
|
+
if (remaining.includes(details.pid)) {
|
|
256
|
+
killPid(details.pid, 'SIGKILL');
|
|
257
|
+
log(`force-stopping LocalBridge listener on port ${port} (pid ${details.pid})`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
await waitForPortRelease(port, 1200);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (skipped.length > 0) {
|
|
266
|
+
const owners = skipped
|
|
267
|
+
.map(item => `pid ${item.pid}${item.commandLine ? ` (${item.commandLine})` : ''}`)
|
|
268
|
+
.join('; ');
|
|
269
|
+
const error = new Error(
|
|
270
|
+
`Port ${port} is already in use by a non-MindExec process: ${owners}. ` +
|
|
271
|
+
'Close it or set BRIDGE_FORCE_KILL_PORT_OWNER=1 to terminate any owner of this bridge port.'
|
|
272
|
+
);
|
|
273
|
+
error.code = 'BRIDGE_PORT_IN_USE';
|
|
274
|
+
error.port = port;
|
|
275
|
+
error.skipped = skipped;
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return { port, killed, skipped, disabled: false };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = {
|
|
283
|
+
normalizePort,
|
|
284
|
+
releaseBridgePort
|
|
285
|
+
};
|