genexus-mcp 2.0.4 → 2.1.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.
Files changed (182) hide show
  1. package/README.md +155 -132
  2. package/cli/commands/axi.js +423 -32
  3. package/cli/index.js +48 -1
  4. package/cli/lib/config.js +219 -18
  5. package/cli/run.test.js +256 -0
  6. package/package.json +1 -1
  7. package/publish/GxMcp.Gateway.deps.json +125 -0
  8. package/publish/GxMcp.Gateway.dll +0 -0
  9. package/publish/GxMcp.Gateway.exe +0 -0
  10. package/publish/GxMcp.Gateway.pdb +0 -0
  11. package/publish/GxMcp.Gateway.runtimeconfig.json +20 -0
  12. package/publish/Newtonsoft.Json.dll +0 -0
  13. package/publish/System.CodeDom.dll +0 -0
  14. package/publish/System.Management.dll +0 -0
  15. package/publish/System.Security.Permissions.dll +0 -0
  16. package/publish/System.Windows.Extensions.dll +0 -0
  17. package/publish/config.json +19 -0
  18. package/publish/runtimes/win/lib/net8.0/System.Management.dll +0 -0
  19. package/publish/runtimes/win/lib/net8.0/System.Windows.Extensions.dll +0 -0
  20. package/publish/start_mcp.bat +22 -0
  21. package/publish/tool_definitions.json +1027 -0
  22. package/publish/web.config +12 -0
  23. package/publish/worker/Definitions/APIDefinition.xml +427 -0
  24. package/publish/worker/Definitions/APILanguageAttributes.xml +13 -0
  25. package/publish/worker/Definitions/APILanguageColorsDefinition.xml +247 -0
  26. package/publish/worker/Definitions/ATTDefinition.xml +12943 -0
  27. package/publish/worker/Definitions/BASEDefinition.xml +60 -0
  28. package/publish/worker/Definitions/ContextDefinition.xml +43 -0
  29. package/publish/worker/Definitions/ControlDefinition.xml +34677 -0
  30. package/publish/worker/Definitions/ControlsPEMs.xml +3852 -0
  31. package/publish/worker/Definitions/DATASELECTORDefinition.xml +117 -0
  32. package/publish/worker/Definitions/DK.Frm.Functions.psx +199 -0
  33. package/publish/worker/Definitions/DK.Trn.Rules.Functions.psx +77 -0
  34. package/publish/worker/Definitions/DK.Trn.Rules.psx +205 -0
  35. package/publish/worker/Definitions/DPRVDefinition.xml +1132 -0
  36. package/publish/worker/Definitions/DPrv.Rules.psx +30 -0
  37. package/publish/worker/Definitions/DPrv.Source.Functions.psx +2274 -0
  38. package/publish/worker/Definitions/DashboardDefinition.xml +49 -0
  39. package/publish/worker/Definitions/DeployDefinition.xml +151 -0
  40. package/publish/worker/Definitions/DesignSystemDefinition.xml +106 -0
  41. package/publish/worker/Definitions/DocumentationPartDefinition.xml +139 -0
  42. package/publish/worker/Definitions/EXODefinition.xml +1877 -0
  43. package/publish/worker/Definitions/EXOITEMDefinition.xml +5 -0
  44. package/publish/worker/Definitions/EXOMETHDefinition.xml +1236 -0
  45. package/publish/worker/Definitions/EXOPARAMDefinition.xml +883 -0
  46. package/publish/worker/Definitions/EXOPROPDefinition.xml +4620 -0
  47. package/publish/worker/Definitions/FILEDefinition.xml +223 -0
  48. package/publish/worker/Definitions/FORMCLASSDefinition.xml +133 -0
  49. package/publish/worker/Definitions/FormObjDefinition.xml +22921 -0
  50. package/publish/worker/Definitions/Frm.Functions.psx +1677 -0
  51. package/publish/worker/Definitions/Functions.psx +2117 -0
  52. package/publish/worker/Definitions/GRPDefinition.xml +78 -0
  53. package/publish/worker/Definitions/GXPMCommonDefinition.xml +8700 -0
  54. package/publish/worker/Definitions/Gxplorer_CommonDefinition.xml +5980 -0
  55. package/publish/worker/Definitions/GxpmConditions.xml +238 -0
  56. package/publish/worker/Definitions/HelpGenSettingsDefinition.xml +318 -0
  57. package/publish/worker/Definitions/IDXDefinition.xml +54 -0
  58. package/publish/worker/Definitions/IMAGEDefinition.xml +121 -0
  59. package/publish/worker/Definitions/KBDefinition.xml +696 -0
  60. package/publish/worker/Definitions/LNGDefinition.xml +558 -0
  61. package/publish/worker/Definitions/MBRDefinition.xml +131 -0
  62. package/publish/worker/Definitions/MBROPTIONDefinition.xml +211 -0
  63. package/publish/worker/Definitions/MNUDefinition.xml +334 -0
  64. package/publish/worker/Definitions/MNUOPTIONDefinition.xml +51 -0
  65. package/publish/worker/Definitions/MiniAppDefinition.xml +91 -0
  66. package/publish/worker/Definitions/ModelDefinition.xml +3518 -0
  67. package/publish/worker/Definitions/ModuleInfoDefinition.xml +113 -0
  68. package/publish/worker/Definitions/PALETTEDefinition.xml +66 -0
  69. package/publish/worker/Definitions/PRCDefinition.xml +2453 -0
  70. package/publish/worker/Definitions/Prc.Rules.Functions.psx +1925 -0
  71. package/publish/worker/Definitions/Prc.Rules.psx +393 -0
  72. package/publish/worker/Definitions/Prc.Source.Functions.psx +2308 -0
  73. package/publish/worker/Definitions/RPTDefinition.xml +2123 -0
  74. package/publish/worker/Definitions/RuntimeContextDefinition.xml +43 -0
  75. package/publish/worker/Definitions/SD.Conditions.Functions.psx +1336 -0
  76. package/publish/worker/Definitions/SD.Rules.psx +67 -0
  77. package/publish/worker/Definitions/SD.Source.Functions.psx +1562 -0
  78. package/publish/worker/Definitions/SDTDefinition.xml +176 -0
  79. package/publish/worker/Definitions/SDTITEMDefinition.xml +13239 -0
  80. package/publish/worker/Definitions/SDTLEVELDefinition.xml +384 -0
  81. package/publish/worker/Definitions/SINGLEIMAGEDefinition.xml +388 -0
  82. package/publish/worker/Definitions/STENCILDefinition.xml +73 -0
  83. package/publish/worker/Definitions/SYNCDefinition.xml +410 -0
  84. package/publish/worker/Definitions/SuperAppDefinition.xml +180 -0
  85. package/publish/worker/Definitions/Sync.Rules.psx +191 -0
  86. package/publish/worker/Definitions/TBDDefinition.xml +99 -0
  87. package/publish/worker/Definitions/TBLDefinition.xml +302 -0
  88. package/publish/worker/Definitions/TRNDefinition.xml +5640 -0
  89. package/publish/worker/Definitions/ThemeDefinition.xml +60769 -0
  90. package/publish/worker/Definitions/TransactionAttributeDefinition.xml +25 -0
  91. package/publish/worker/Definitions/TransactionLevelDefinition.xml +48 -0
  92. package/publish/worker/Definitions/Trn.Rules.Functions.psx +2237 -0
  93. package/publish/worker/Definitions/Trn.Rules.psx +560 -0
  94. package/publish/worker/Definitions/Trn.Source.Functions.psx +2154 -0
  95. package/publish/worker/Definitions/UrlRewriteDefinition.xml +66 -0
  96. package/publish/worker/Definitions/UserControlDefinition.xml +146 -0
  97. package/publish/worker/Definitions/WBPDefinition.xml +1874 -0
  98. package/publish/worker/Definitions/WKPDefinition.xml +1813 -0
  99. package/publish/worker/Definitions/Wbp.Conditions.Functions.psx +2032 -0
  100. package/publish/worker/Definitions/Wbp.Rules.Functions.psx +2170 -0
  101. package/publish/worker/Definitions/Wbp.Rules.psx +336 -0
  102. package/publish/worker/Definitions/Wbp.Source.Functions.psx +2344 -0
  103. package/publish/worker/Definitions/Wrk.Conditions.Functions.psx +1909 -0
  104. package/publish/worker/Definitions/Wrk.Rules.Functions.psx +1937 -0
  105. package/publish/worker/Definitions/Wrk.Rules.psx +453 -0
  106. package/publish/worker/Definitions/Wrk.Source.Functions.psx +2224 -0
  107. package/publish/worker/Definitions/XFLDefinition.xml +246 -0
  108. package/publish/worker/Definitions/cobolDefinition.xml +2074 -0
  109. package/publish/worker/Definitions/conditions.xml +251 -0
  110. package/publish/worker/Definitions/conditionsSD.xml +236 -0
  111. package/publish/worker/Definitions/csharpDefinition.xml +3527 -0
  112. package/publish/worker/Definitions/csharpwinDefinition.xml +2979 -0
  113. package/publish/worker/Definitions/dataprovider.xml +254 -0
  114. package/publish/worker/Definitions/ds_AS400NATIVEDefinition.xml +2167 -0
  115. package/publish/worker/Definitions/ds_DAMENGDefinition.xml +2546 -0
  116. package/publish/worker/Definitions/ds_DB2400Definition.xml +2209 -0
  117. package/publish/worker/Definitions/ds_DB2COMMONSERVERSDefinition.xml +2394 -0
  118. package/publish/worker/Definitions/ds_DBFCDXDefinition.xml +5 -0
  119. package/publish/worker/Definitions/ds_DBFIDXDefinition.xml +5 -0
  120. package/publish/worker/Definitions/ds_HANADefinition.xml +1821 -0
  121. package/publish/worker/Definitions/ds_INFORMIXDefinition.xml +2498 -0
  122. package/publish/worker/Definitions/ds_MYSQLDefinition.xml +2222 -0
  123. package/publish/worker/Definitions/ds_ORACLEDefinition.xml +2915 -0
  124. package/publish/worker/Definitions/ds_POSTGRESQLDefinition.xml +2206 -0
  125. package/publish/worker/Definitions/ds_SERVICEDefinition.xml +710 -0
  126. package/publish/worker/Definitions/ds_SQLCEDefinition.xml +822 -0
  127. package/publish/worker/Definitions/ds_SQLITEDefinition.xml +1075 -0
  128. package/publish/worker/Definitions/ds_SQLSERVERDefinition.xml +2481 -0
  129. package/publish/worker/Definitions/dv_AS400NATIVEDefinition.xml +53 -0
  130. package/publish/worker/Definitions/dv_DAMENGDefinition.xml +53 -0
  131. package/publish/worker/Definitions/dv_DB2400Definition.xml +47 -0
  132. package/publish/worker/Definitions/dv_DB2COMMONSERVERSDefinition.xml +47 -0
  133. package/publish/worker/Definitions/dv_DBFCDXDefinition.xml +47 -0
  134. package/publish/worker/Definitions/dv_DBFIDXDefinition.xml +47 -0
  135. package/publish/worker/Definitions/dv_HANADefinition.xml +53 -0
  136. package/publish/worker/Definitions/dv_INFORMIXDefinition.xml +53 -0
  137. package/publish/worker/Definitions/dv_MYSQLDefinition.xml +53 -0
  138. package/publish/worker/Definitions/dv_ORACLEDefinition.xml +53 -0
  139. package/publish/worker/Definitions/dv_POSTGRESQLDefinition.xml +53 -0
  140. package/publish/worker/Definitions/dv_SERVICEDefinition.xml +47 -0
  141. package/publish/worker/Definitions/dv_SQLCEDefinition.xml +48 -0
  142. package/publish/worker/Definitions/dv_SQLITEDefinition.xml +47 -0
  143. package/publish/worker/Definitions/dv_SQLSERVERDefinition.xml +53 -0
  144. package/publish/worker/Definitions/dvi_AS400NATIVEDefinition.xml +53 -0
  145. package/publish/worker/Definitions/dvi_DAMENGDefinition.xml +53 -0
  146. package/publish/worker/Definitions/dvi_DB2400Definition.xml +47 -0
  147. package/publish/worker/Definitions/dvi_DB2COMMONSERVERSDefinition.xml +47 -0
  148. package/publish/worker/Definitions/dvi_DBFCDXDefinition.xml +51 -0
  149. package/publish/worker/Definitions/dvi_DBFIDXDefinition.xml +47 -0
  150. package/publish/worker/Definitions/dvi_HANADefinition.xml +53 -0
  151. package/publish/worker/Definitions/dvi_INFORMIXDefinition.xml +53 -0
  152. package/publish/worker/Definitions/dvi_MYSQLDefinition.xml +53 -0
  153. package/publish/worker/Definitions/dvi_ORACLEDefinition.xml +53 -0
  154. package/publish/worker/Definitions/dvi_POSTGRESQLDefinition.xml +53 -0
  155. package/publish/worker/Definitions/dvi_SERVICEDefinition.xml +47 -0
  156. package/publish/worker/Definitions/dvi_SQLCEDefinition.xml +48 -0
  157. package/publish/worker/Definitions/dvi_SQLITEDefinition.xml +47 -0
  158. package/publish/worker/Definitions/dvi_SQLSERVERDefinition.xml +53 -0
  159. package/publish/worker/Definitions/events.xml +340 -0
  160. package/publish/worker/Definitions/eventsSD.xml +331 -0
  161. package/publish/worker/Definitions/exportDefinition.xml +123 -0
  162. package/publish/worker/Definitions/formula.xml +215 -0
  163. package/publish/worker/Definitions/gxtypes.xml +24099 -0
  164. package/publish/worker/Definitions/importDefinition.xml +148 -0
  165. package/publish/worker/Definitions/javaDefinition.xml +4733 -0
  166. package/publish/worker/Definitions/kbInfoDefinition.xml +43 -0
  167. package/publish/worker/Definitions/netcoreDefinition.xml +3506 -0
  168. package/publish/worker/Definitions/netmobileDefinition.xml +1559 -0
  169. package/publish/worker/Definitions/procedures.xml +292 -0
  170. package/publish/worker/Definitions/rpgDefinition.xml +2083 -0
  171. package/publish/worker/Definitions/rubyDefinition.xml +1358 -0
  172. package/publish/worker/Definitions/rules.xml +279 -0
  173. package/publish/worker/Definitions/rulesSD.xml +253 -0
  174. package/publish/worker/Definitions/sdpatternsDefinition.xml +3509 -0
  175. package/publish/worker/Definitions/smartdeviceDefinition.xml +897 -0
  176. package/publish/worker/Definitions/trn_src.xml +335 -0
  177. package/publish/worker/Definitions/vfpDefinition.xml +2372 -0
  178. package/publish/worker/Definitions/vfpcsDefinition.xml +2631 -0
  179. package/publish/worker/GxMcp.Worker.exe +0 -0
  180. package/publish/worker/GxMcp.Worker.exe.config +76 -0
  181. package/publish/worker/GxMcp.Worker.pdb +0 -0
  182. package/publish/worker/Newtonsoft.Json.dll +0 -0
