@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,237 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const net = require('net');
|
|
6
|
+
const log = require('../utils/logger');
|
|
7
|
+
const authManager = require('../core/auth-manager');
|
|
8
|
+
const subscription = require('../core/subscription');
|
|
9
|
+
const configStore = require('../core/config-store');
|
|
10
|
+
const { PythonManager } = require('../utils/python-manager');
|
|
11
|
+
|
|
12
|
+
async function serve(opts = {}) {
|
|
13
|
+
const isTcpMode = !!opts.port;
|
|
14
|
+
|
|
15
|
+
// In stdio mode, redirect all logging to stderr (stdout is for JSON-RPC)
|
|
16
|
+
if (!isTcpMode) {
|
|
17
|
+
log.setStdioMode(true);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (opts.debug) {
|
|
21
|
+
process.env.CREATELEX_DEBUG = 'true';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
log.info('CreatelexGenAI MCP Server starting...');
|
|
25
|
+
|
|
26
|
+
// --- Auth validation ---
|
|
27
|
+
const token = authManager.getToken();
|
|
28
|
+
if (!token) {
|
|
29
|
+
log.error('Not authenticated. Run `createlex login` first.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const tokenValidation = authManager.validateTokenFormat(token);
|
|
34
|
+
if (!tokenValidation.valid) {
|
|
35
|
+
const messages = {
|
|
36
|
+
'token_expired': 'Token expired. Run `createlex login` to re-authenticate.',
|
|
37
|
+
'invalid_token_format': 'Invalid token format. Run `createlex login`.',
|
|
38
|
+
'invalid_token_structure': 'Invalid token. Run `createlex login`.'
|
|
39
|
+
};
|
|
40
|
+
log.error(messages[tokenValidation.reason] || 'Invalid token. Run `createlex login`.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --- Subscription validation ---
|
|
45
|
+
const shouldBypass = process.env.BYPASS_SUBSCRIPTION === 'true';
|
|
46
|
+
|
|
47
|
+
if (!shouldBypass) {
|
|
48
|
+
log.info('Validating subscription...');
|
|
49
|
+
const status = await subscription.getSubscriptionStatus();
|
|
50
|
+
const hasValid = subscription.hasValidSubscription(status);
|
|
51
|
+
|
|
52
|
+
if (!hasValid) {
|
|
53
|
+
if (status.seatError) {
|
|
54
|
+
log.error(`Device seat limit reached: ${status.seatError}`);
|
|
55
|
+
} else {
|
|
56
|
+
log.error('Active subscription required. Visit https://createlex.com to subscribe.');
|
|
57
|
+
}
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
log.success('Subscription validated');
|
|
61
|
+
} else {
|
|
62
|
+
log.warn('Subscription bypass enabled (BYPASS_SUBSCRIPTION=true)');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// --- Python setup ---
|
|
66
|
+
const rootPath = path.resolve(__dirname, '..', '..');
|
|
67
|
+
const pyManager = new PythonManager(rootPath);
|
|
68
|
+
|
|
69
|
+
let pythonCmd;
|
|
70
|
+
try {
|
|
71
|
+
pythonCmd = await pyManager.ensureDependencies();
|
|
72
|
+
} catch (err) {
|
|
73
|
+
log.error(`Python setup failed: ${err.message}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const scriptPath = pyManager.findMCPServerScript();
|
|
78
|
+
if (!scriptPath) {
|
|
79
|
+
log.error('MCP server script not found. Ensure python/mcp_server_stdio.py exists.');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// --- Build environment ---
|
|
84
|
+
const config = configStore.load();
|
|
85
|
+
const apiBaseUrl = config.apiBaseUrl || 'https://api.createlex.com/api';
|
|
86
|
+
const webBaseUrl = config.webBaseUrl || 'https://createlex.com';
|
|
87
|
+
const subscriptionStatus = await subscription.getSubscriptionStatus();
|
|
88
|
+
|
|
89
|
+
const envVars = {
|
|
90
|
+
...process.env,
|
|
91
|
+
AUTH_TOKEN: token,
|
|
92
|
+
API_BASE_URL: apiBaseUrl,
|
|
93
|
+
CREATELEX_BASE_URL: webBaseUrl,
|
|
94
|
+
PYTHONIOENCODING: 'utf-8',
|
|
95
|
+
BYPASS_SUBSCRIPTION: shouldBypass ? 'true' : 'false',
|
|
96
|
+
DEV_MODE: process.env.DEV_MODE || 'false',
|
|
97
|
+
NODE_ENV: process.env.NODE_ENV || 'production',
|
|
98
|
+
BRIDGE_SUBSCRIPTION_VALIDATED: subscription.hasValidSubscription(subscriptionStatus) ? 'true' : 'false',
|
|
99
|
+
UNREAL_PORT: String(config.unrealPort || 9878),
|
|
100
|
+
UNREAL_SOCKET_PORT: String(config.unrealPort || 9878),
|
|
101
|
+
CREATELEX_USER_ID: subscriptionStatus.userId || subscriptionStatus.user_id || '',
|
|
102
|
+
CREATELEX_USER_EMAIL: subscriptionStatus.email || subscriptionStatus.userEmail || '',
|
|
103
|
+
CREATELEX_DEVICE_ID: authManager.generateDeviceId() || '',
|
|
104
|
+
CREATELEX_AUTH_TOKEN: token,
|
|
105
|
+
CREATELEX_API_URL: apiBaseUrl,
|
|
106
|
+
MCP_PLATFORM: 'cli',
|
|
107
|
+
MCP_VERSION: require('../../package.json').version
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (isTcpMode) {
|
|
111
|
+
startTcpMode(pythonCmd, scriptPath, envVars, parseInt(opts.port, 10));
|
|
112
|
+
} else {
|
|
113
|
+
startStdioMode(pythonCmd, scriptPath, envVars);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function startStdioMode(pythonCmd, scriptPath, envVars) {
|
|
118
|
+
log.info(`Spawning MCP server: ${scriptPath}`);
|
|
119
|
+
|
|
120
|
+
const mcpProcess = spawn(pythonCmd, [scriptPath], {
|
|
121
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
122
|
+
env: envVars
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Pipe stdin from parent to Python process
|
|
126
|
+
process.stdin.pipe(mcpProcess.stdin);
|
|
127
|
+
|
|
128
|
+
// Pipe stdout from Python process to parent (JSON-RPC messages)
|
|
129
|
+
mcpProcess.stdout.on('data', (data) => {
|
|
130
|
+
process.stdout.write(data);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Pipe stderr from Python to our stderr for logging
|
|
134
|
+
mcpProcess.stderr.on('data', (data) => {
|
|
135
|
+
process.stderr.write(data);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
mcpProcess.on('exit', (code) => {
|
|
139
|
+
log.info(`MCP server exited (code ${code})`);
|
|
140
|
+
process.exit(code || 0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
mcpProcess.on('error', (err) => {
|
|
144
|
+
log.error(`MCP server error: ${err.message}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Graceful shutdown
|
|
149
|
+
const shutdown = () => {
|
|
150
|
+
mcpProcess.kill('SIGTERM');
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
process.on('SIGINT', shutdown);
|
|
154
|
+
process.on('SIGTERM', shutdown);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function startTcpMode(pythonCmd, scriptPath, envVars, port) {
|
|
158
|
+
log.info(`Starting MCP server in TCP mode on port ${port}`);
|
|
159
|
+
|
|
160
|
+
const mcpProcess = spawn(pythonCmd, [scriptPath], {
|
|
161
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
162
|
+
env: envVars
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
let stdoutBuffer = '';
|
|
166
|
+
|
|
167
|
+
const server = net.createServer((socket) => {
|
|
168
|
+
const clientId = `${socket.remoteAddress}:${socket.remotePort}`;
|
|
169
|
+
log.info(`Client connected: ${clientId}`);
|
|
170
|
+
|
|
171
|
+
socket.on('data', (data) => {
|
|
172
|
+
if (mcpProcess.stdin && !mcpProcess.stdin.destroyed) {
|
|
173
|
+
mcpProcess.stdin.write(data);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
socket.on('close', () => {
|
|
178
|
+
log.info(`Client disconnected: ${clientId}`);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
socket.on('error', (err) => {
|
|
182
|
+
if (err.code !== 'ECONNRESET') {
|
|
183
|
+
log.warn(`Client error: ${err.message}`);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Forward Python stdout to all connected clients
|
|
188
|
+
mcpProcess.stdout.on('data', (data) => {
|
|
189
|
+
const output = data.toString();
|
|
190
|
+
stdoutBuffer += output;
|
|
191
|
+
|
|
192
|
+
let lines = stdoutBuffer.split('\n');
|
|
193
|
+
stdoutBuffer = lines.pop();
|
|
194
|
+
|
|
195
|
+
for (const line of lines) {
|
|
196
|
+
if (line.trim()) {
|
|
197
|
+
try {
|
|
198
|
+
JSON.parse(line.trim());
|
|
199
|
+
socket.write(line + '\n');
|
|
200
|
+
} catch {
|
|
201
|
+
// Non-JSON output, skip
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
mcpProcess.stderr.on('data', (data) => {
|
|
209
|
+
process.stderr.write(data);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
mcpProcess.on('exit', (code) => {
|
|
213
|
+
log.info(`MCP server exited (code ${code})`);
|
|
214
|
+
server.close();
|
|
215
|
+
process.exit(code || 0);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
server.listen(port, '127.0.0.1', () => {
|
|
219
|
+
log.success(`TCP MCP server listening on 127.0.0.1:${port}`);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
server.on('error', (err) => {
|
|
223
|
+
log.error(`TCP server error: ${err.message}`);
|
|
224
|
+
mcpProcess.kill('SIGTERM');
|
|
225
|
+
process.exit(1);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const shutdown = () => {
|
|
229
|
+
server.close();
|
|
230
|
+
mcpProcess.kill('SIGTERM');
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
process.on('SIGINT', shutdown);
|
|
234
|
+
process.on('SIGTERM', shutdown);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = { serve };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../utils/logger');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { IDE_CONFIGS, configureIde, getAvailableIdes } = require('../core/ide-configurator');
|
|
6
|
+
|
|
7
|
+
function setup(ide, opts = {}) {
|
|
8
|
+
if (opts.all) {
|
|
9
|
+
log.header('Configuring all IDEs');
|
|
10
|
+
const ides = getAvailableIdes();
|
|
11
|
+
let configured = 0;
|
|
12
|
+
|
|
13
|
+
for (const ideInfo of ides) {
|
|
14
|
+
const result = configureIde(ideInfo.key);
|
|
15
|
+
if (result.success) {
|
|
16
|
+
if (result.manual) {
|
|
17
|
+
log.info(`${ideInfo.name}: ${result.message}`);
|
|
18
|
+
} else {
|
|
19
|
+
log.success(result.message);
|
|
20
|
+
}
|
|
21
|
+
configured++;
|
|
22
|
+
} else {
|
|
23
|
+
log.warn(`${ideInfo.name}: ${result.error}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
log.blank();
|
|
28
|
+
log.success(`Configured ${configured}/${ides.length} IDEs`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!ide) {
|
|
33
|
+
// Show available IDEs
|
|
34
|
+
log.header('Available IDEs');
|
|
35
|
+
const ides = getAvailableIdes();
|
|
36
|
+
|
|
37
|
+
for (const ideInfo of ides) {
|
|
38
|
+
const status = ideInfo.exists ? chalk.green('available') : chalk.gray('not detected');
|
|
39
|
+
console.log(` ${chalk.bold(ideInfo.key.padEnd(20))} ${ideInfo.name.padEnd(20)} ${status}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
log.blank();
|
|
43
|
+
log.info('Usage: createlex setup <ide-name>');
|
|
44
|
+
log.info(' createlex setup --all');
|
|
45
|
+
log.blank();
|
|
46
|
+
log.info('Example: createlex setup claude-code');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Configure specific IDE
|
|
51
|
+
const result = configureIde(ide);
|
|
52
|
+
if (result.success) {
|
|
53
|
+
if (result.manual) {
|
|
54
|
+
log.header(`Setup: ${IDE_CONFIGS[ide]?.name || ide}`);
|
|
55
|
+
log.info(result.message);
|
|
56
|
+
} else {
|
|
57
|
+
log.success(result.message);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
log.error(result.error);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { setup };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../utils/logger');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const authManager = require('../core/auth-manager');
|
|
6
|
+
const subscription = require('../core/subscription');
|
|
7
|
+
const configStore = require('../core/config-store');
|
|
8
|
+
const { detectBackends, BACKEND } = require('../core/unreal-connection');
|
|
9
|
+
|
|
10
|
+
async function status() {
|
|
11
|
+
const config = configStore.load();
|
|
12
|
+
|
|
13
|
+
log.header('CreatelexGenAI Status');
|
|
14
|
+
|
|
15
|
+
// Auth status
|
|
16
|
+
const token = authManager.getToken();
|
|
17
|
+
if (token) {
|
|
18
|
+
const validation = authManager.validateTokenFormat(token);
|
|
19
|
+
if (validation.valid) {
|
|
20
|
+
log.success('Authentication: Logged in');
|
|
21
|
+
if (validation.payload?.email) {
|
|
22
|
+
log.keyValue(' Email', validation.payload.email);
|
|
23
|
+
}
|
|
24
|
+
if (validation.payload?.exp) {
|
|
25
|
+
const expiry = new Date(validation.payload.exp * 1000);
|
|
26
|
+
log.keyValue(' Expires', expiry.toLocaleString());
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
log.warn(`Authentication: Token ${validation.reason}`);
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
log.error('Authentication: Not logged in');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
log.blank();
|
|
36
|
+
|
|
37
|
+
// Subscription status
|
|
38
|
+
if (token) {
|
|
39
|
+
log.info('Checking subscription...');
|
|
40
|
+
const subStatus = await subscription.getSubscriptionStatus();
|
|
41
|
+
const hasValid = subscription.hasValidSubscription(subStatus);
|
|
42
|
+
|
|
43
|
+
if (hasValid) {
|
|
44
|
+
log.success('Subscription: Active');
|
|
45
|
+
if (subStatus.plan) {
|
|
46
|
+
log.keyValue(' Plan', subStatus.plan);
|
|
47
|
+
}
|
|
48
|
+
if (subStatus.bypass) {
|
|
49
|
+
log.warn(' (Bypass mode)');
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
log.error('Subscription: Inactive');
|
|
53
|
+
if (subStatus.seatError) {
|
|
54
|
+
log.keyValue(' Reason', subStatus.seatError);
|
|
55
|
+
} else if (subStatus.error) {
|
|
56
|
+
log.keyValue(' Reason', subStatus.error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
log.blank();
|
|
62
|
+
|
|
63
|
+
// Connection backends
|
|
64
|
+
log.info('Scanning Unreal Engine connections...');
|
|
65
|
+
const backends = await detectBackends({
|
|
66
|
+
pluginPort: config.unrealPort || 9878
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
log.blank();
|
|
70
|
+
|
|
71
|
+
const pluginStatus = backends[BACKEND.PLUGIN];
|
|
72
|
+
const webStatus = backends[BACKEND.WEB_REMOTE];
|
|
73
|
+
const remoteStatus = backends[BACKEND.REMOTE_EXEC];
|
|
74
|
+
|
|
75
|
+
// Plugin
|
|
76
|
+
if (pluginStatus.connected) {
|
|
77
|
+
log.success(`Plugin (TCP ${config.unrealPort || 9878}): ${chalk.green('Connected')}`);
|
|
78
|
+
} else {
|
|
79
|
+
log.keyValue(`Plugin (TCP ${config.unrealPort || 9878})`, chalk.gray('Not connected'));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Web Remote Control
|
|
83
|
+
if (webStatus.connected) {
|
|
84
|
+
log.success(`Web Remote Control (HTTP ${webStatus.port || 30010}): ${chalk.green('Connected')}`);
|
|
85
|
+
} else {
|
|
86
|
+
log.keyValue(`Web Remote Control (HTTP 30010)`, chalk.gray('Not available'));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Python Remote Execution
|
|
90
|
+
if (remoteStatus.connected) {
|
|
91
|
+
log.success(`Remote Execution (UDP 6766): ${chalk.green('Available')}`);
|
|
92
|
+
if (remoteStatus.nodes) {
|
|
93
|
+
log.keyValue(' Nodes', remoteStatus.nodes.length);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
log.keyValue(`Remote Execution (UDP 6766)`, chalk.gray('Not available'));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
log.blank();
|
|
100
|
+
|
|
101
|
+
// Summary
|
|
102
|
+
const connectedCount = [pluginStatus, webStatus, remoteStatus].filter(b => b.connected).length;
|
|
103
|
+
if (connectedCount > 0) {
|
|
104
|
+
log.success(`${connectedCount} backend(s) available`);
|
|
105
|
+
if (!pluginStatus.connected) {
|
|
106
|
+
log.info('Tip: Install the CreatelexGenAI plugin for full 71+ tool access.');
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
log.warn('No backends connected to Unreal Engine.');
|
|
110
|
+
log.info('Run `createlex connect` for setup instructions.');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
log.blank();
|
|
114
|
+
|
|
115
|
+
// Config summary
|
|
116
|
+
log.keyValue('API URL', config.apiBaseUrl);
|
|
117
|
+
log.keyValue('UE Port', config.unrealPort);
|
|
118
|
+
log.keyValue('MCP Port', config.mcpPort);
|
|
119
|
+
|
|
120
|
+
const deviceInfo = authManager.getDeviceInfo();
|
|
121
|
+
log.blank();
|
|
122
|
+
log.keyValue('Device', deviceInfo.deviceName);
|
|
123
|
+
log.keyValue('Platform', deviceInfo.platform);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = { status };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../utils/logger');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
|
|
6
|
+
// Tools catalog — extracted from mcp_server_stdio.py
|
|
7
|
+
const TOOLS = [
|
|
8
|
+
// General
|
|
9
|
+
{ name: 'how_to_use', category: 'General', description: 'Show usage instructions' },
|
|
10
|
+
{ name: 'handshake_test', category: 'General', description: 'Test connection with a message echo' },
|
|
11
|
+
|
|
12
|
+
// Script Execution
|
|
13
|
+
{ name: 'execute_python_script', category: 'Script', description: 'Execute a Python script in Unreal Engine' },
|
|
14
|
+
{ name: 'execute_unreal_command', category: 'Script', description: 'Execute an Unreal console command' },
|
|
15
|
+
|
|
16
|
+
// Actor Management
|
|
17
|
+
{ name: 'spawn_object', category: 'Actors', description: 'Spawn an actor in the scene' },
|
|
18
|
+
{ name: 'get_all_scene_objects', category: 'Actors', description: 'List all actors in the current scene' },
|
|
19
|
+
{ name: 'find_actors_by_name', category: 'Actors', description: 'Find actors matching a name pattern' },
|
|
20
|
+
{ name: 'delete_actor', category: 'Actors', description: 'Delete an actor from the scene' },
|
|
21
|
+
{ name: 'set_actor_transform', category: 'Actors', description: 'Set actor location/rotation/scale' },
|
|
22
|
+
|
|
23
|
+
// Blueprint Core
|
|
24
|
+
{ name: 'create_blueprint', category: 'Blueprint', description: 'Create a new Blueprint asset' },
|
|
25
|
+
{ name: 'add_component_to_blueprint', category: 'Blueprint', description: 'Add a component to a Blueprint' },
|
|
26
|
+
{ name: 'add_variable_to_blueprint', category: 'Blueprint', description: 'Add a variable to a Blueprint' },
|
|
27
|
+
{ name: 'add_function_to_blueprint', category: 'Blueprint', description: 'Add a function to a Blueprint' },
|
|
28
|
+
{ name: 'compile_blueprint', category: 'Blueprint', description: 'Compile a Blueprint' },
|
|
29
|
+
{ name: 'spawn_blueprint_actor', category: 'Blueprint', description: 'Spawn a Blueprint actor in the scene' },
|
|
30
|
+
{ name: 'add_component_with_events', category: 'Blueprint', description: 'Add component with event bindings' },
|
|
31
|
+
{ name: 'get_blueprint_context', category: 'Blueprint', description: 'Get Blueprint editor state and context' },
|
|
32
|
+
{ name: 'edit_component_property', category: 'Blueprint', description: 'Edit a component property value' },
|
|
33
|
+
|
|
34
|
+
// Blueprint Nodes
|
|
35
|
+
{ name: 'add_node_to_blueprint', category: 'Blueprint Nodes', description: 'Add a node to a Blueprint graph' },
|
|
36
|
+
{ name: 'delete_node_from_blueprint', category: 'Blueprint Nodes', description: 'Delete a node from a graph' },
|
|
37
|
+
{ name: 'get_all_nodes_in_graph', category: 'Blueprint Nodes', description: 'List all nodes in a graph' },
|
|
38
|
+
{ name: 'connect_blueprint_nodes', category: 'Blueprint Nodes', description: 'Connect two Blueprint nodes' },
|
|
39
|
+
{ name: 'connect_blueprint_nodes_bulk', category: 'Blueprint Nodes', description: 'Connect multiple node pairs at once' },
|
|
40
|
+
{ name: 'get_blueprint_node_guid', category: 'Blueprint Nodes', description: 'Get the GUID of a Blueprint node' },
|
|
41
|
+
{ name: 'get_node_suggestions', category: 'Blueprint Nodes', description: 'Get suggested nodes for a type' },
|
|
42
|
+
{ name: 'discover_available_blueprint_nodes', category: 'Blueprint Nodes', description: 'Discover available Blueprint node types' },
|
|
43
|
+
{ name: 'get_node_alternatives', category: 'Blueprint Nodes', description: 'Get alternatives for a failed node' },
|
|
44
|
+
{ name: 'smart_add_node_with_discovery', category: 'Blueprint Nodes', description: 'Add a node with auto-discovery fallback' },
|
|
45
|
+
{ name: 'set_node_pin_default_value', category: 'Blueprint Nodes', description: 'Set a node pin default value' },
|
|
46
|
+
{ name: 'get_node_pin_info', category: 'Blueprint Nodes', description: 'Get pin information for a node' },
|
|
47
|
+
|
|
48
|
+
// Materials
|
|
49
|
+
{ name: 'create_material', category: 'Materials', description: 'Create a new material with a color' },
|
|
50
|
+
{ name: 'get_actor_material_info', category: 'Materials', description: 'Get material info for an actor' },
|
|
51
|
+
{ name: 'get_available_materials', category: 'Materials', description: 'List available materials in a folder' },
|
|
52
|
+
{ name: 'apply_material_to_actor', category: 'Materials', description: 'Apply a material to an actor' },
|
|
53
|
+
{ name: 'apply_material_to_blueprint', category: 'Materials', description: 'Apply a material to a Blueprint component' },
|
|
54
|
+
{ name: 'set_mesh_material_color', category: 'Materials', description: 'Set material color property' },
|
|
55
|
+
|
|
56
|
+
// Static Mesh
|
|
57
|
+
{ name: 'set_static_mesh_properties', category: 'Mesh', description: 'Set static mesh component properties' },
|
|
58
|
+
|
|
59
|
+
// Physics
|
|
60
|
+
{ name: 'spawn_physics_blueprint_actor', category: 'Physics', description: 'Spawn a Blueprint actor with physics' },
|
|
61
|
+
{ name: 'set_physics_properties', category: 'Physics', description: 'Set physics properties on an actor' },
|
|
62
|
+
|
|
63
|
+
// UI / Widgets
|
|
64
|
+
{ name: 'create_user_widget', category: 'UI', description: 'Create a new User Widget (UMG)' },
|
|
65
|
+
{ name: 'add_widget_to_user_widget', category: 'UI', description: 'Add a child widget' },
|
|
66
|
+
{ name: 'edit_widget_property', category: 'UI', description: 'Edit a widget property' },
|
|
67
|
+
{ name: 'get_widget_hierarchy', category: 'UI', description: 'Get the widget tree hierarchy' },
|
|
68
|
+
{ name: 'get_widget_properties', category: 'UI', description: 'Get widget property values' },
|
|
69
|
+
{ name: 'remove_widget_from_user_widget', category: 'UI', description: 'Remove a widget from the tree' },
|
|
70
|
+
{ name: 'add_widgets_bulk', category: 'UI', description: 'Add multiple widgets at once' },
|
|
71
|
+
{ name: 'edit_widget_properties_bulk', category: 'UI', description: 'Edit multiple widget properties at once' },
|
|
72
|
+
{ name: 'list_available_widget_types', category: 'UI', description: 'List available widget types' },
|
|
73
|
+
{ name: 'bind_widget_event', category: 'UI', description: 'Bind a widget event' },
|
|
74
|
+
{ name: 'validate_ui_spec', category: 'UI', description: 'Validate a UI specification' },
|
|
75
|
+
{ name: 'generate_widget_blueprint_from_spec', category: 'UI', description: 'Generate widgets from a UI spec' },
|
|
76
|
+
{ name: 'export_widget_to_ui_spec', category: 'UI', description: 'Export a widget tree to a UI spec' },
|
|
77
|
+
|
|
78
|
+
// UI Slicing
|
|
79
|
+
{ name: 'auto_slice_ui_mockup', category: 'UI Slicing', description: 'Slice a UI mockup image into components' },
|
|
80
|
+
{ name: 'apply_ui_slices_to_widget', category: 'UI Slicing', description: 'Apply sliced UI images to widgets' },
|
|
81
|
+
{ name: 'auto_slice_and_apply_ui_mockup', category: 'UI Slicing', description: 'Slice and apply a UI mockup end-to-end' },
|
|
82
|
+
|
|
83
|
+
// Project / Files
|
|
84
|
+
{ name: 'create_project_folder', category: 'Project', description: 'Create a folder in the content browser' },
|
|
85
|
+
{ name: 'get_files_in_folder', category: 'Project', description: 'List files in a content folder' },
|
|
86
|
+
{ name: 'create_game_mode', category: 'Project', description: 'Create a new Game Mode' },
|
|
87
|
+
{ name: 'add_input_binding', category: 'Project', description: 'Add an input action binding' },
|
|
88
|
+
|
|
89
|
+
// Architecture / Prefabs
|
|
90
|
+
{ name: 'create_town', category: 'Architecture', description: 'Generate a procedural town' },
|
|
91
|
+
{ name: 'construct_house', category: 'Architecture', description: 'Construct a house structure' },
|
|
92
|
+
{ name: 'construct_mansion', category: 'Architecture', description: 'Construct a mansion structure' },
|
|
93
|
+
{ name: 'create_tower', category: 'Architecture', description: 'Create a tower structure' },
|
|
94
|
+
{ name: 'create_arch', category: 'Architecture', description: 'Create an arch structure' },
|
|
95
|
+
{ name: 'create_staircase', category: 'Architecture', description: 'Create a staircase' },
|
|
96
|
+
{ name: 'create_castle_fortress', category: 'Architecture', description: 'Create a castle/fortress' },
|
|
97
|
+
{ name: 'create_suspension_bridge', category: 'Architecture', description: 'Create a suspension bridge' },
|
|
98
|
+
{ name: 'create_aqueduct', category: 'Architecture', description: 'Create an aqueduct' },
|
|
99
|
+
{ name: 'create_maze', category: 'Architecture', description: 'Create a procedural maze' },
|
|
100
|
+
{ name: 'create_pyramid', category: 'Architecture', description: 'Create a pyramid' },
|
|
101
|
+
{ name: 'create_wall', category: 'Architecture', description: 'Create a wall segment' },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
function tools(opts = {}) {
|
|
105
|
+
if (opts.json) {
|
|
106
|
+
console.log(JSON.stringify(TOOLS, null, 2));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
log.header(`CreatelexGenAI Tools (${TOOLS.length})`);
|
|
111
|
+
|
|
112
|
+
// Group by category
|
|
113
|
+
const grouped = {};
|
|
114
|
+
for (const tool of TOOLS) {
|
|
115
|
+
if (!grouped[tool.category]) {
|
|
116
|
+
grouped[tool.category] = [];
|
|
117
|
+
}
|
|
118
|
+
grouped[tool.category].push(tool);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const [category, categoryTools] of Object.entries(grouped)) {
|
|
122
|
+
log.blank();
|
|
123
|
+
console.log(chalk.bold.yellow(` ${category}`));
|
|
124
|
+
for (const t of categoryTools) {
|
|
125
|
+
console.log(` ${chalk.green(t.name.padEnd(40))} ${chalk.gray(t.description)}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
log.blank();
|
|
130
|
+
log.info(`Use: createlex exec <tool_name> --param value`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = { tools, TOOLS };
|