@mindexec/cli 0.2.93 → 0.2.94

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.93",
3
+ "version": "0.2.94",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
package/remote-hub.js CHANGED
@@ -77,10 +77,72 @@ function isPrivateIPv4(value) {
77
77
  || (parts[0] === 192 && parts[1] === 168);
78
78
  }
79
79
 
80
- function getReachableIPv4Candidates() {
80
+ function isPreferredPhysicalInterfaceName(value) {
81
+ const name = String(value || '').toLowerCase();
82
+ return /(^|[\s_\-()])((wi[\s_\-]?fi)|wifi|wireless|wlan|ethernet|lan|이더넷|en\d+|eth\d+)(?=$|[\s_\-()])/i.test(name);
83
+ }
84
+
85
+ function isVirtualInterfaceName(value) {
86
+ const name = String(value || '').toLowerCase();
87
+ return /virtual|vethernet|hyper-v|hyperv|vmware|virtualbox|vbox|docker|wsl|container|bluetooth|loopback|npcap|isatap|teredo/i.test(name);
88
+ }
89
+
90
+ function getIPv4LastOctet(value) {
91
+ const parts = String(value || '').split('.').map(part => Number(part));
92
+ if (parts.length !== 4 || parts.some(part => !Number.isInteger(part))) {
93
+ return -1;
94
+ }
95
+
96
+ return parts[3];
97
+ }
98
+
99
+ function scoreReachableIPv4Candidate(candidate) {
100
+ const address = String(candidate?.address || '');
101
+ const name = String(candidate?.name || '');
102
+ let score = 0;
103
+
104
+ if (isPrivateIPv4(address)) {
105
+ score += 1000;
106
+ }
107
+
108
+ if (/^192\.168\./.test(address)) {
109
+ score += 90;
110
+ } else if (/^10\./.test(address)) {
111
+ score += 80;
112
+ } else if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(address)) {
113
+ score += 70;
114
+ }
115
+
116
+ if (isPreferredPhysicalInterfaceName(name)) {
117
+ score += 500;
118
+ }
119
+
120
+ if (isVirtualInterfaceName(name)) {
121
+ score -= 1200;
122
+ }
123
+
124
+ if (/^169\.254\./.test(address)) {
125
+ score -= 2000;
126
+ }
127
+
128
+ const lastOctet = getIPv4LastOctet(address);
129
+ if (lastOctet === 1) {
130
+ score -= 60;
131
+ } else if (lastOctet === 0 || lastOctet === 255) {
132
+ score -= 100;
133
+ }
134
+
135
+ if (candidate?.mac && candidate.mac === '00:00:00:00:00:00') {
136
+ score -= 50;
137
+ }
138
+
139
+ return score;
140
+ }
141
+
142
+ function getReachableIPv4CandidateEntries() {
81
143
  const candidates = [];
82
144
  const interfaces = os.networkInterfaces();
83
- for (const entries of Object.values(interfaces)) {
145
+ for (const [name, entries] of Object.entries(interfaces)) {
84
146
  for (const entry of entries || []) {
85
147
  if (entry?.family !== 'IPv4' || entry.internal) {
86
148
  continue;
@@ -91,13 +153,28 @@ function getReachableIPv4Candidates() {
91
153
  continue;
92
154
  }
93
155
 
94
- candidates.push(address);
156
+ candidates.push({
157
+ address,
158
+ name: safeString(name, 128),
159
+ mac: safeString(entry.mac, 32),
160
+ score: 0,
161
+ virtual: isVirtualInterfaceName(name),
162
+ preferred: isPreferredPhysicalInterfaceName(name)
163
+ });
95
164
  }
96
165
  }
97
166
 
98
167
  return candidates
99
- .sort((left, right) => Number(isPrivateIPv4(right)) - Number(isPrivateIPv4(left)))
100
- .filter((address, index, all) => all.indexOf(address) === index);
168
+ .map(candidate => ({
169
+ ...candidate,
170
+ score: scoreReachableIPv4Candidate(candidate)
171
+ }))
172
+ .sort((left, right) => right.score - left.score || left.address.localeCompare(right.address))
173
+ .filter((candidate, index, all) => all.findIndex(item => item.address === candidate.address) === index);
174
+ }
175
+
176
+ function getReachableIPv4Candidates() {
177
+ return getReachableIPv4CandidateEntries().map(candidate => candidate.address);
101
178
  }
102
179
 
103
180
  function getReachableIPv4Address() {
@@ -409,7 +486,8 @@ export function createRemoteHub(options = {}) {
409
486
  }
410
487
 
411
488
  function getAgentEndpointRouteInfo() {
412
- const candidates = getReachableIPv4Candidates();
489
+ const candidateDetails = getReachableIPv4CandidateEntries();
490
+ const candidates = candidateDetails.map(candidate => candidate.address);
413
491
  const endpoint = getAgentEndpoint();
414
492
  const wildcardWithoutReachableAddress =
415
493
  !publicEndpoint
@@ -424,7 +502,8 @@ export function createRemoteHub(options = {}) {
424
502
  port: parsed?.port || 0,
425
503
  accountRouteReady: reason === 'ok',
426
504
  reason,
427
- candidates
505
+ candidates,
506
+ candidateDetails
428
507
  };
429
508
  }
430
509
 
@@ -574,6 +653,7 @@ export function createRemoteHub(options = {}) {
574
653
  agentEndpointAccountRouteReady: routeInfo.accountRouteReady,
575
654
  agentEndpointRouteReason: routeInfo.reason,
576
655
  agentEndpointCandidates: routeInfo.candidates,
656
+ agentEndpointCandidateDetails: routeInfo.candidateDetails,
577
657
  pairToken: includeSecrets ? pairToken : undefined,
578
658
  pairTokenPreview: maskToken(pairToken),
579
659
  deviceCount: devices.size,
package/server.js CHANGED
@@ -2954,10 +2954,12 @@ async function startRemoteAgentConnection(options = {}) {
2954
2954
  const engine = normalizeRemoteAgentEngine(options.engine);
2955
2955
 
2956
2956
  if (!manager) {
2957
+ logWarn('remote', 'managed RemoteAgent connect rejected: invalid manager endpoint');
2957
2958
  return { ok: false, error: 'invalid-manager-endpoint', agent: serializeRemoteAgentState() };
2958
2959
  }
2959
2960
 
2960
2961
  if (!pairToken) {
2962
+ logWarn('remote', `managed RemoteAgent connect rejected: missing pair token ${formatKeyValue('manager', manager)}`);
2961
2963
  return { ok: false, error: 'missing-pair-token', agent: serializeRemoteAgentState() };
2962
2964
  }
2963
2965
 
@@ -3056,6 +3058,7 @@ async function startRemoteAgentConnection(options = {}) {
3056
3058
  const exitedEarly = await waitForRemoteAgentEarlyExit(child);
3057
3059
  if (exitedEarly) {
3058
3060
  const error = formatRemoteAgentFailureSummary();
3061
+ logWarn('remote', `managed RemoteAgent exited during startup ${formatKeyValue('manager', manager)} ${formatKeyValue('error', error)}`);
3059
3062
  return { ok: false, error, agent: serializeRemoteAgentState() };
3060
3063
  }
3061
3064
 
@@ -7615,14 +7618,18 @@ app.get('/api/remote/agent/status', (req, res) => {
7615
7618
  app.post('/api/remote/agent/connect', async (req, res) => {
7616
7619
  res.setHeader('Cache-Control', 'no-store');
7617
7620
  try {
7621
+ const requestedManager = normalizeRemoteManagerEndpoint(req.body?.manager || req.body?.managerEndpoint);
7618
7622
  const result = await startRemoteAgentConnection({
7619
- manager: req.body?.manager || req.body?.managerEndpoint,
7623
+ manager: requestedManager,
7620
7624
  pairToken: req.body?.pairToken || req.body?.pair,
7621
7625
  leaseId: req.body?.leaseId,
7622
7626
  nodeId: req.body?.nodeId,
7623
7627
  engine: req.body?.engine,
7624
7628
  source: req.body?.source || 'registry'
7625
7629
  });
7630
+ if (!result.ok) {
7631
+ logWarn('remote', `managed RemoteAgent connect failed ${formatKeyValue('manager', requestedManager || 'invalid')} ${formatKeyValue('error', result.error || 'unknown')}`);
7632
+ }
7626
7633
  res.status(result.ok ? 200 : 400).json(result);
7627
7634
  } catch (err) {
7628
7635
  logError('remote', 'managed RemoteAgent connect failed.', err);