@createlex/createlexgenai 1.0.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/README.md +272 -0
- package/bin/createlex.js +5 -0
- package/package.json +45 -0
- package/python/activity_tracker.py +280 -0
- package/python/fastmcp.py +768 -0
- package/python/mcp_server_stdio.py +4720 -0
- package/python/requirements.txt +7 -0
- package/python/subscription_validator.py +199 -0
- package/python/ue_native_handler.py +573 -0
- package/python/ui_slice_host.py +637 -0
- package/src/cli.js +109 -0
- package/src/commands/config.js +56 -0
- package/src/commands/connect.js +100 -0
- package/src/commands/exec.js +148 -0
- package/src/commands/login.js +111 -0
- package/src/commands/logout.js +17 -0
- package/src/commands/serve.js +237 -0
- package/src/commands/setup.js +65 -0
- package/src/commands/status.js +126 -0
- package/src/commands/tools.js +133 -0
- package/src/core/auth-manager.js +147 -0
- package/src/core/config-store.js +81 -0
- package/src/core/discovery.js +71 -0
- package/src/core/ide-configurator.js +189 -0
- package/src/core/remote-execution.js +228 -0
- package/src/core/subscription.js +176 -0
- package/src/core/unreal-connection.js +318 -0
- package/src/core/web-remote-control.js +243 -0
- package/src/utils/logger.js +66 -0
- package/src/utils/python-manager.js +142 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const axios = require('axios');
|
|
4
|
+
const configStore = require('./config-store');
|
|
5
|
+
const authManager = require('./auth-manager');
|
|
6
|
+
|
|
7
|
+
// In-memory subscription cache (5-minute TTL)
|
|
8
|
+
let subscriptionCache = null;
|
|
9
|
+
let subscriptionCacheTimestamp = null;
|
|
10
|
+
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
11
|
+
|
|
12
|
+
function isCacheValid() {
|
|
13
|
+
if (!subscriptionCache || !subscriptionCacheTimestamp) return false;
|
|
14
|
+
return (Date.now() - subscriptionCacheTimestamp) < CACHE_TTL_MS;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function clearCache() {
|
|
18
|
+
subscriptionCache = null;
|
|
19
|
+
subscriptionCacheTimestamp = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getApiBaseUrl() {
|
|
23
|
+
return configStore.get('apiBaseUrl') || 'https://api.createlex.com/api';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function makeApiRequest(endpoint, options = {}) {
|
|
27
|
+
const { method = 'GET', data, headers = {}, timeout = 15000 } = options;
|
|
28
|
+
const baseUrl = getApiBaseUrl();
|
|
29
|
+
const normalizedBase = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
30
|
+
const relativeEndpoint = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint;
|
|
31
|
+
const url = `${normalizedBase}${relativeEndpoint}`;
|
|
32
|
+
|
|
33
|
+
const response = await axios({
|
|
34
|
+
method,
|
|
35
|
+
url,
|
|
36
|
+
data,
|
|
37
|
+
timeout,
|
|
38
|
+
headers: {
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
...headers
|
|
41
|
+
},
|
|
42
|
+
validateStatus: status => status >= 200 && status < 500
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (response.status >= 200 && response.status < 300) {
|
|
46
|
+
return response;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const err = new Error(`HTTP ${response.status}: ${JSON.stringify(response.data)}`);
|
|
50
|
+
err.status = response.status;
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function getSubscriptionStatus() {
|
|
55
|
+
const token = authManager.getToken();
|
|
56
|
+
if (!token) {
|
|
57
|
+
clearCache();
|
|
58
|
+
return { hasActiveSubscription: false, error: 'No authentication token' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const validation = authManager.validateTokenFormat(token);
|
|
62
|
+
if (!validation.valid) {
|
|
63
|
+
clearCache();
|
|
64
|
+
return { hasActiveSubscription: false, error: validation.reason };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Return cached if valid
|
|
68
|
+
if (isCacheValid()) {
|
|
69
|
+
return subscriptionCache;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const response = await makeApiRequest('subscription/status', {
|
|
74
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const data = response.data || {};
|
|
78
|
+
|
|
79
|
+
const hasActiveSubscription = data.hasActiveSubscription === true ||
|
|
80
|
+
data.subscriptionStatus === 'active' ||
|
|
81
|
+
data.hasSubscription === true;
|
|
82
|
+
|
|
83
|
+
// Device seat check
|
|
84
|
+
if (hasActiveSubscription && data.userId) {
|
|
85
|
+
try {
|
|
86
|
+
const deviceInfo = authManager.getDeviceInfo();
|
|
87
|
+
const seatResponse = await makeApiRequest('device/check-seat', {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
90
|
+
data: {
|
|
91
|
+
deviceInfo,
|
|
92
|
+
subscriptionPlan: data.plan,
|
|
93
|
+
userId: data.userId
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
data.seatStatus = seatResponse.data;
|
|
97
|
+
|
|
98
|
+
if (seatResponse.data?.canUse === false) {
|
|
99
|
+
data.hasActiveSubscription = false;
|
|
100
|
+
data.seatError = seatResponse.data.error;
|
|
101
|
+
}
|
|
102
|
+
} catch (seatErr) {
|
|
103
|
+
data.seatStatus = { canUse: true, note: 'Seat check skipped: ' + seatErr.message };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
data.deviceId = authManager.generateDeviceId();
|
|
108
|
+
data.deviceInfo = authManager.getDeviceInfo();
|
|
109
|
+
|
|
110
|
+
subscriptionCache = data;
|
|
111
|
+
subscriptionCacheTimestamp = Date.now();
|
|
112
|
+
return data;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
// Bypass on 404/403/401 or explicit bypass
|
|
115
|
+
if (process.env.BYPASS_SUBSCRIPTION === 'true' ||
|
|
116
|
+
error.status === 404 || error.status === 403 || error.status === 401) {
|
|
117
|
+
const bypassData = {
|
|
118
|
+
hasActiveSubscription: true,
|
|
119
|
+
bypass: true,
|
|
120
|
+
error: error.message
|
|
121
|
+
};
|
|
122
|
+
subscriptionCache = bypassData;
|
|
123
|
+
subscriptionCacheTimestamp = Date.now();
|
|
124
|
+
return bypassData;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
clearCache();
|
|
128
|
+
return { hasActiveSubscription: false, error: error.message };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function hasValidSubscription(status) {
|
|
133
|
+
if (!status) return false;
|
|
134
|
+
|
|
135
|
+
const hasSub = status.hasActiveSubscription === true ||
|
|
136
|
+
(status.subscriptionStatus && status.subscriptionStatus.toLowerCase() === 'active') ||
|
|
137
|
+
status.hasSubscription === true;
|
|
138
|
+
|
|
139
|
+
if (!hasSub) return false;
|
|
140
|
+
|
|
141
|
+
if (status.seatStatus && status.seatStatus.canUse === false) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function sendDeviceHeartbeat() {
|
|
149
|
+
const token = authManager.getToken();
|
|
150
|
+
const deviceId = authManager.generateDeviceId();
|
|
151
|
+
if (!token || !deviceId) return { success: false };
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const response = await makeApiRequest('device/heartbeat', {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
157
|
+
data: {
|
|
158
|
+
deviceId,
|
|
159
|
+
deviceInfo: authManager.getDeviceInfo()
|
|
160
|
+
},
|
|
161
|
+
timeout: 10000
|
|
162
|
+
});
|
|
163
|
+
return { success: true, data: response.data };
|
|
164
|
+
} catch (error) {
|
|
165
|
+
return { success: false, error: error.message };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = {
|
|
170
|
+
getSubscriptionStatus,
|
|
171
|
+
hasValidSubscription,
|
|
172
|
+
sendDeviceHeartbeat,
|
|
173
|
+
clearCache,
|
|
174
|
+
getApiBaseUrl,
|
|
175
|
+
makeApiRequest
|
|
176
|
+
};
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const net = require('net');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_PORT = 9878;
|
|
6
|
+
const CONNECT_TIMEOUT = 5000;
|
|
7
|
+
const RECEIVE_TIMEOUT = 30000;
|
|
8
|
+
|
|
9
|
+
// Backend identifiers
|
|
10
|
+
const BACKEND = {
|
|
11
|
+
PLUGIN: 'plugin', // CreatelexGenAI plugin TCP socket (port 9878)
|
|
12
|
+
REMOTE_EXEC: 'remote-exec', // UE Python Remote Execution (UDP 6766 + TCP)
|
|
13
|
+
WEB_REMOTE: 'web-remote' // UE Web Remote Control HTTP API (port 30010)
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Send a JSON command to Unreal Engine's socket server and return the response.
|
|
18
|
+
* This is the CreatelexGenAI plugin backend (port 9878).
|
|
19
|
+
*/
|
|
20
|
+
function sendCommand(command, options = {}) {
|
|
21
|
+
const port = options.port || DEFAULT_PORT;
|
|
22
|
+
const host = options.host || '127.0.0.1';
|
|
23
|
+
const timeout = options.timeout || RECEIVE_TIMEOUT;
|
|
24
|
+
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const socket = new net.Socket();
|
|
27
|
+
let responseBuffer = '';
|
|
28
|
+
|
|
29
|
+
socket.setTimeout(CONNECT_TIMEOUT);
|
|
30
|
+
|
|
31
|
+
socket.connect(port, host, () => {
|
|
32
|
+
socket.setTimeout(timeout);
|
|
33
|
+
const payload = typeof command === 'string' ? command : JSON.stringify(command);
|
|
34
|
+
socket.write(payload);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
socket.on('data', (data) => {
|
|
38
|
+
responseBuffer += data.toString();
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(responseBuffer);
|
|
41
|
+
socket.destroy();
|
|
42
|
+
resolve(parsed);
|
|
43
|
+
} catch {
|
|
44
|
+
// Keep buffering
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
socket.on('timeout', () => {
|
|
49
|
+
socket.destroy();
|
|
50
|
+
reject(new Error(`Connection timed out (port ${port})`));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
socket.on('error', (err) => {
|
|
54
|
+
socket.destroy();
|
|
55
|
+
if (err.code === 'ECONNREFUSED') {
|
|
56
|
+
reject(new Error(`Cannot connect to Unreal Engine on port ${port}. Is the editor running with the CreatelexGenAI plugin?`));
|
|
57
|
+
} else {
|
|
58
|
+
reject(new Error(`Socket error: ${err.message}`));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
socket.on('close', () => {
|
|
63
|
+
if (responseBuffer.trim()) {
|
|
64
|
+
try {
|
|
65
|
+
resolve(JSON.parse(responseBuffer));
|
|
66
|
+
} catch {
|
|
67
|
+
resolve({ raw: responseBuffer.trim() });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Test if Unreal Engine is reachable on the given port (plugin backend).
|
|
76
|
+
*/
|
|
77
|
+
async function testConnection(port = DEFAULT_PORT) {
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
const socket = new net.Socket();
|
|
80
|
+
socket.setTimeout(CONNECT_TIMEOUT);
|
|
81
|
+
|
|
82
|
+
socket.connect(port, '127.0.0.1', () => {
|
|
83
|
+
socket.destroy();
|
|
84
|
+
resolve({ connected: true, port, backend: BACKEND.PLUGIN });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
socket.on('timeout', () => {
|
|
88
|
+
socket.destroy();
|
|
89
|
+
resolve({ connected: false, port, error: 'Connection timed out' });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
socket.on('error', (err) => {
|
|
93
|
+
socket.destroy();
|
|
94
|
+
resolve({ connected: false, port, error: err.message });
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Detect all available backends and return their status.
|
|
101
|
+
*/
|
|
102
|
+
async function detectBackends(options = {}) {
|
|
103
|
+
const remoteExec = require('./remote-execution');
|
|
104
|
+
const webRemote = require('./web-remote-control');
|
|
105
|
+
|
|
106
|
+
const results = {};
|
|
107
|
+
|
|
108
|
+
// Check all three in parallel
|
|
109
|
+
const [pluginResult, remoteExecResult, webRemoteResult] = await Promise.all([
|
|
110
|
+
testConnection(options.pluginPort || DEFAULT_PORT),
|
|
111
|
+
remoteExec.testConnection(),
|
|
112
|
+
webRemote.testConnection({ port: options.webRemotePort })
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
results[BACKEND.PLUGIN] = {
|
|
116
|
+
name: 'CreatelexGenAI Plugin (TCP)',
|
|
117
|
+
...pluginResult
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
results[BACKEND.REMOTE_EXEC] = {
|
|
121
|
+
name: 'Python Remote Execution (UDP/TCP)',
|
|
122
|
+
connected: remoteExecResult.available,
|
|
123
|
+
...remoteExecResult
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
results[BACKEND.WEB_REMOTE] = {
|
|
127
|
+
name: 'Web Remote Control (HTTP)',
|
|
128
|
+
connected: webRemoteResult.available,
|
|
129
|
+
...webRemoteResult
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return results;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Auto-select the best available backend.
|
|
137
|
+
* Priority: Plugin > Web Remote Control > Python Remote Execution
|
|
138
|
+
*/
|
|
139
|
+
async function autoSelectBackend(options = {}) {
|
|
140
|
+
const backends = await detectBackends(options);
|
|
141
|
+
|
|
142
|
+
if (backends[BACKEND.PLUGIN].connected) {
|
|
143
|
+
return { backend: BACKEND.PLUGIN, info: backends[BACKEND.PLUGIN] };
|
|
144
|
+
}
|
|
145
|
+
if (backends[BACKEND.WEB_REMOTE].connected) {
|
|
146
|
+
return { backend: BACKEND.WEB_REMOTE, info: backends[BACKEND.WEB_REMOTE] };
|
|
147
|
+
}
|
|
148
|
+
if (backends[BACKEND.REMOTE_EXEC].connected) {
|
|
149
|
+
return { backend: BACKEND.REMOTE_EXEC, info: backends[BACKEND.REMOTE_EXEC] };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { backend: null, info: null, backends };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Execute a tool/command using the specified or auto-detected backend.
|
|
157
|
+
*/
|
|
158
|
+
async function executeWithBackend(toolName, params = {}, options = {}) {
|
|
159
|
+
const backend = options.backend || null;
|
|
160
|
+
|
|
161
|
+
if (backend === BACKEND.PLUGIN || (!backend && options.port)) {
|
|
162
|
+
// Plugin backend — send JSON-RPC to port 9878
|
|
163
|
+
const command = {
|
|
164
|
+
jsonrpc: '2.0',
|
|
165
|
+
id: 1,
|
|
166
|
+
method: 'tools/call',
|
|
167
|
+
params: { name: toolName, arguments: params }
|
|
168
|
+
};
|
|
169
|
+
return sendCommand(command, options);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (backend === BACKEND.WEB_REMOTE) {
|
|
173
|
+
// Web Remote Control — translate tool calls to HTTP API calls
|
|
174
|
+
const webRemote = require('./web-remote-control');
|
|
175
|
+
return executeViaWebRemote(webRemote, toolName, params, options);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (backend === BACKEND.REMOTE_EXEC) {
|
|
179
|
+
// Python Remote Execution — generate Python code and execute
|
|
180
|
+
const remoteExec = require('./remote-execution');
|
|
181
|
+
return executeViaRemoteExec(remoteExec, toolName, params, options);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Auto-detect
|
|
185
|
+
const selected = await autoSelectBackend(options);
|
|
186
|
+
if (!selected.backend) {
|
|
187
|
+
throw new Error('No Unreal Engine connection available. See `createlex connect` for details.');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return executeWithBackend(toolName, params, { ...options, backend: selected.backend });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Execute a tool via Web Remote Control by translating to appropriate API calls.
|
|
195
|
+
*/
|
|
196
|
+
async function executeViaWebRemote(webRemote, toolName, params, options) {
|
|
197
|
+
// Map common tool names to Web Remote Control API calls
|
|
198
|
+
switch (toolName) {
|
|
199
|
+
case 'get_all_scene_objects':
|
|
200
|
+
return webRemote.getAllLevelActors(options);
|
|
201
|
+
|
|
202
|
+
case 'execute_python_script':
|
|
203
|
+
return webRemote.executePythonScript(params.script, options);
|
|
204
|
+
|
|
205
|
+
case 'execute_unreal_command':
|
|
206
|
+
return webRemote.executeConsoleCommand(params.command, options);
|
|
207
|
+
|
|
208
|
+
default: {
|
|
209
|
+
// For tools that don't have a direct mapping, try to execute via Python
|
|
210
|
+
const script = buildPythonForTool(toolName, params);
|
|
211
|
+
if (script) {
|
|
212
|
+
return webRemote.executePythonScript(script, options);
|
|
213
|
+
}
|
|
214
|
+
throw new Error(`Tool '${toolName}' is not supported via Web Remote Control. Use the CreatelexGenAI plugin for full tool access.`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Execute a tool via Python Remote Execution by generating Python code.
|
|
221
|
+
*/
|
|
222
|
+
async function executeViaRemoteExec(remoteExec, toolName, params, options) {
|
|
223
|
+
switch (toolName) {
|
|
224
|
+
case 'execute_python_script':
|
|
225
|
+
return remoteExec.executeCommand(params.script, {
|
|
226
|
+
execMode: 'ExecuteStatement',
|
|
227
|
+
...options
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
case 'execute_unreal_command': {
|
|
231
|
+
const pyCode = `import unreal; unreal.SystemLibrary.execute_console_command(None, "${params.command.replace(/"/g, '\\"')}")`;
|
|
232
|
+
return remoteExec.executeCommand(pyCode, {
|
|
233
|
+
execMode: 'ExecuteStatement',
|
|
234
|
+
...options
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
case 'get_all_scene_objects': {
|
|
239
|
+
const pyCode = `
|
|
240
|
+
import unreal, json
|
|
241
|
+
actors = unreal.EditorLevelLibrary.get_all_level_actors()
|
|
242
|
+
result = [{"name": a.get_actor_label(), "class": a.get_class().get_name(), "location": [a.get_actor_location().x, a.get_actor_location().y, a.get_actor_location().z]} for a in actors]
|
|
243
|
+
print(json.dumps(result))
|
|
244
|
+
`;
|
|
245
|
+
return remoteExec.executeCommand(pyCode, {
|
|
246
|
+
execMode: 'ExecuteStatement',
|
|
247
|
+
...options
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
case 'spawn_object': {
|
|
252
|
+
const loc = params.location || '[0,0,0]';
|
|
253
|
+
const rot = params.rotation || '[0,0,0]';
|
|
254
|
+
const scale = params.scale || '[1,1,1]';
|
|
255
|
+
const pyCode = `
|
|
256
|
+
import unreal, json
|
|
257
|
+
loc = ${loc}
|
|
258
|
+
rot = ${rot}
|
|
259
|
+
scale = ${scale}
|
|
260
|
+
actor = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.EditorAssetLibrary.load_asset('/Script/Engine.${params.actor_class}').get_class(), unreal.Vector(*loc), unreal.Rotator(*rot))
|
|
261
|
+
if actor:
|
|
262
|
+
actor.set_actor_scale3d(unreal.Vector(*scale))
|
|
263
|
+
${params.actor_label ? `actor.set_actor_label("${params.actor_label}")` : ''}
|
|
264
|
+
print(json.dumps({"success": True, "actor": actor.get_actor_label()}))
|
|
265
|
+
else:
|
|
266
|
+
print(json.dumps({"success": False, "error": "Failed to spawn actor"}))
|
|
267
|
+
`;
|
|
268
|
+
return remoteExec.executeCommand(pyCode, {
|
|
269
|
+
execMode: 'ExecuteStatement',
|
|
270
|
+
...options
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
default: {
|
|
275
|
+
const script = buildPythonForTool(toolName, params);
|
|
276
|
+
if (script) {
|
|
277
|
+
return remoteExec.executeCommand(script, {
|
|
278
|
+
execMode: 'ExecuteStatement',
|
|
279
|
+
...options
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
throw new Error(`Tool '${toolName}' is not supported via Remote Execution. Use the CreatelexGenAI plugin for full tool access.`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Build a generic Python script to execute a tool by name.
|
|
289
|
+
* Falls back to importing from the MCP server module if available.
|
|
290
|
+
*/
|
|
291
|
+
function buildPythonForTool(toolName, params) {
|
|
292
|
+
// Generic approach: serialize params and call the tool function in the MCP server
|
|
293
|
+
// This works if the mcp_server_stdio.py module is importable
|
|
294
|
+
const paramsJson = JSON.stringify(params).replace(/"/g, '\\"');
|
|
295
|
+
|
|
296
|
+
return `
|
|
297
|
+
import json, sys
|
|
298
|
+
try:
|
|
299
|
+
# Try to call the tool function directly
|
|
300
|
+
from mcp_server_stdio import ${toolName}
|
|
301
|
+
result = ${toolName}(**json.loads("${paramsJson}"))
|
|
302
|
+
print(result if isinstance(result, str) else json.dumps(result))
|
|
303
|
+
except ImportError:
|
|
304
|
+
print(json.dumps({"error": "Tool '${toolName}' requires the CreatelexGenAI plugin or MCP server."}))
|
|
305
|
+
except Exception as e:
|
|
306
|
+
print(json.dumps({"error": str(e)}))
|
|
307
|
+
`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
module.exports = {
|
|
311
|
+
BACKEND,
|
|
312
|
+
sendCommand,
|
|
313
|
+
testConnection,
|
|
314
|
+
detectBackends,
|
|
315
|
+
autoSelectBackend,
|
|
316
|
+
executeWithBackend,
|
|
317
|
+
DEFAULT_PORT
|
|
318
|
+
};
|