genexus-mcp 2.8.3 → 2.9.0
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/cli/commands/axi.js +7 -2
- package/cli/index.js +11 -0
- package/cli/lib/update-check.js +241 -74
- package/cli/run.test.js +31 -1
- package/package.json +1 -1
- 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.json +17 -20
- package/publish/worker/GxMcp.Worker.exe +0 -0
- package/publish/GxMcp.Gateway.pdb +0 -0
- package/publish/worker/GxMcp.Worker.pdb +0 -0
package/cli/commands/axi.js
CHANGED
|
@@ -2042,8 +2042,13 @@ function commandHelpMap() {
|
|
|
2042
2042
|
examples: ['genexus-mcp llm help --format json', 'genexus-mcp llm help --full --format json']
|
|
2043
2043
|
},
|
|
2044
2044
|
update: {
|
|
2045
|
-
usage: 'genexus-mcp update [--format toon|json|text]',
|
|
2046
|
-
examples: [
|
|
2045
|
+
usage: 'genexus-mcp update [--apply] [--yes] [--channel latest|next] [--format toon|json|text]',
|
|
2046
|
+
examples: [
|
|
2047
|
+
'genexus-mcp update # check; reports your install method + the right upgrade step',
|
|
2048
|
+
'genexus-mcp update --apply # perform the upgrade for your install method (confirms first)',
|
|
2049
|
+
'genexus-mcp update --apply --yes # unattended (CI/automation)',
|
|
2050
|
+
'genexus-mcp update --channel next # check the @next dist-tag'
|
|
2051
|
+
]
|
|
2047
2052
|
},
|
|
2048
2053
|
layout: {
|
|
2049
2054
|
usage: 'genexus-mcp layout status [--title "GeneXus"] [--format ...] OR genexus-mcp layout run --action <focus|activate-layout|activate-tab|send-keys|type-text|click> [--tab "Layout"] [--keys "..."] [--text "..."] [--x N --y N] [--title "..."] [--format ...] OR genexus-mcp layout inspect [--tab "Layout"] [--limit N] [--full] [--title "..."] [--format ...]',
|
package/cli/index.js
CHANGED
|
@@ -48,6 +48,8 @@ const GLOBAL_DEFAULTS = {
|
|
|
48
48
|
noSmoke: false,
|
|
49
49
|
warm: false,
|
|
50
50
|
yes: false,
|
|
51
|
+
apply: false,
|
|
52
|
+
channel: null,
|
|
51
53
|
name: null,
|
|
52
54
|
limit: 100,
|
|
53
55
|
query: null,
|
|
@@ -287,6 +289,15 @@ function parseArgs(argv) {
|
|
|
287
289
|
case 'yes':
|
|
288
290
|
result.options.yes = true;
|
|
289
291
|
break;
|
|
292
|
+
case 'apply':
|
|
293
|
+
result.options.apply = true;
|
|
294
|
+
break;
|
|
295
|
+
case 'channel': {
|
|
296
|
+
const val = takeValue();
|
|
297
|
+
if (val) result.options.channel = val;
|
|
298
|
+
else result.unknownFlags.push('--channel requires a value');
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
290
301
|
case 'quiet':
|
|
291
302
|
result.options.quiet = true;
|
|
292
303
|
break;
|
package/cli/lib/update-check.js
CHANGED
|
@@ -2,18 +2,18 @@ const fs = require('fs');
|
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const https = require('https');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
5
6
|
const {
|
|
6
7
|
getGatewayExePath,
|
|
7
|
-
|
|
8
|
-
filterClientTargets,
|
|
9
|
-
readClientCommandEntry,
|
|
8
|
+
clientsStatus,
|
|
10
9
|
normalizeExePath
|
|
11
10
|
} = require('./config');
|
|
12
11
|
|
|
13
12
|
const REPO = 'lennix1337/Genexus18MCP';
|
|
14
13
|
const NPM_PACKAGE = 'genexus-mcp';
|
|
15
14
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
16
|
-
const FETCH_TIMEOUT_MS =
|
|
15
|
+
const FETCH_TIMEOUT_MS = 2500;
|
|
16
|
+
const INSTALL_ONE_LINER = 'iex (irm https://raw.githubusercontent.com/lennix1337/Genexus18MCP/main/scripts/install.ps1)';
|
|
17
17
|
|
|
18
18
|
function getPackageVersion() {
|
|
19
19
|
try {
|
|
@@ -25,9 +25,21 @@ function getPackageVersion() {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function getCacheFile() {
|
|
28
|
+
// Share the cache with the gateway (UpdateNotifier.cs) so a check by either
|
|
29
|
+
// side serves the other. On Windows that's %LOCALAPPDATA%\GenexusMCP; on other
|
|
30
|
+
// platforms fall back to ~/.genexus-mcp (the gateway is Windows-only).
|
|
31
|
+
if (process.platform === 'win32') {
|
|
32
|
+
const base = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
33
|
+
return path.join(base, 'GenexusMCP', 'update-check.json');
|
|
34
|
+
}
|
|
28
35
|
return path.join(os.homedir(), '.genexus-mcp', 'update-check.json');
|
|
29
36
|
}
|
|
30
37
|
|
|
38
|
+
function releaseUrlForVersion(version) {
|
|
39
|
+
const v = stripV(version);
|
|
40
|
+
return v ? `https://github.com/${REPO}/releases/tag/v${v}` : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
function readCache() {
|
|
32
44
|
try {
|
|
33
45
|
const raw = fs.readFileSync(getCacheFile(), 'utf8');
|
|
@@ -69,51 +81,62 @@ function compareSemver(a, b) {
|
|
|
69
81
|
return 0;
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
function
|
|
84
|
+
function httpGetJson(url) {
|
|
73
85
|
return new Promise((resolve) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
'
|
|
80
|
-
'
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const req = https.request(options, (res) => {
|
|
85
|
-
if (res.statusCode !== 200) {
|
|
86
|
-
res.resume();
|
|
87
|
-
resolve(null);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
let body = '';
|
|
91
|
-
res.setEncoding('utf8');
|
|
92
|
-
res.on('data', (chunk) => { body += chunk; });
|
|
93
|
-
res.on('end', () => {
|
|
94
|
-
try {
|
|
95
|
-
const json = JSON.parse(body);
|
|
96
|
-
const tag = stripV(json.tag_name || '');
|
|
97
|
-
const url = typeof json.html_url === 'string' ? json.html_url : null;
|
|
98
|
-
if (!tag) { resolve(null); return; }
|
|
99
|
-
resolve({ latestVersion: tag, releaseUrl: url });
|
|
100
|
-
} catch {
|
|
101
|
-
resolve(null);
|
|
102
|
-
}
|
|
86
|
+
let req;
|
|
87
|
+
try {
|
|
88
|
+
req = https.request(url, { method: 'GET', headers: { 'User-Agent': `${NPM_PACKAGE}-cli`, Accept: 'application/json' } }, (res) => {
|
|
89
|
+
if (res.statusCode !== 200) { res.resume(); resolve(null); return; }
|
|
90
|
+
let body = '';
|
|
91
|
+
res.setEncoding('utf8');
|
|
92
|
+
res.on('data', (c) => { body += c; });
|
|
93
|
+
res.on('end', () => {
|
|
94
|
+
try { resolve(JSON.parse(body)); } catch { resolve(null); }
|
|
95
|
+
});
|
|
103
96
|
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
req.on('error', () => resolve(null));
|
|
107
|
-
req.setTimeout(FETCH_TIMEOUT_MS, () => {
|
|
108
|
-
req.destroy();
|
|
97
|
+
} catch {
|
|
109
98
|
resolve(null);
|
|
110
|
-
|
|
111
|
-
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
req.on('error', () => resolve(null));
|
|
102
|
+
req.setTimeout(FETCH_TIMEOUT_MS, () => { req.destroy(); resolve(null); });
|
|
112
103
|
req.end();
|
|
113
104
|
if (typeof req.unref === 'function') req.unref();
|
|
114
105
|
});
|
|
115
106
|
}
|
|
116
107
|
|
|
108
|
+
// Resolve the latest version for a dist-tag channel. Authority is the npm
|
|
109
|
+
// registry — that's exactly what `npm install -g <pkg>@<channel>` resolves, so
|
|
110
|
+
// we never advertise a version npm can't install yet (the GitHub-release-before-
|
|
111
|
+
// npm-publish window) and we work on networks that allow npm but block
|
|
112
|
+
// api.github.com. Falls back to the GitHub releases API only for the default
|
|
113
|
+
// channel. The release URL is derived from the version (no API call needed).
|
|
114
|
+
async function fetchLatestRelease(opts = {}) {
|
|
115
|
+
const channel = (opts && opts.channel) || 'latest';
|
|
116
|
+
|
|
117
|
+
// 1. npm registry dist-tags (lightweight: just the tag → version map).
|
|
118
|
+
const tags = await httpGetJson(`https://registry.npmjs.org/-/package/${NPM_PACKAGE}/dist-tags`);
|
|
119
|
+
if (tags && typeof tags === 'object') {
|
|
120
|
+
const v = stripV(tags[channel] || '');
|
|
121
|
+
if (v) return { latestVersion: v, releaseUrl: releaseUrlForVersion(v), source: 'npm' };
|
|
122
|
+
// Channel not found on npm — for non-default channels, that's a definitive "no".
|
|
123
|
+
if (channel !== 'latest') return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 2. GitHub releases fallback (default channel only).
|
|
127
|
+
if (channel === 'latest') {
|
|
128
|
+
const rel = await httpGetJson(`https://api.github.com/repos/${REPO}/releases/latest`);
|
|
129
|
+
if (rel && typeof rel === 'object') {
|
|
130
|
+
const tag = stripV(rel.tag_name || '');
|
|
131
|
+
if (tag) {
|
|
132
|
+
const url = typeof rel.html_url === 'string' ? rel.html_url : releaseUrlForVersion(tag);
|
|
133
|
+
return { latestVersion: tag, releaseUrl: url, source: 'github' };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
117
140
|
function formatBanner(current, latest, releaseUrl) {
|
|
118
141
|
const lines = [
|
|
119
142
|
`[genexus-mcp] update available: v${current} -> v${latest}`,
|
|
@@ -155,7 +178,8 @@ function scheduleBackgroundFetch() {
|
|
|
155
178
|
writeCache({
|
|
156
179
|
checkedAt: Date.now(),
|
|
157
180
|
latestVersion: result.latestVersion,
|
|
158
|
-
releaseUrl: result.releaseUrl
|
|
181
|
+
releaseUrl: result.releaseUrl,
|
|
182
|
+
source: result.source || null
|
|
159
183
|
});
|
|
160
184
|
}).catch(() => {});
|
|
161
185
|
}
|
|
@@ -166,21 +190,23 @@ function startBackgroundUpdateCheck(opts) {
|
|
|
166
190
|
scheduleBackgroundFetch();
|
|
167
191
|
}
|
|
168
192
|
|
|
193
|
+
// Drift = a client points at a gateway launcher (exe/bat/...) that is NOT this
|
|
194
|
+
// npm package's gateway, so `npm install -g` won't update it. Reuses the
|
|
195
|
+
// broadened command resolution from config.js rather than re-implementing the
|
|
196
|
+
// (formerly .exe-only) match.
|
|
169
197
|
function detectClientExeDrift() {
|
|
170
198
|
try {
|
|
171
199
|
const packageNorm = normalizeExePath(getGatewayExePath());
|
|
172
|
-
const targets = filterClientTargets(getClientConfigTargets(), { platform: process.platform });
|
|
173
200
|
const mismatches = [];
|
|
174
|
-
for (const
|
|
175
|
-
if (!
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
//
|
|
180
|
-
if (
|
|
181
|
-
if (!/\.exe$/i.test(cmd)) continue;
|
|
201
|
+
for (const c of clientsStatus()) {
|
|
202
|
+
if (!c.registered || !c.command) continue;
|
|
203
|
+
const cmd = c.command;
|
|
204
|
+
// npx/node/genexus-mcp shims resolve via npm at runtime — not drift.
|
|
205
|
+
if (/(^|[\\/])(npx|npx\.cmd|node|node\.exe|genexus-mcp|genexus-mcp\.cmd)$/i.test(cmd)) continue;
|
|
206
|
+
// Only an explicit-path launcher can "drift" from the package exe.
|
|
207
|
+
if (!/[\\/]/.test(cmd)) continue;
|
|
182
208
|
if (normalizeExePath(cmd) !== packageNorm) {
|
|
183
|
-
mismatches.push({ client:
|
|
209
|
+
mismatches.push({ client: c.name, configured: cmd });
|
|
184
210
|
}
|
|
185
211
|
}
|
|
186
212
|
return mismatches;
|
|
@@ -189,41 +215,161 @@ function detectClientExeDrift() {
|
|
|
189
215
|
}
|
|
190
216
|
}
|
|
191
217
|
|
|
192
|
-
|
|
218
|
+
// How is the gateway actually launched by the registered clients? That decides
|
|
219
|
+
// the right upgrade action (npx@latest auto-updates on restart; npm-global needs
|
|
220
|
+
// `npm i -g`; a fixed exe path needs the installer). Returns the dominant method
|
|
221
|
+
// plus per-method evidence.
|
|
222
|
+
function detectInstallMethod() {
|
|
223
|
+
// Corporate/fixed-path env override wins — the gateway runs from a pinned exe.
|
|
224
|
+
if (process.env.GENEXUS_MCP_GATEWAY_EXE) {
|
|
225
|
+
return { method: 'fixed-path', detail: process.env.GENEXUS_MCP_GATEWAY_EXE, evidence: [] };
|
|
226
|
+
}
|
|
227
|
+
const counts = { 'npx-latest': 0, 'npm-global': 0, 'fixed-path': 0 };
|
|
228
|
+
const evidence = [];
|
|
229
|
+
try {
|
|
230
|
+
for (const c of clientsStatus()) {
|
|
231
|
+
if (!c.registered || !c.command) continue;
|
|
232
|
+
const cmd = String(c.command);
|
|
233
|
+
let m;
|
|
234
|
+
if (/(^|[\\/])npx(\.cmd)?$/i.test(cmd)) m = 'npx-latest';
|
|
235
|
+
else if (/(^|[\\/])genexus-mcp(\.cmd)?$/i.test(cmd)) m = 'npm-global';
|
|
236
|
+
else if (/[\\/]/.test(cmd)) m = 'fixed-path';
|
|
237
|
+
else m = 'npm-global';
|
|
238
|
+
counts[m] = (counts[m] || 0) + 1;
|
|
239
|
+
evidence.push({ client: c.name, method: m, command: cmd });
|
|
240
|
+
}
|
|
241
|
+
} catch { /* ignore */ }
|
|
242
|
+
// Pick the most common; default to npx-latest (the recommended/auto path).
|
|
243
|
+
let method = 'npx-latest';
|
|
244
|
+
let best = -1;
|
|
245
|
+
for (const k of Object.keys(counts)) {
|
|
246
|
+
if (counts[k] > best) { best = counts[k]; method = k; }
|
|
247
|
+
}
|
|
248
|
+
if (best <= 0) method = 'npx-latest';
|
|
249
|
+
return { method, counts, evidence };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// The method-appropriate upgrade plan. `auto` means no manual install step is
|
|
253
|
+
// needed (the npx launcher fetches @latest on the next client start).
|
|
254
|
+
function upgradePlanFor(method, channel) {
|
|
255
|
+
const tag = channel && channel !== 'latest' ? `@${channel}` : '@latest';
|
|
256
|
+
if (method === 'npx-latest') {
|
|
257
|
+
return {
|
|
258
|
+
method,
|
|
259
|
+
auto: true,
|
|
260
|
+
steps: [
|
|
261
|
+
'Your clients launch via `npx genexus-mcp@latest`, which fetches the newest version on each start.',
|
|
262
|
+
'Just fully restart your AI client — it will pick up the new version automatically.'
|
|
263
|
+
],
|
|
264
|
+
// --apply busts a stale npx cache so the next spawn is guaranteed fresh.
|
|
265
|
+
applyCommand: { exe: process.platform === 'win32' ? 'npm.cmd' : 'npm', args: ['cache', 'clean', '--force'] },
|
|
266
|
+
restartRequired: true
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
if (method === 'fixed-path') {
|
|
270
|
+
return {
|
|
271
|
+
method,
|
|
272
|
+
auto: false,
|
|
273
|
+
steps: [
|
|
274
|
+
'Your install runs the gateway from a fixed path (corporate install).',
|
|
275
|
+
`Re-run the installer to update in place: ${INSTALL_ONE_LINER}`,
|
|
276
|
+
'Then fully restart your AI client.'
|
|
277
|
+
],
|
|
278
|
+
applyCommand: null, // self-stage is a future enhancement; installer is the path
|
|
279
|
+
restartRequired: true
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
// npm-global
|
|
283
|
+
return {
|
|
284
|
+
method: 'npm-global',
|
|
285
|
+
auto: false,
|
|
286
|
+
steps: [
|
|
287
|
+
`Run: npm install -g ${NPM_PACKAGE}${tag}`,
|
|
288
|
+
'Then fully restart your AI client.'
|
|
289
|
+
],
|
|
290
|
+
applyCommand: { exe: process.platform === 'win32' ? 'npm.cmd' : 'npm', args: ['install', '-g', `${NPM_PACKAGE}${tag}`] },
|
|
291
|
+
restartRequired: true
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function runCommand(exe, args) {
|
|
296
|
+
return new Promise((resolve) => {
|
|
297
|
+
let child;
|
|
298
|
+
try {
|
|
299
|
+
child = spawn(exe, args, { stdio: 'inherit', windowsHide: true });
|
|
300
|
+
} catch (err) {
|
|
301
|
+
resolve({ ok: false, code: null, error: err && err.message ? err.message : 'spawn failed' });
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
child.on('error', (err) => resolve({ ok: false, code: null, error: err && err.message ? err.message : 'spawn failed' }));
|
|
305
|
+
child.on('exit', (code) => resolve({ ok: code === 0, code }));
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function handleUpdate(options, ctx) {
|
|
310
|
+
const opts = options || {};
|
|
311
|
+
const channel = opts.channel || 'latest';
|
|
193
312
|
const current = getPackageVersion();
|
|
194
|
-
const result = await fetchLatestRelease();
|
|
313
|
+
const result = await fetchLatestRelease({ channel });
|
|
195
314
|
const mismatches = detectClientExeDrift();
|
|
315
|
+
const install = detectInstallMethod();
|
|
196
316
|
|
|
197
317
|
const driftHelp = mismatches.length
|
|
198
|
-
? [`WARNING: ${mismatches.length} AI client(s) point at a gateway
|
|
318
|
+
? [`WARNING: ${mismatches.length} AI client(s) point at a gateway launcher that is NOT this npm package — updating npm will NOT update them. Mismatches: ${mismatches.map((m) => `${m.client} -> ${m.configured}`).join('; ')}. Re-run scripts/install.ps1 (or genexus-mcp clients add) to resync.`]
|
|
199
319
|
: [];
|
|
200
320
|
|
|
201
321
|
if (!result) {
|
|
322
|
+
const reason = channel !== 'latest'
|
|
323
|
+
? `No '${channel}' version found — the npm dist-tag '${channel}' is absent (or the registry is unreachable).`
|
|
324
|
+
: 'Could not resolve the latest version (npm registry + GitHub both unreachable). Check connectivity/proxy or retry later.';
|
|
202
325
|
return {
|
|
203
326
|
exitCode: ctx.EXIT_CODES.OK,
|
|
204
327
|
envelope: {
|
|
205
|
-
ok: {
|
|
206
|
-
|
|
207
|
-
latest: null,
|
|
208
|
-
updateAvailable: false,
|
|
209
|
-
fetched: false,
|
|
210
|
-
clientDrift: mismatches
|
|
211
|
-
},
|
|
212
|
-
help: ['Could not reach GitHub releases API. Check connectivity or retry later.', ...driftHelp]
|
|
328
|
+
ok: { current, latest: null, channel, updateAvailable: false, fetched: false, installMethod: install.method, clientDrift: mismatches },
|
|
329
|
+
help: [reason, ...driftHelp]
|
|
213
330
|
}
|
|
214
331
|
};
|
|
215
332
|
}
|
|
216
333
|
|
|
217
|
-
writeCache({
|
|
218
|
-
checkedAt: Date.now(),
|
|
219
|
-
latestVersion: result.latestVersion,
|
|
220
|
-
releaseUrl: result.releaseUrl
|
|
221
|
-
});
|
|
334
|
+
writeCache({ checkedAt: Date.now(), latestVersion: result.latestVersion, releaseUrl: result.releaseUrl, source: result.source || null });
|
|
222
335
|
|
|
223
336
|
const updateAvailable = compareSemver(result.latestVersion, current || '0.0.0') > 0;
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
337
|
+
const plan = upgradePlanFor(install.method, channel);
|
|
338
|
+
|
|
339
|
+
// --apply: actually perform the method-appropriate upgrade.
|
|
340
|
+
let applied = null;
|
|
341
|
+
if (opts.apply && updateAvailable) {
|
|
342
|
+
if (!plan.applyCommand) {
|
|
343
|
+
applied = { ran: false, reason: 'No automatic apply for this install method; follow the steps above.' };
|
|
344
|
+
} else if (!opts.yes && (!process.stderr || !process.stderr.isTTY)) {
|
|
345
|
+
applied = { ran: false, reason: 'Refusing to run an unattended install without --yes (no interactive terminal).' };
|
|
346
|
+
} else {
|
|
347
|
+
if (!opts.yes) {
|
|
348
|
+
ctx.stderr.write(`\n[genexus-mcp update] About to run: ${plan.applyCommand.exe} ${plan.applyCommand.args.join(' ')}\n`);
|
|
349
|
+
}
|
|
350
|
+
const proceed = opts.yes ? true : await confirmTty(ctx, 'Proceed?');
|
|
351
|
+
if (!proceed) {
|
|
352
|
+
applied = { ran: false, reason: 'Cancelled by user.' };
|
|
353
|
+
} else {
|
|
354
|
+
const r = await runCommand(plan.applyCommand.exe, plan.applyCommand.args);
|
|
355
|
+
applied = { ran: true, ok: r.ok, exitCode: r.code, command: `${plan.applyCommand.exe} ${plan.applyCommand.args.join(' ')}`, error: r.error || null };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const help = [];
|
|
361
|
+
if (updateAvailable) {
|
|
362
|
+
if (plan.auto) help.push('Auto-update path: restart your AI client and it fetches the new version (via npx @latest).');
|
|
363
|
+
help.push(...plan.steps);
|
|
364
|
+
if (result.releaseUrl) help.push(`Release notes: ${result.releaseUrl}`);
|
|
365
|
+
if (!opts.apply && plan.applyCommand) help.push('Or run `genexus-mcp update --apply` to do it now.');
|
|
366
|
+
} else {
|
|
367
|
+
help.push(`Already on the latest ${channel} version.`);
|
|
368
|
+
}
|
|
369
|
+
if (applied && applied.ran && applied.ok) help.push('Update applied. Fully restart your AI client to load it.');
|
|
370
|
+
if (applied && applied.ran && !applied.ok) help.push(`Apply command exited non-zero (${applied.exitCode}). ${applied.error || ''}`.trim());
|
|
371
|
+
if (applied && !applied.ran) help.push(applied.reason);
|
|
372
|
+
help.push(...driftHelp);
|
|
227
373
|
|
|
228
374
|
return {
|
|
229
375
|
exitCode: ctx.EXIT_CODES.OK,
|
|
@@ -231,21 +377,42 @@ async function handleUpdate(_options, ctx) {
|
|
|
231
377
|
ok: {
|
|
232
378
|
current,
|
|
233
379
|
latest: result.latestVersion,
|
|
380
|
+
channel,
|
|
234
381
|
releaseUrl: result.releaseUrl,
|
|
235
382
|
updateAvailable,
|
|
236
|
-
|
|
383
|
+
source: result.source || null,
|
|
384
|
+
installMethod: install.method,
|
|
385
|
+
autoUpdates: plan.auto,
|
|
386
|
+
installCommand: plan.applyCommand ? `${plan.applyCommand.exe} ${plan.applyCommand.args.join(' ')}` : INSTALL_ONE_LINER,
|
|
387
|
+
applied,
|
|
237
388
|
fetched: true,
|
|
238
389
|
clientDrift: mismatches
|
|
239
390
|
},
|
|
240
|
-
help
|
|
391
|
+
help
|
|
241
392
|
}
|
|
242
393
|
};
|
|
243
394
|
}
|
|
244
395
|
|
|
396
|
+
function confirmTty(ctx, question) {
|
|
397
|
+
return new Promise((resolve) => {
|
|
398
|
+
if (!process.stdin || !process.stdin.isTTY) { resolve(false); return; }
|
|
399
|
+
const readline = require('readline');
|
|
400
|
+
const rl = readline.createInterface({ input: process.stdin, output: ctx.stderr });
|
|
401
|
+
rl.question(`${question} [y/N]: `, (a) => {
|
|
402
|
+
rl.close();
|
|
403
|
+
const t = (a || '').trim().toLowerCase();
|
|
404
|
+
resolve(t === 'y' || t === 'yes');
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
245
409
|
module.exports = {
|
|
246
410
|
startBackgroundUpdateCheck,
|
|
247
411
|
handleUpdate,
|
|
248
412
|
compareSemver,
|
|
249
413
|
parseSemver,
|
|
250
|
-
getPackageVersion
|
|
414
|
+
getPackageVersion,
|
|
415
|
+
detectInstallMethod,
|
|
416
|
+
upgradePlanFor,
|
|
417
|
+
fetchLatestRelease
|
|
251
418
|
};
|
package/cli/run.test.js
CHANGED
|
@@ -5,7 +5,7 @@ const path = require('node:path');
|
|
|
5
5
|
const os = require('node:os');
|
|
6
6
|
const fs = require('node:fs');
|
|
7
7
|
const { renderOutput } = require('./lib/output');
|
|
8
|
-
const { compareSemver } = require('./lib/update-check');
|
|
8
|
+
const { compareSemver, detectInstallMethod, upgradePlanFor } = require('./lib/update-check');
|
|
9
9
|
const { detectClientInstalled, readJsonFileSafe } = require('./lib/config');
|
|
10
10
|
|
|
11
11
|
const cliPath = path.join(__dirname, 'run.js');
|
|
@@ -829,6 +829,36 @@ test('compareSemver detects newer, older, equal versions', () => {
|
|
|
829
829
|
assert.equal(compareSemver('garbage', '1.0.0'), 0);
|
|
830
830
|
});
|
|
831
831
|
|
|
832
|
+
test('detectInstallMethod returns fixed-path when GENEXUS_MCP_GATEWAY_EXE is set', () => {
|
|
833
|
+
const prev = process.env.GENEXUS_MCP_GATEWAY_EXE;
|
|
834
|
+
process.env.GENEXUS_MCP_GATEWAY_EXE = 'C:\\Tools\\GenexusMCP\\GxMcp.Gateway.exe';
|
|
835
|
+
try {
|
|
836
|
+
const r = detectInstallMethod();
|
|
837
|
+
assert.equal(r.method, 'fixed-path');
|
|
838
|
+
assert.equal(r.detail, 'C:\\Tools\\GenexusMCP\\GxMcp.Gateway.exe');
|
|
839
|
+
} finally {
|
|
840
|
+
if (prev === undefined) delete process.env.GENEXUS_MCP_GATEWAY_EXE;
|
|
841
|
+
else process.env.GENEXUS_MCP_GATEWAY_EXE = prev;
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
test('upgradePlanFor encodes the per-method upgrade strategy', () => {
|
|
846
|
+
const npx = upgradePlanFor('npx-latest', 'latest');
|
|
847
|
+
assert.equal(npx.auto, true, 'npx@latest auto-updates on restart');
|
|
848
|
+
assert.ok(npx.steps.join(' ').toLowerCase().includes('restart'));
|
|
849
|
+
|
|
850
|
+
const npm = upgradePlanFor('npm-global', 'latest');
|
|
851
|
+
assert.equal(npm.auto, false);
|
|
852
|
+
assert.deepEqual(npm.applyCommand.args, ['install', '-g', 'genexus-mcp@latest']);
|
|
853
|
+
|
|
854
|
+
const npmNext = upgradePlanFor('npm-global', 'next');
|
|
855
|
+
assert.deepEqual(npmNext.applyCommand.args, ['install', '-g', 'genexus-mcp@next']);
|
|
856
|
+
|
|
857
|
+
const fixed = upgradePlanFor('fixed-path', 'latest');
|
|
858
|
+
assert.equal(fixed.auto, false);
|
|
859
|
+
assert.equal(fixed.applyCommand, null, 'fixed-path has no npm apply; uses the installer');
|
|
860
|
+
});
|
|
861
|
+
|
|
832
862
|
test('gateway passthrough remains intact when no AXI subcommand is used', () => {
|
|
833
863
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genexus-mcp-test-'));
|
|
834
864
|
const fakeGateway = path.join(tempRoot, 'fake-gateway.js');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genexus-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"mcpName": "io.github.lennix1337/genexus",
|
|
5
5
|
"description": "GeneXus 18 MCP server — read, edit, and analyze GeneXus knowledge base objects (transactions, web panels, procedures, SDTs) directly from Claude, Cursor, and other AI agents over the Model Context Protocol.",
|
|
6
6
|
"keywords": [
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"targets": {
|
|
8
8
|
".NETCoreApp,Version=v8.0": {},
|
|
9
9
|
".NETCoreApp,Version=v8.0/win-x64": {
|
|
10
|
-
"GxMcp.Gateway/2.
|
|
10
|
+
"GxMcp.Gateway/2.9.0": {
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"Newtonsoft.Json": "13.0.3",
|
|
13
13
|
"System.Management": "10.0.5",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
"libraries": {
|
|
69
|
-
"GxMcp.Gateway/2.
|
|
69
|
+
"GxMcp.Gateway/2.9.0": {
|
|
70
70
|
"type": "project",
|
|
71
71
|
"serviceable": false,
|
|
72
72
|
"sha512": ""
|
|
Binary file
|
|
Binary file
|
package/publish/config.json
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"DefaultKb": "academicohomolog1"
|
|
19
|
-
}
|
|
20
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"Logging": {
|
|
3
|
+
"Level": "Debug",
|
|
4
|
+
"Path": "logs"
|
|
5
|
+
},
|
|
6
|
+
"Environment": {
|
|
7
|
+
"KBPath": "C:\\\\KBs\\\\YourKB"
|
|
8
|
+
},
|
|
9
|
+
"GeneXus": {
|
|
10
|
+
"InstallationPath": "C:\\\\Program Files (x86)\\\\GeneXus\\\\GeneXus18",
|
|
11
|
+
"WorkerExecutable": "C:\\Projetos\\Genexus18MCP\\publish\\\\worker\\\\GxMcp.Worker.exe"
|
|
12
|
+
},
|
|
13
|
+
"Server": {
|
|
14
|
+
"McpStdio": true,
|
|
15
|
+
"HttpPort": 5000
|
|
16
|
+
}
|
|
17
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|