@@ -10,7 +10,15 @@ const {
10
10
  directoryLooksLikeKnowledgeBase,
11
11
  createConfigFile,
12
12
  patchClientConfig,
13
- discoverGeneXusInstallation
13
+ unpatchClientConfig,
14
+ getLocalAppDataCacheDir,
15
+ readGeneXusVersionFromInstall,
16
+ discoverGeneXusInstallation,
17
+ discoverKnowledgeBase,
18
+ readKbCatalog,
19
+ addKbToConfig,
20
+ removeKbFromConfig,
21
+ switchActiveKb
14
22
  } = require('../lib/config');
15
23
 
16
24
  function parseFieldSelection(raw) {
@@ -127,10 +135,8 @@ function buildStatusData(cwd) {
127
135
  return { ready, configFound, gatewayExeFound, kbLooksValid, configPath, gatewayExePath, kbPath, gxPath, configSource };
128
136
  }
129
137
 
130
- async function probeGatewaySpawn(gatewayExePath) {
131
- if (!fs.existsSync(gatewayExePath)) {
132
- return { status: 'fail', detail: 'Gateway executable does not exist for spawn probe.' };
133
- }
138
+ async function spawnGatewayProbe({ env = process.env, spawnHoldMs, timeoutMs, label, successDetail }) {
139
+ const gatewayExePath = getGatewayExePath();
134
140
 
135
141
  return await new Promise((resolve) => {
136
142
  let done = false;
@@ -144,38 +150,41 @@ async function probeGatewaySpawn(gatewayExePath) {
144
150
  const child = spawn(gatewayExePath, ['--axi-spawn-probe'], {
145
151
  stdio: 'ignore',
146
152
  windowsHide: true,
147
- env: process.env
153
+ env
148
154
  });
149
155
 
150
156
  child.once('error', (err) => {
151
- finish({ status: 'fail', detail: `Spawn probe failed: ${err.message}` });
157
+ finish({ status: 'fail', detail: `${label} failed: ${err.message}` });
152
158
  });
153
159
 
154
160
  child.once('spawn', () => {
155
161
  setTimeout(() => {
156
- try {
157
- child.kill();
158
- } catch {
159
- }
160
- finish({ status: 'pass', detail: 'Gateway process can be spawned (probe launched and terminated).' });
161
- }, 180);
162
+ try { child.kill(); } catch { }
163
+ finish({ status: 'pass', detail: successDetail });
164
+ }, spawnHoldMs);
162
165
  });
163
166
 
164
167
  setTimeout(() => {
165
168
  if (!done) {
166
- try {
167
- child.kill();
168
- } catch {
169
- }
170
- finish({ status: 'warn', detail: 'Spawn probe timed out; process was force-stopped.' });
169
+ try { child.kill(); } catch { }
170
+ finish({ status: 'warn', detail: `${label} timed out; process was force-stopped.` });
171
171
  }
172
- }, 900);
172
+ }, timeoutMs);
173
173
  } catch (err) {
174
- finish({ status: 'fail', detail: `Spawn probe threw: ${err.message}` });
174
+ finish({ status: 'fail', detail: `${label} threw: ${err.message}` });
175
175
  }
176
176
  });
