genexus-mcp 3.5.3 → 3.6.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.
- package/README.md +37 -17
- package/cli/commands/axi.js +128 -22
- package/cli/lib/client-adapters.js +8 -2
- package/cli/lib/config.js +163 -36
- package/cli/run.test.js +264 -12
- package/config/gx-versions.json +57 -0
- package/docs/llm_cli_mcp_playbook.md +23 -0
- package/package.json +3 -3
- package/publish/GxMcp.Gateway.deps.json +2 -2
- package/publish/GxMcp.Gateway.dll +0 -0
- package/publish/GxMcp.Gateway.exe +0 -0
- package/publish/config/gx-versions.json +57 -0
- package/publish/config.json +8 -8
- package/publish/gxmcp-manifest.json +10 -10
- package/publish/gxmcp-sbom.json +4 -4
- package/publish/tool_definitions.json +12 -12
- package/publish/worker/GxMcp.Worker.exe +0 -0
- package/publish/worker/config/gx-versions.json +57 -0
- package/publish/worker/gx-versions.json +57 -0
- package/publish/worker/sdk-compatibility.json +1 -1
- package/scripts/verify-install.js +2 -1
package/cli/lib/config.js
CHANGED
|
@@ -88,9 +88,12 @@ function getGeneXusVersionCatalog() {
|
|
|
88
88
|
const catalog = JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
89
89
|
if (!catalog || !catalog.primaryMajor || !Array.isArray(catalog.supportedMajors)) continue;
|
|
90
90
|
const entries = catalog.supportedMajors.filter((entry) => entry && /^\d+$/.test(String(entry.major)));
|
|
91
|
+
const legacyEntries = Array.isArray(catalog.legacyMajors)
|
|
92
|
+
? catalog.legacyMajors.filter((entry) => entry && /^(?:10\.[1-3]|\d+)$/.test(String(entry.major)))
|
|
93
|
+
: [];
|
|
91
94
|
const primary = entries.find((entry) => String(entry.major) === String(catalog.primaryMajor));
|
|
92
95
|
if (entries.length > 0 && primary?.defaultInstallPath) {
|
|
93
|
-
return { ...catalog, supportedMajors: entries };
|
|
96
|
+
return { ...catalog, supportedMajors: entries, legacyMajors: legacyEntries };
|
|
94
97
|
}
|
|
95
98
|
} catch {
|
|
96
99
|
}
|
|
@@ -101,13 +104,24 @@ function getGeneXusVersionCatalog() {
|
|
|
101
104
|
{ major: '17', displayName: 'GeneXus 17', defaultInstallPath: 'C:\\Program Files (x86)\\GeneXus\\GeneXus17Trial' },
|
|
102
105
|
{ major: '18', displayName: 'GeneXus 18', defaultInstallPath: 'C:\\Program Files (x86)\\GeneXus\\GeneXus18' }
|
|
103
106
|
],
|
|
107
|
+
legacyMajors: [
|
|
108
|
+
{ major: '10.3', displayName: 'GeneXus Evolution 3', defaultInstallPath: 'C:\\Program Files (x86)\\GeneXus\\GeneXusXEv3' },
|
|
109
|
+
{ major: '9', displayName: 'GeneXus 9.0', defaultInstallPath: 'C:\\Program Files (x86)\\ARTech\\GeneXus\\GeneXus 9.0' },
|
|
110
|
+
{ major: '8', displayName: 'GeneXus 8.0', defaultInstallPath: 'C:\\Program Files (x86)\\ARTech\\GeneXus\\GeneXus 8.0' }
|
|
111
|
+
],
|
|
104
112
|
source: 'built-in-fallback'
|
|
105
113
|
};
|
|
106
114
|
}
|
|
107
115
|
|
|
116
|
+
function hasGeneXusExecutable(gxPath) {
|
|
117
|
+
return Boolean(gxPath) && ['genexus.exe', 'gx.exe']
|
|
118
|
+
.some((name) => fs.existsSync(path.join(gxPath, name)));
|
|
119
|
+
}
|
|
120
|
+
|
|
108
121
|
function getGeneXusCatalogEntries(preferredMajor = null) {
|
|
109
122
|
const catalog = getGeneXusVersionCatalog();
|
|
110
|
-
|
|
123
|
+
const all = [...catalog.supportedMajors, ...(catalog.legacyMajors || [])];
|
|
124
|
+
return all.sort((left, right) => {
|
|
111
125
|
const leftPreferred = preferredMajor !== null && String(left.major) === String(preferredMajor);
|
|
112
126
|
const rightPreferred = preferredMajor !== null && String(right.major) === String(preferredMajor);
|
|
113
127
|
if (leftPreferred !== rightPreferred) return Number(rightPreferred) - Number(leftPreferred);
|
|
@@ -144,7 +158,7 @@ function discoverGeneXusFromRegistry(preferredMajor = null) {
|
|
|
144
158
|
const match = out.match(/InstallationDirectory\s+REG_SZ\s+(.+?)\r?\n/i);
|
|
145
159
|
if (match) {
|
|
146
160
|
const candidate = match[1].trim().replace(/[\\/]+$/, '');
|
|
147
|
-
if (candidate &&
|
|
161
|
+
if (candidate && hasGeneXusExecutable(candidate) && matchesPreferredGeneXusMajor(candidate, preferredMajor)) {
|
|
148
162
|
return candidate;
|
|
149
163
|
}
|
|
150
164
|
}
|
|
@@ -163,7 +177,7 @@ function discoverGeneXusFromRegistry(preferredMajor = null) {
|
|
|
163
177
|
const match = out.match(/InstallPath\s+REG_SZ\s+(.+?)\r?\n/i);
|
|
164
178
|
if (match) {
|
|
165
179
|
const candidate = match[1].trim().replace(/[\\/]+$/, '');
|
|
166
|
-
if (candidate &&
|
|
180
|
+
if (candidate && hasGeneXusExecutable(candidate) && matchesPreferredGeneXusMajor(candidate, preferredMajor)) return candidate;
|
|
167
181
|
}
|
|
168
182
|
} catch {
|
|
169
183
|
}
|
|
@@ -178,7 +192,7 @@ function discoverGeneXusFromRegistry(preferredMajor = null) {
|
|
|
178
192
|
function discoverGeneXusInstallation(preferredMajor = null) {
|
|
179
193
|
if (process.env.GENEXUS_HOME) {
|
|
180
194
|
const candidate = process.env.GENEXUS_HOME.replace(/[\\/]+$/, '');
|
|
181
|
-
if (
|
|
195
|
+
if (hasGeneXusExecutable(candidate) && matchesPreferredGeneXusMajor(candidate, preferredMajor)) return candidate;
|
|
182
196
|
}
|
|
183
197
|
|
|
184
198
|
const fromRegistry = discoverGeneXusFromRegistry(preferredMajor);
|
|
@@ -196,7 +210,11 @@ function discoverGeneXusInstallation(preferredMajor = null) {
|
|
|
196
210
|
const entries = getGeneXusCatalogEntries(preferredMajor);
|
|
197
211
|
const seen = new Set();
|
|
198
212
|
for (const base of programDirs) {
|
|
199
|
-
const
|
|
213
|
+
const roots = [
|
|
214
|
+
path.join(base, 'GeneXus'),
|
|
215
|
+
path.join(base, 'Artech', 'GeneXus')
|
|
216
|
+
];
|
|
217
|
+
for (const root of roots) {
|
|
200
218
|
const key = root.toLowerCase();
|
|
201
219
|
if (seen.has(key)) continue;
|
|
202
220
|
seen.add(key);
|
|
@@ -208,7 +226,7 @@ function discoverGeneXusInstallation(preferredMajor = null) {
|
|
|
208
226
|
for (const ver of candidateNames) {
|
|
209
227
|
if (!ver) continue;
|
|
210
228
|
const candidate = path.join(root, ver);
|
|
211
|
-
if (
|
|
229
|
+
if (hasGeneXusExecutable(candidate) && matchesPreferredGeneXusMajor(candidate, preferredMajor)) {
|
|
212
230
|
return candidate;
|
|
213
231
|
}
|
|
214
232
|
}
|
|
@@ -221,13 +239,14 @@ function discoverGeneXusInstallation(preferredMajor = null) {
|
|
|
221
239
|
const majorMatch = entry.match(/^GeneXus\s*(\d+)/i);
|
|
222
240
|
if (majorMatch && !entries.some((item) => String(item.major) === majorMatch[1])) continue;
|
|
223
241
|
const candidate = path.join(root, entry);
|
|
224
|
-
if (
|
|
242
|
+
if (hasGeneXusExecutable(candidate) && matchesPreferredGeneXusMajor(candidate, preferredMajor)) {
|
|
225
243
|
return candidate;
|
|
226
244
|
}
|
|
227
245
|
}
|
|
228
246
|
}
|
|
229
247
|
} catch {
|
|
230
248
|
}
|
|
249
|
+
}
|
|
231
250
|
}
|
|
232
251
|
|
|
233
252
|
const fromPath = discoverGeneXusFromPath(preferredMajor);
|
|
@@ -240,15 +259,22 @@ function discoverGeneXusFromPath(preferredMajor = null) {
|
|
|
240
259
|
if (process.platform !== 'win32') return null;
|
|
241
260
|
try {
|
|
242
261
|
const { execFileSync } = require('child_process');
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
262
|
+
for (const executable of ['genexus.exe', 'gx.exe']) {
|
|
263
|
+
let out;
|
|
264
|
+
try {
|
|
265
|
+
out = execFileSync('where.exe', [executable], {
|
|
266
|
+
encoding: 'utf8',
|
|
267
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
268
|
+
windowsHide: true,
|
|
269
|
+
timeout: 3000
|
|
270
|
+
});
|
|
271
|
+
} catch {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const first = out.split('\n').map((s) => s.trim()).find(Boolean);
|
|
275
|
+
if (first && fs.existsSync(first) && matchesPreferredGeneXusMajor(path.dirname(first), preferredMajor)) {
|
|
276
|
+
return path.dirname(first);
|
|
277
|
+
}
|
|
252
278
|
}
|
|
253
279
|
} catch {
|
|
254
280
|
}
|
|
@@ -708,6 +734,12 @@ function resolveOpenCodeConfigPath(xdgConfig) {
|
|
|
708
734
|
return path.join(xdgConfig, 'opencode', 'opencode.json');
|
|
709
735
|
}
|
|
710
736
|
|
|
737
|
+
function alternateOpenCodeConfigPath(filePath) {
|
|
738
|
+
return path.extname(filePath).toLowerCase() === '.jsonc'
|
|
739
|
+
? filePath.slice(0, -1)
|
|
740
|
+
: filePath.replace(/\.json$/i, '.jsonc');
|
|
741
|
+
}
|
|
742
|
+
|
|
711
743
|
// VS Code stores its user profile (and native MCP mcp.json) in a per-platform
|
|
712
744
|
// location. `variant` is 'Code' (stable) or 'Code - Insiders'.
|
|
713
745
|
function vscodeUserDir(variant, { appData, macAppSupport, xdgConfig }) {
|
|
@@ -807,6 +839,7 @@ function getClientConfigTargets() {
|
|
|
807
839
|
name: 'OpenCode (CLI)',
|
|
808
840
|
format: 'opencode',
|
|
809
841
|
path: resolveOpenCodeConfigPath(xdgConfig),
|
|
842
|
+
alternatePaths: [alternateOpenCodeConfigPath(resolveOpenCodeConfigPath(xdgConfig))],
|
|
810
843
|
installMarkers: [
|
|
811
844
|
path.join(xdgConfig, 'opencode'),
|
|
812
845
|
path.join(home, '.local', 'share', 'opencode')
|
|
@@ -826,6 +859,7 @@ function getClientConfigTargets() {
|
|
|
826
859
|
name: 'OpenCode Desktop',
|
|
827
860
|
format: 'opencode',
|
|
828
861
|
path: resolveOpenCodeConfigPath(xdgConfig),
|
|
862
|
+
alternatePaths: [alternateOpenCodeConfigPath(resolveOpenCodeConfigPath(xdgConfig))],
|
|
829
863
|
detectByMarkerOnly: true,
|
|
830
864
|
installMarkers: [
|
|
831
865
|
path.join(localAppData, 'Programs', '@opencode-aidesktop'),
|
|
@@ -937,21 +971,27 @@ function clientCommandHealth(entry, client = null, { fs: fileSystem = fs } = {})
|
|
|
937
971
|
reason: null,
|
|
938
972
|
structuralState: 'indeterminate',
|
|
939
973
|
semanticState: 'unknown',
|
|
940
|
-
semanticReason: null
|
|
974
|
+
semanticReason: null,
|
|
975
|
+
pathDrift: false,
|
|
976
|
+
pathDriftReason: null
|
|
941
977
|
};
|
|
942
978
|
const invalid = (reason, structuralState = 'present') => ({
|
|
943
979
|
stale: true,
|
|
944
980
|
reason,
|
|
945
981
|
structuralState,
|
|
946
982
|
semanticState: 'invalid',
|
|
947
|
-
semanticReason: reason
|
|
983
|
+
semanticReason: reason,
|
|
984
|
+
pathDrift: false,
|
|
985
|
+
pathDriftReason: null
|
|
948
986
|
});
|
|
949
987
|
const valid = (structuralState = 'present') => ({
|
|
950
988
|
stale: false,
|
|
951
989
|
reason: null,
|
|
952
990
|
structuralState,
|
|
953
991
|
semanticState: 'valid',
|
|
954
|
-
semanticReason: null
|
|
992
|
+
semanticReason: null,
|
|
993
|
+
pathDrift: false,
|
|
994
|
+
pathDriftReason: null
|
|
955
995
|
});
|
|
956
996
|
const missingLauncherReason = 'configured launcher does not exist on disk';
|
|
957
997
|
|
|
@@ -961,7 +1001,9 @@ function clientCommandHealth(entry, client = null, { fs: fileSystem = fs } = {})
|
|
|
961
1001
|
reason: null,
|
|
962
1002
|
structuralState: 'not-registered',
|
|
963
1003
|
semanticState: 'not-applicable',
|
|
964
|
-
semanticReason: null
|
|
1004
|
+
semanticReason: null,
|
|
1005
|
+
pathDrift: false,
|
|
1006
|
+
pathDriftReason: null
|
|
965
1007
|
};
|
|
966
1008
|
}
|
|
967
1009
|
if (typeof entry !== 'object') {
|
|
@@ -973,7 +1015,9 @@ function clientCommandHealth(entry, client = null, { fs: fileSystem = fs } = {})
|
|
|
973
1015
|
reason: null,
|
|
974
1016
|
structuralState: 'not-applicable',
|
|
975
1017
|
semanticState: 'not-applicable',
|
|
976
|
-
semanticReason: null
|
|
1018
|
+
semanticReason: null,
|
|
1019
|
+
pathDrift: false,
|
|
1020
|
+
pathDriftReason: null
|
|
977
1021
|
};
|
|
978
1022
|
}
|
|
979
1023
|
|
|
@@ -1004,13 +1048,21 @@ function clientCommandHealth(entry, client = null, { fs: fileSystem = fs } = {})
|
|
|
1004
1048
|
};
|
|
1005
1049
|
}
|
|
1006
1050
|
if (!exists) return invalid(missingLauncherReason, 'missing');
|
|
1051
|
+
// A gateway exe that exists at a different absolute path is a *different,
|
|
1052
|
+
// working* install — a local checkout's publish/ copy, a fixed-path install,
|
|
1053
|
+
// or another package cache — not a broken registration. Reporting it as
|
|
1054
|
+
// `stale` made `npx genexus-mcp clients` tell a checkout operator to
|
|
1055
|
+
// re-register, which rewrote the harness away from the checkout gateway
|
|
1056
|
+
// (Issue #210). The drift is still surfaced (as `pathDrift`, never `stale`)
|
|
1057
|
+
// and doctor's `client_config_sync` check keeps the "not this CLI's packaged
|
|
1058
|
+
// gateway" warning with its own remediation.
|
|
1007
1059
|
if (client && client.preferDirectGateway) {
|
|
1008
1060
|
const currentPackageGateway = getGatewayExePath();
|
|
1009
|
-
if (
|
|
1061
|
+
if (fileSystem.existsSync(currentPackageGateway) && normalizeExePath(command) !== normalizeExePath(currentPackageGateway)) {
|
|
1010
1062
|
return {
|
|
1011
1063
|
...valid('present'),
|
|
1012
|
-
|
|
1013
|
-
|
|
1064
|
+
pathDrift: true,
|
|
1065
|
+
pathDriftReason: `configured gateway differs from this CLI's gateway (${currentPackageGateway})`
|
|
1014
1066
|
};
|
|
1015
1067
|
}
|
|
1016
1068
|
}
|
|
@@ -1131,13 +1183,16 @@ function clientsStatus(opts = {}) {
|
|
|
1131
1183
|
serverName,
|
|
1132
1184
|
isThirdParty: Boolean(isThirdParty),
|
|
1133
1185
|
writeSupported: client.writeSupported !== false,
|
|
1134
|
-
configPath: client.path,
|
|
1186
|
+
configPath: entry && entry.configPath ? entry.configPath : client.path,
|
|
1187
|
+
configPaths: entry && entry.configPath ? [entry.configPath] : [client.path],
|
|
1135
1188
|
command: entry && entry.command ? entry.command : null,
|
|
1136
1189
|
args: entry && Array.isArray(entry.args) ? entry.args : [],
|
|
1137
1190
|
url: entry && entry.url ? entry.url : null,
|
|
1138
1191
|
launcherStructuralState: health.structuralState,
|
|
1139
1192
|
launcherSemanticState: health.semanticState,
|
|
1140
1193
|
launcherSemanticReason: health.semanticReason,
|
|
1194
|
+
launcherPathDrift: health.pathDrift,
|
|
1195
|
+
launcherPathDriftReason: health.pathDriftReason,
|
|
1141
1196
|
commandStale: health.stale,
|
|
1142
1197
|
commandStaleReason: health.reason || (isThirdParty ? 'configured as third-party / HTTP MCP server (e.g. official GeneXus MCP)' : null),
|
|
1143
1198
|
detectedAt: det.markerHit || (det.hasConfig ? client.path : null),
|
|
@@ -1426,7 +1481,7 @@ function getOpenCodeMcpContainer(cfgObj) {
|
|
|
1426
1481
|
};
|
|
1427
1482
|
}
|
|
1428
1483
|
|
|
1429
|
-
function applyOpenCodeJson(filePath, launcher, targetConfigPath, { serverName = DEFAULT_MCP_SERVER_NAME, force = false, globalConfig = false, fs: fileSystem = fs } = {}) {
|
|
1484
|
+
function applyOpenCodeJson(filePath, launcher, targetConfigPath, { serverName = DEFAULT_MCP_SERVER_NAME, force = false, globalConfig = false, alternatePaths = [], fs: fileSystem = fs } = {}) {
|
|
1430
1485
|
const parsed = fileSystem.existsSync(filePath) ? readJsonFileSafe(filePath, fileSystem) : {};
|
|
1431
1486
|
if (parsed === null) throw new Error('Invalid JSON');
|
|
1432
1487
|
const cfgObj = parsed || {};
|
|
@@ -1470,10 +1525,20 @@ function applyOpenCodeJson(filePath, launcher, targetConfigPath, { serverName =
|
|
|
1470
1525
|
}
|
|
1471
1526
|
}
|
|
1472
1527
|
writeClientJson(filePath, cfgObj, fileSystem);
|
|
1528
|
+
for (const alternatePath of alternatePaths || []) {
|
|
1529
|
+
if (!fileSystem.existsSync(alternatePath)) continue;
|
|
1530
|
+
applyOpenCodeJson(alternatePath, launcher, targetConfigPath, {
|
|
1531
|
+
serverName,
|
|
1532
|
+
force,
|
|
1533
|
+
globalConfig,
|
|
1534
|
+
alternatePaths: [],
|
|
1535
|
+
fs: fileSystem
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1473
1538
|
}
|
|
1474
1539
|
|
|
1475
|
-
function removeOpenCodeJson(filePath, { serverName = DEFAULT_MCP_SERVER_NAME, fs: fileSystem = fs } = {}) {
|
|
1476
|
-
const parsed = readJsonFileSafe(filePath);
|
|
1540
|
+
function removeOpenCodeJson(filePath, { serverName = DEFAULT_MCP_SERVER_NAME, alternatePaths = [], fs: fileSystem = fs } = {}) {
|
|
1541
|
+
const parsed = readJsonFileSafe(filePath, fileSystem);
|
|
1477
1542
|
if (parsed === null) throw new Error('Invalid JSON');
|
|
1478
1543
|
const cfgObj = parsed || {};
|
|
1479
1544
|
if (!cfgObj.mcp || typeof cfgObj.mcp !== 'object') return false;
|
|
@@ -1503,9 +1568,17 @@ function removeOpenCodeJson(filePath, { serverName = DEFAULT_MCP_SERVER_NAME, fs
|
|
|
1503
1568
|
}
|
|
1504
1569
|
}
|
|
1505
1570
|
}
|
|
1506
|
-
if (
|
|
1507
|
-
|
|
1508
|
-
|
|
1571
|
+
if (removedAny) writeClientJson(filePath, cfgObj, fileSystem);
|
|
1572
|
+
let removedAlternate = false;
|
|
1573
|
+
for (const alternatePath of alternatePaths || []) {
|
|
1574
|
+
if (!fileSystem.existsSync(alternatePath)) continue;
|
|
1575
|
+
removedAlternate = removeOpenCodeJson(alternatePath, {
|
|
1576
|
+
serverName,
|
|
1577
|
+
alternatePaths: [],
|
|
1578
|
+
fs: fileSystem
|
|
1579
|
+
}) || removedAlternate;
|
|
1580
|
+
}
|
|
1581
|
+
return removedAny || removedAlternate;
|
|
1509
1582
|
}
|
|
1510
1583
|
|
|
1511
1584
|
function extractCodexTomlEntry(content, serverName = DEFAULT_MCP_SERVER_NAME) {
|
|
@@ -1650,8 +1723,49 @@ function normalizeExePath(p) {
|
|
|
1650
1723
|
return s;
|
|
1651
1724
|
}
|
|
1652
1725
|
|
|
1726
|
+
function readOpenCodeCommandEntry(filePath, serverName, fileSystem) {
|
|
1727
|
+
if (!fileSystem.existsSync(filePath)) return null;
|
|
1728
|
+
const parsed = readJsonFileSafe(filePath, fileSystem);
|
|
1729
|
+
if (!parsed || typeof parsed !== 'object') return null;
|
|
1730
|
+
let entry = parsed.mcp?.servers?.[serverName] || parsed.mcp?.[serverName];
|
|
1731
|
+
if (!entry && serverName === DEFAULT_MCP_SERVER_NAME) {
|
|
1732
|
+
const legacyGx = parsed.mcp?.servers?.genexus || parsed.mcp?.genexus;
|
|
1733
|
+
if (legacyGx && !isThirdPartyMcpEntry(legacyGx)) entry = legacyGx;
|
|
1734
|
+
else entry = parsed.mcp?.servers?.genexus18 || parsed.mcp?.genexus18;
|
|
1735
|
+
}
|
|
1736
|
+
if (!entry) return null;
|
|
1737
|
+
if (Array.isArray(entry.command) && entry.command.length > 0) {
|
|
1738
|
+
return {
|
|
1739
|
+
command: entry.command[0],
|
|
1740
|
+
args: entry.command.slice(1),
|
|
1741
|
+
url: entry.url || null,
|
|
1742
|
+
type: entry.type || null,
|
|
1743
|
+
environment: entry.environment || null,
|
|
1744
|
+
raw: entry,
|
|
1745
|
+
configPath: filePath
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
return {
|
|
1749
|
+
command: null,
|
|
1750
|
+
args: [],
|
|
1751
|
+
url: entry.url || null,
|
|
1752
|
+
type: entry.type || null,
|
|
1753
|
+
environment: entry.environment || null,
|
|
1754
|
+
raw: entry,
|
|
1755
|
+
configPath: filePath
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1653
1759
|
function readClientCommandEntry(client, serverName = DEFAULT_MCP_SERVER_NAME, { fs: fileSystem = fs } = {}) {
|
|
1654
1760
|
if (client.writeSupported === false) return null;
|
|
1761
|
+
if (client.format === 'opencode') {
|
|
1762
|
+
const paths = [client.path, ...(client.alternatePaths || [])];
|
|
1763
|
+
for (const configPath of paths) {
|
|
1764
|
+
const entry = readOpenCodeCommandEntry(configPath, serverName, fileSystem);
|
|
1765
|
+
if (entry) return entry;
|
|
1766
|
+
}
|
|
1767
|
+
return null;
|
|
1768
|
+
}
|
|
1655
1769
|
if (!fileSystem.existsSync(client.path)) return null;
|
|
1656
1770
|
try {
|
|
1657
1771
|
if (client.format === 'mcpServers') {
|
|
@@ -1752,7 +1866,7 @@ function getLocalAppDataCacheDir() {
|
|
|
1752
1866
|
}
|
|
1753
1867
|
|
|
1754
1868
|
function getGeneXusMajor(version) {
|
|
1755
|
-
const match = String(version || '').match(/^\s*(
|
|
1869
|
+
const match = String(version || '').match(/^\s*(10\.[1-3](?!\d)|\d+)/);
|
|
1756
1870
|
return match ? match[1] : null;
|
|
1757
1871
|
}
|
|
1758
1872
|
|
|
@@ -1809,7 +1923,10 @@ function readGeneXusInstallationIdentity(gxPath, options = {}) {
|
|
|
1809
1923
|
const readExecutableVersion = typeof options.readExecutableVersion === 'function'
|
|
1810
1924
|
? options.readExecutableVersion
|
|
1811
1925
|
: readExecutableProductVersion;
|
|
1812
|
-
|
|
1926
|
+
let executableVersion = readExecutableVersion(path.join(gxPath, 'GeneXus.exe'));
|
|
1927
|
+
if (!executableVersion) {
|
|
1928
|
+
executableVersion = readExecutableVersion(path.join(gxPath, 'gx.exe'));
|
|
1929
|
+
}
|
|
1813
1930
|
if (executableVersion) {
|
|
1814
1931
|
return {
|
|
1815
1932
|
version: executableVersion,
|
|
@@ -1895,14 +2012,24 @@ function readGeneXusKbIdentity(kbPath) {
|
|
|
1895
2012
|
if (!kbPath) return { version: null, major: null, source: 'unavailable', reason: 'missing-kb-path' };
|
|
1896
2013
|
|
|
1897
2014
|
let gxwFiles;
|
|
2015
|
+
let gxiFiles = [];
|
|
1898
2016
|
try {
|
|
1899
|
-
|
|
2017
|
+
const allFiles = fs.readdirSync(kbPath);
|
|
2018
|
+
gxwFiles = allFiles
|
|
1900
2019
|
.filter((fileName) => fileName.toLowerCase().endsWith('.gxw'))
|
|
1901
2020
|
.map((fileName) => path.join(kbPath, fileName));
|
|
2021
|
+
gxiFiles = allFiles
|
|
2022
|
+
.filter((fileName) => fileName.toLowerCase().endsWith('.gxi'))
|
|
2023
|
+
.map((fileName) => path.join(kbPath, fileName));
|
|
1902
2024
|
} catch {
|
|
1903
2025
|
return { version: null, major: null, source: 'unavailable', reason: 'unreadable-kb-path' };
|
|
1904
2026
|
}
|
|
1905
|
-
if (gxwFiles.length === 0)
|
|
2027
|
+
if (gxwFiles.length === 0) {
|
|
2028
|
+
if (gxiFiles.length > 0) {
|
|
2029
|
+
return { version: '9.0', major: '9', source: 'gxi-classic', reason: null };
|
|
2030
|
+
}
|
|
2031
|
+
return { version: null, major: null, source: 'unavailable', reason: 'no-gxw' };
|
|
2032
|
+
}
|
|
1906
2033
|
if (gxwFiles.length > 1) return { version: null, major: null, source: 'unavailable', reason: 'multiple-gxw' };
|
|
1907
2034
|
|
|
1908
2035
|
try {
|