@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.
@@ -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 };