177
177
  }
178
178
 
179
+ async function probeGatewaySpawn() {
180
+ return spawnGatewayProbe({
181
+ spawnHoldMs: 180,
182
+ timeoutMs: 900,
183
+ label: 'Spawn probe',
184
+ successDetail: 'Gateway process can be spawned (probe launched and terminated).'
185
+ });
186
+ }
187
+
179
188
  function resolveMcpBaseUrl(cwd) {
180
189
  const configPath = resolveConfigPathNoMutate(cwd);
181
190
  const fallback = 'http://127.0.0.1:5000/mcp';
@@ -339,7 +348,7 @@ async function handleDoctor(options, ctx) {
339
348
  ];
340
349
 
341
350
  if (options.full) {
342
- const probe = await probeGatewaySpawn(gatewayExePath);
351
+ const probe = await probeGatewaySpawn();
343
352
  checks.push({ id: 'gateway_spawn_probe', status: probe.status, detail: probe.detail });
344
353
  } else {
345
354
  checks.push({ id: 'gateway_spawn_probe', status: 'warn', detail: 'Spawn probe skipped by default. Run doctor with --full.' });
@@ -795,20 +804,72 @@ async function handleInit(options, ctx) {
795
804
  return runInteractiveInit({ ...ctx, options });
796
805
  }
797
806
 
798
- if (!options.kb || !options.gx) {
807
+ const resolution = {
808
+ kb: { value: options.kb || null, source: options.kb ? 'flag' : null },
809
+ gx: { value: options.gx || null, source: options.gx ? 'flag' : null }
810
+ };
811
+
812
+ if (!resolution.kb.value) {
813
+ const fromCwd = discoverKnowledgeBase(ctx.cwd);
814
+ if (fromCwd) {
815
+ resolution.kb.value = fromCwd;
816
+ resolution.kb.source = 'cwd';
817
+ }
818
+ }
819
+
820
+ if (!resolution.gx.value) {
821
+ const fromDisco = discoverGeneXusInstallation();
822
+ if (fromDisco) {
823
+ resolution.gx.value = fromDisco;
824
+ resolution.gx.source = 'auto-discovery';
825
+ }
826
+ }
827
+
828
+ if (!resolution.kb.value || !resolution.gx.value) {
829
+ const missing = [];
830
+ if (!resolution.kb.value) missing.push('--kb (and current directory is not a GeneXus KB)');
831
+ if (!resolution.gx.value) missing.push('--gx (and no GeneXus installation was auto-discovered)');
799
832
  return {
800
833
  exitCode: ctx.EXIT_CODES.USAGE,
801
- envelope: usageEnvelope('Missing required flags for non-interactive init. Use --kb and --gx.', ctx.EXIT_CODES.USAGE)
834
+ envelope: usageEnvelope(
835
+ `Cannot resolve required paths: ${missing.join('; ')}. Pass flags explicitly or run from inside a KB folder.`,
836
+ ctx.EXIT_CODES.USAGE
837
+ )
802
838
  };
803
839
  }
804
840
 
805
841
  try {
806
- const created = createConfigFile(options.kb, options.gx);
842
+ const created = createConfigFile(resolution.kb.value, resolution.gx.value);
843
+ const kbName = path.basename(resolution.kb.value);
844
+ addKbToConfig(created.targetConfigPath, kbName, resolution.kb.value);
845
+
807
846
  let patchResult = { patched: [], failed: [] };
808
847
  if (options.writeClients) {
809
848
  patchResult = patchClientConfig(created.targetConfigPath);
810
849
  }
811
850
 
851
+ const verification = await runPostInitVerification({
852
+ cwd: ctx.cwd,
853
+ includeSmoke: !options.noSmoke,
854
+ ctx
855
+ });
856
+
857
+ let warm = null;
858
+ if (options.warm) {
859
+ warm = await warmGateway({ configPath: created.targetConfigPath });
860
+ }
861
+
862
+ const help = [];
863
+ if (patchResult.patched.length === 0 && !options.writeClients) {
864
+ help.push('Run `genexus-mcp init --kb "<kbPath>" --gx "<geneXusPath>" --write-clients` to patch supported clients.');
865
+ }
866
+ if (verification.summary.fail > 0 || verification.summary.warn > 0) {
867
+ help.push('Some verification checks did not pass. Run `genexus-mcp doctor --full --mcp-smoke` for details.');
868
+ }
869
+ if (options.noSmoke) {
870
+ help.push('MCP protocol smoke was skipped (--no-smoke). Re-run `genexus-mcp doctor --mcp-smoke` to validate end-to-end.');
871
+ }
872
+
812
873
  return {
813
874
  exitCode: ctx.EXIT_CODES.OK,
814
875
  envelope: {
@@ -818,14 +879,23 @@ async function handleInit(options, ctx) {
818
879
  configPath: created.targetConfigPath,
819
880
  configFound: true,
820
881
  noOp: !created.changed,
821
- clientsPatchedCount: patchResult.patched.length
882
+ clientsPatchedCount: patchResult.patched.length,
883
+ resolved: {
884
+ kb: { path: resolution.kb.value, source: resolution.kb.source },
885
+ gx: { path: resolution.gx.value, source: resolution.gx.source }
886
+ },
887
+ verification: {
888
+ summary: verification.summary,
889
+ checks: verification.checks
890
+ },
891
+ warm: warm || null
822
892
  },
823
- help: patchResult.patched.length === 0 && !options.writeClients
824
- ? ['Run `genexus-mcp init --kb "<kbPath>" --gx "<geneXusPath>" --write-clients` to patch supported clients.']
825
- : [],
893
+ help,
826
894
  meta: {
827
895
  patchedClients: patchResult.patched,
828
- failedClients: patchResult.failed
896
+ failedClients: patchResult.failed,
897
+ smokeSkipped: !!options.noSmoke,
898
+ warmed: !!warm && warm.status === 'pass'
829
899
  }
830
900
  }
831
901
  };
@@ -837,6 +907,301 @@ async function handleInit(options, ctx) {
837
907
  }
838
908
  }
839
909
 
910
+ async function warmGateway({ configPath }) {
911
+ return spawnGatewayProbe({
912
+ env: { ...process.env, GX_CONFIG_PATH: configPath },
913
+ spawnHoldMs: 1500,
914
+ timeoutMs: 12000,
915
+ label: 'Warm spawn',
916
+ successDetail: 'Gateway warmed (worker process kicked).'
917
+ });
918
+ }
919
+
920
+ async function runPostInitVerification({ cwd, includeSmoke, ctx }) {
921
+ const doctorResult = await handleDoctor(
922
+ { full: false, mcpSmoke: !!includeSmoke, fields: null, limit: 100 },
923
+ { cwd, EXIT_CODES: ctx.EXIT_CODES }
924
+ );
925
+
926
+ const { checks, summary } = doctorResult.envelope.ok;
927
+ return { checks, summary };
928
+ }
929
+
930
+ async function handleWhoami(options, ctx) {
931
+ const data = buildStatusData(ctx.cwd);
932
+
933
+ if (!data.configFound) {
934
+ return {
935
+ exitCode: ctx.EXIT_CODES.OK,
936
+ envelope: {
937
+ ok: {
938
+ connected: false,
939
+ reason: 'No GeneXus MCP configuration was found in the current context.'
940
+ },
941
+ help: [
942
+ 'Run `genexus-mcp init --kb "<kbPath>" --gx "<geneXusPath>"` to configure.',
943
+ 'Or run `genexus-mcp init --interactive` for a guided setup.'
944
+ ]
945
+ }
946
+ };
947
+ }
948
+
949
+ const kbPath = data.kbPath;
950
+ const gxPath = data.gxPath;
951
+ const kbName = kbPath ? path.basename(kbPath) : null;
952
+ const kbExists = !!(kbPath && fs.existsSync(kbPath));
953
+ const kbValid = data.kbLooksValid;
954
+ const gxVersion = readGeneXusVersionFromInstall(gxPath);
955
+
956
+ const ok = {
957
+ connected: true,
958
+ kb: {
959
+ name: kbName,
960
+ path: kbPath,
961
+ exists: kbExists,
962
+ looksValid: kbValid
963
+ },
964
+ geneXus: {
965
+ installationPath: gxPath,
966
+ version: gxVersion
967
+ },
968
+ config: {
969
+ path: data.configPath,
970
+ source: data.configSource
971
+ }
972
+ };
973
+
974
+ const help = [];
975
+ if (!kbExists) help.push('Configured KB path does not exist on disk.');
976
+ if (kbExists && !kbValid) help.push('KB path exists but does not look like a GeneXus KB (no `.gxw` or `KnowledgeBase.Connection`).');
977
+ if (!gxVersion) help.push('Could not read GeneXus version from installation folder (no version.txt detected).');
978
+
979
+ return { exitCode: ctx.EXIT_CODES.OK, envelope: { ok, help } };
980
+ }
981
+
982
+ async function promptYesNo(question) {
983
+ return new Promise((resolve) => {
984
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
985
+ rl.question(`${question} [y/N]: `, (answer) => {
986
+ rl.close();
987
+ const trimmed = (answer || '').trim().toLowerCase();
988
+ resolve(trimmed === 'y' || trimmed === 'yes');
989
+ });
990
+ });
991
+ }
992
+
993
+ async function handleUninstall(options, ctx) {
994
+ const data = buildStatusData(ctx.cwd);
995
+ const cacheDir = getLocalAppDataCacheDir();
996
+ const cacheDirExists = !!(cacheDir && fs.existsSync(cacheDir));
997
+ const localConfigPath = data.configPath;
998
+ const hasLocalConfig = !!localConfigPath && fs.existsSync(localConfigPath);
999
+
1000
+ const plan = {
1001
+ clientEntries: 'Remove `mcpServers.genexus` from any detected AI client config.',
1002
+ cacheDir: cacheDirExists ? `Delete ${cacheDir}` : 'No cache directory present.',
1003
+ localConfig: hasLocalConfig ? `Delete ${localConfigPath}` : 'No local config.json detected.'
1004
+ };
1005
+
1006
+ if (!options.yes) {
1007
+ process.stderr.write('\n[genexus-mcp uninstall] The following actions will be performed:\n');
1008
+ process.stderr.write(` - ${plan.clientEntries}\n`);
1009
+ process.stderr.write(` - ${plan.cacheDir}\n`);
1010
+ process.stderr.write(` - ${plan.localConfig}\n\n`);
1011
+ const confirmed = await promptYesNo('Proceed?');
1012
+ if (!confirmed) {
1013
+ return {
1014
+ exitCode: ctx.EXIT_CODES.OK,
1015
+ envelope: {
1016
+ ok: { action: 'uninstall', cancelled: true, plan },
1017
+ help: ['Pass --yes to skip the interactive confirmation.']
1018
+ }
1019
+ };
1020
+ }
1021
+ }
1022
+
1023
+ const unpatch = unpatchClientConfig();
1024
+
1025
+ let cacheRemoved = false;
1026
+ let cacheError = null;
1027
+ if (cacheDirExists) {
1028
+ try {
1029
+ fs.rmSync(cacheDir, { recursive: true, force: true });
1030
+ cacheRemoved = true;
1031
+ } catch (err) {
1032
+ cacheError = err && err.message ? err.message : 'Unknown error removing cache dir';
1033
+ }
1034
+ }
1035
+
1036
+ let configRemoved = false;
1037
+ let configError = null;
1038
+ if (hasLocalConfig) {
1039
+ try {
1040
+ fs.unlinkSync(localConfigPath);
1041
+ configRemoved = true;
1042
+ } catch (err) {
1043
+ configError = err && err.message ? err.message : 'Unknown error removing config.json';
1044
+ }
1045
+ }
1046
+
1047
+ const help = [];
1048
+ if (cacheError) help.push(`Cache removal failed: ${cacheError}`);
1049
+ if (configError) help.push(`Local config removal failed: ${configError}`);
1050
+ if (unpatch.failed.length > 0) help.push(`Some client configs could not be updated (see meta.failedClients).`);
1051
+ if (unpatch.removed.length > 0) help.push('Restart your AI clients to release any stale MCP connections.');
1052
+
1053
+ return {
1054
+ exitCode: ctx.EXIT_CODES.OK,
1055
+ envelope: {
1056
+ ok: {
1057
+ action: 'uninstall',
1058
+ cancelled: false,
1059
+ removedClients: unpatch.removed,
1060
+ cacheRemoved,
1061
+ cacheDir: cacheDir || null,
1062
+ configRemoved,
1063
+ configPath: localConfigPath || null
1064
+ },
1065
+ help,
1066
+ meta: {
1067
+ skippedClients: unpatch.skipped,
1068
+ failedClients: unpatch.failed
1069
+ }
1070
+ }
1071
+ };
1072
+ }
1073
+
1074
+ async function handleKb(subcommand, options, ctx) {
1075
+ const data = buildStatusData(ctx.cwd);
1076
+ if (!data.configPath) {
1077
+ return {
1078
+ exitCode: ctx.EXIT_CODES.ERROR,
1079
+ envelope: operationalErrorEnvelope(
1080
+ 'No GeneXus MCP config.json found. Run `genexus-mcp init` first.',
1081
+ ctx.EXIT_CODES.ERROR,
1082
+ ['Run `genexus-mcp init --kb "<path>" --gx "<path>"` to create one.']
1083
+ )
1084
+ };
1085
+ }
1086
+
1087
+ const configPath = data.configPath;
1088
+
1089
+ if (subcommand === 'list') {
1090
+ const catalog = readKbCatalog(configPath);
1091
+ const entries = Object.entries(catalog.kbs).map(([name, p]) => ({
1092
+ name,
1093
+ path: p,
1094
+ active: name === catalog.activeKb,
1095
+ exists: fs.existsSync(p)
1096
+ }));
1097
+ if (entries.length === 0 && catalog.kbPath) {
1098
+ entries.push({ name: '(legacy)', path: catalog.kbPath, active: true, exists: fs.existsSync(catalog.kbPath) });
1099
+ }
1100
+ return {
1101
+ exitCode: ctx.EXIT_CODES.OK,
1102
+ envelope: {
1103
+ ok: {
1104
+ activeKb: catalog.activeKb,
1105
+ kbs: entries,
1106
+ returned: entries.length,
1107
+ total: entries.length
1108
+ },
1109
+ help: entries.length === 0
1110
+ ? ['No KBs registered. Run `genexus-mcp kb add --name <name> --kb <path>` to register one.']
1111
+ : []
1112
+ }
1113
+ };
1114
+ }
1115
+
1116
+ if (subcommand === 'add') {
1117
+ if (!options.name || !options.kb) {
1118
+ return {
1119
+ exitCode: ctx.EXIT_CODES.USAGE,
1120
+ envelope: usageEnvelope('`kb add` requires --name and --kb.', ctx.EXIT_CODES.USAGE)
1121
+ };
1122
+ }
1123
+ const catalog = addKbToConfig(configPath, options.name, options.kb);
1124
+ return {
1125
+ exitCode: ctx.EXIT_CODES.OK,
1126
+ envelope: {
1127
+ ok: {
1128
+ action: 'kb.add',
1129
+ name: options.name,
1130
+ path: options.kb,
1131
+ activeKb: catalog.activeKb,
1132
+ registeredCount: Object.keys(catalog.kbs).length
1133
+ },
1134
+ help: catalog.activeKb === options.name
1135
+ ? ['Restart your AI client to pick up the new active KB.']
1136
+ : [`KB registered. Run \`genexus-mcp kb switch --name ${options.name}\` to make it active.`]
1137
+ }
1138
+ };
1139
+ }
1140
+
1141
+ if (subcommand === 'remove') {
1142
+ if (!options.name) {
1143
+ return {
1144
+ exitCode: ctx.EXIT_CODES.USAGE,
1145
+ envelope: usageEnvelope('`kb remove` requires --name.', ctx.EXIT_CODES.USAGE)
1146
+ };
1147
+ }
1148
+ const { catalog, removed } = removeKbFromConfig(configPath, options.name);
1149
+ if (!removed) {
1150
+ return {
1151
+ exitCode: ctx.EXIT_CODES.OK,
1152
+ envelope: {
1153
+ ok: { action: 'kb.remove', name: options.name, removed: false },
1154
+ help: [`KB '${options.name}' was not registered. Run \`genexus-mcp kb list\` to see available names.`]
1155
+ }
1156
+ };
1157
+ }
1158
+ return {
1159
+ exitCode: ctx.EXIT_CODES.OK,
1160
+ envelope: {
1161
+ ok: {
1162
+ action: 'kb.remove',
1163
+ name: options.name,
1164
+ removed: true,
1165
+ activeKb: catalog.activeKb,
1166
+ registeredCount: Object.keys(catalog.kbs).length
1167
+ },
1168
+ help: ['Restart your AI client to apply changes.']
1169
+ }
1170
+ };
1171
+ }
1172
+
1173
+ if (subcommand === 'switch') {
1174
+ const result = switchActiveKb(configPath, { name: options.name, path: options.kb });
1175
+ if (!result.ok) {
1176
+ return {
1177
+ exitCode: ctx.EXIT_CODES.USAGE,
1178
+ envelope: usageEnvelope(result.reason, ctx.EXIT_CODES.USAGE)
1179
+ };
1180
+ }
1181
+ return {
1182
+ exitCode: ctx.EXIT_CODES.OK,
1183
+ envelope: {
1184
+ ok: {
1185
+ action: 'kb.switch',
1186
+ activeKb: result.switchedTo.name,
1187
+ kbPath: result.switchedTo.path
1188
+ },
1189
+ help: [
1190
+ 'Restart your AI client (or run `genexus_lifecycle action=stop-worker` via MCP) so the worker reloads with the new KB.'
1191
+ ]
1192
+ }
1193
+ };
1194
+ }
1195
+
1196
+ return {
1197
+ exitCode: ctx.EXIT_CODES.USAGE,
1198
+ envelope: usageEnvelope(
1199
+ `Unknown kb subcommand '${subcommand}'. Use list, add, remove, or switch.`,
1200
+ ctx.EXIT_CODES.USAGE
1201
+ )
1202
+ };
1203
+ }
1204
+
840
1205
  function commandHelpMap() {
841
1206
  return {
842
1207
  axi: {
@@ -864,8 +1229,31 @@ function commandHelpMap() {
864
1229
  examples: ['genexus-mcp config show', 'genexus-mcp config show --full --format json']
865
1230
  },
866
1231
  init: {
867
- usage: 'genexus-mcp init --kb <path> --gx <path> [--write-clients] [--format ...] OR genexus-mcp init --interactive',
868
- examples: ['genexus-mcp init --kb "C:\\KBs\\MyKB" --gx "C:\\Program Files (x86)\\GeneXus\\GeneXus18"', 'genexus-mcp init --interactive']
1232
+ usage: 'genexus-mcp init [--kb <path>] [--gx <path>] [--write-clients] [--no-smoke] [--warm] [--format ...] OR genexus-mcp init --interactive',
1233
+ examples: [
1234
+ 'genexus-mcp init # zero-config: auto-discovers GX from registry/Program Files and KB from cwd',
1235
+ 'genexus-mcp init --kb "C:\\KBs\\MyKB" --gx "C:\\Program Files (x86)\\GeneXus\\GeneXus18"',
1236
+ 'genexus-mcp init --interactive',
1237
+ 'genexus-mcp init --kb <path> --gx <path> --no-smoke'
1238
+ ]
1239
+ },
1240
+ whoami: {
1241
+ usage: 'genexus-mcp whoami [--format toon|json|text]',
1242
+ examples: ['genexus-mcp whoami', 'genexus-mcp whoami --format json']
1243
+ },
1244
+ uninstall: {
1245
+ usage: 'genexus-mcp uninstall [--yes] [--format toon|json|text]',
1246
+ examples: ['genexus-mcp uninstall', 'genexus-mcp uninstall --yes --format json']
1247
+ },
1248
+ kb: {
1249
+ usage: 'genexus-mcp kb <list|add|remove|switch> [--name <name>] [--kb <path>] [--format ...]',
1250
+ examples: [
1251
+ 'genexus-mcp kb list',
1252
+ 'genexus-mcp kb add --name sales --kb "C:\\KBs\\SalesProd"',
1253
+ 'genexus-mcp kb switch --name sales',
1254
+ 'genexus-mcp kb switch --kb "C:\\KBs\\NewKB" # auto-registers by folder name',
1255
+ 'genexus-mcp kb remove --name sales'
1256
+ ]
869
1257
  },
870
1258
  llm: {
871
1259
  usage: 'genexus-mcp llm help [--full] [--fields f1,f2] [--format toon|json|text]',
@@ -939,7 +1327,7 @@ async function handleHelp(targetCommand, ctx) {
939
1327
  bin: binPath,
940
1328
  command: 'genexus-mcp',
941
1329
  description: 'GeneXus MCP launcher and AXI-oriented utility CLI',
942
- commands: ['home', 'axi home', 'status', 'doctor', 'tools list', 'config show', 'layout status', 'layout run', 'layout inspect', 'init', 'llm help', 'update', 'help'],
1330
+ commands: ['home', 'axi home', 'status', 'doctor', 'tools list', 'config show', 'layout status', 'layout run', 'layout inspect', 'init', 'whoami', 'uninstall', 'kb list', 'kb add', 'kb remove', 'kb switch', 'llm help', 'update', 'help'],
943
1331
  defaults: { format: 'toon', limit: 100 }
944
1332
  },
945
1333
  help: [
@@ -1042,6 +1430,9 @@ module.exports = {
1042
1430
  handleToolsList,
1043
1431
  handleConfigShow,
1044
1432
  handleInit,
1433
+ handleWhoami,
1434
+ handleUninstall,
1435
+ handleKb,
1045
1436
  handleHome,
1046
1437
  handleLlmHelp,
1047
1438
  handleLayout,
package/cli/index.js CHANGED
@@ -15,6 +15,9 @@ const {
15
15
  handleToolsList,
16
16
  handleConfigShow,
17
17
  handleInit,
18
+ handleWhoami,
19
+ handleUninstall,
20
+ handleKb,
18
21
  handleHome,
19
22
  handleLlmHelp,
20
23
  handleLayout,
@@ -37,6 +40,10 @@ const GLOBAL_DEFAULTS = {
37
40
  interactive: false,
38
41
  writeClients: false,
39
42
  mcpSmoke: false,
43
+ noSmoke: false,
44
+ warm: false,
45
+ yes: false,
46
+ name: null,
40
47
  limit: 100,
41
48
  query: null,
42
49
  quiet: false,
@@ -44,7 +51,7 @@ const GLOBAL_DEFAULTS = {
44
51
  help: false
45
52
  };
46
53
 
47
- const KNOWN_COMMANDS = new Set(['status', 'doctor', 'tools', 'config', 'init', 'setup', 'help', 'home', 'axi', 'llm', 'layout', 'update']);
54
+ const KNOWN_COMMANDS = new Set(['status', 'doctor', 'tools', 'config', 'init', 'setup', 'whoami', 'uninstall', 'kb', 'help', 'home', 'axi', 'llm', 'layout', 'update']);
48
55
 
49
56
  function parseArgs(argv) {
50
57
  const result = {
@@ -99,6 +106,11 @@ function parseArgs(argv) {
99
106
  tokens.shift();
100
107
  }
101
108
 
109
+ if (result.command === 'kb' && ['list', 'add', 'remove', 'switch'].includes(tokens[0])) {
110
+ result.subcommand = tokens[0];
111
+ tokens.shift();
112
+ }
113
+
102
114
  if (result.command === 'layout' && (tokens[0] === 'status' || tokens[0] === 'run' || tokens[0] === 'inspect')) {
103
115
  result.subcommand = tokens[0];
104
116
  tokens.shift();
@@ -148,6 +160,12 @@ function parseArgs(argv) {
148
160
  else result.unknownFlags.push('--gx requires a value');
149
161
  break;
150
162
  }
163
+ case 'name': {
164
+ const val = takeValue();
165
+ if (val) result.options.name = val;
166
+ else result.unknownFlags.push('--name requires a value');
167
+ break;
168
+ }
151
169
  case 'limit': {
152
170
  const val = takeValue();
153
171
  if (!val) {
@@ -238,6 +256,15 @@ function parseArgs(argv) {
238
256
  case 'mcp-smoke':
239
257
  result.options.mcpSmoke = true;
240
258
  break;
259
+ case 'no-smoke':
260
+ result.options.noSmoke = true;
261
+ break;
262
+ case 'warm':
263
+ result.options.warm = true;
264
+ break;
265
+ case 'yes':
266
+ result.options.yes = true;
267
+ break;
241
268
  case 'quiet':
242
269
  result.options.quiet = true;
243
270
  break;
@@ -333,6 +360,9 @@ function resolveMetaCommand(parsed, targetHelp) {
333
360
  if (parsed.subcommand === 'inspect') return 'layout.inspect';
334
361
  return 'layout.status';
335
362
  }
363
+ if (parsed.command === 'kb') {
364
+ return parsed.subcommand ? `kb.${parsed.subcommand}` : 'kb';
365
+ }
336
366
  if (parsed.command === 'update') return 'update';
337
367
  return parsed.command || 'unknown';
338
368
  }
@@ -444,6 +474,23 @@ async function main(argv) {
444
474
  case 'init':
445
475
  result = await handleInit(parsed.options, ctx);
446
476
  break;
477
+ case 'whoami':
478
+ result = await handleWhoami(parsed.options, ctx);
479
+ break;
480
+ case 'uninstall':
481
+ result = await handleUninstall(parsed.options, ctx);
482
+ break;
483
+ case 'kb':
484
+ if (!parsed.subcommand || !['list', 'add', 'remove', 'switch'].includes(parsed.subcommand)) {
485
+ writeStructured(
486
+ process.stdout,
487
+ withCommandMeta(usageEnvelope('kb requires subcommand `list`, `add`, `remove`, or `switch`.', EXIT_CODES.USAGE), resolveMetaCommand(parsed)),
488
+ parsed.options.format
489
+ );
490
+ return EXIT_CODES.USAGE;
491
+ }
492
+ result = await handleKb(parsed.subcommand, parsed.options, ctx);
493
+ break;
447
494
  case 'update':
448
495
  result = await handleUpdate(parsed.options, ctx);
449
496
  break;