@createlex/createlexgenai 1.0.5 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@createlex/createlexgenai",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "CLI tool and MCP server for CreatelexGenAI — Unreal Engine AI integration",
5
5
  "bin": {
6
6
  "createlex": "./bin/createlex.js"
@@ -171,14 +171,18 @@ async function executeWithBackend(toolName, params = {}, options = {}) {
171
171
 
172
172
  if (backend === BACKEND.WEB_REMOTE) {
173
173
  // Web Remote Control — translate tool calls to HTTP API calls
174
+ // Don't pass plugin port to web remote; let it use its own default (30010)
174
175
  const webRemote = require('./web-remote-control');
175
- return executeViaWebRemote(webRemote, toolName, params, options);
176
+ const webOpts = { ...options, port: options.webRemotePort || undefined };
177
+ return executeViaWebRemote(webRemote, toolName, params, webOpts);
176
178
  }
177
179
 
178
180
  if (backend === BACKEND.REMOTE_EXEC) {
179
181
  // Python Remote Execution — generate Python code and execute
182
+ // Don't pass plugin port to remote exec; it uses UDP multicast
180
183
  const remoteExec = require('./remote-execution');
181
- return executeViaRemoteExec(remoteExec, toolName, params, options);
184
+ const execOpts = { ...options, port: undefined };
185
+ return executeViaRemoteExec(remoteExec, toolName, params, execOpts);
182
186
  }
183
187
 
184
188
  // Auto-detect
@@ -196,9 +200,6 @@ async function executeWithBackend(toolName, params = {}, options = {}) {
196
200
  async function executeViaWebRemote(webRemote, toolName, params, options) {
197
201
  // Map common tool names to Web Remote Control API calls
198
202
  switch (toolName) {
199
- case 'get_all_scene_objects':
200
- return webRemote.getAllLevelActors(options);
201
-
202
203
  case 'execute_python_script':
203
204
  return webRemote.executePythonScript(params.script, options);
204
205
 
@@ -206,12 +207,10 @@ async function executeViaWebRemote(webRemote, toolName, params, options) {
206
207
  return webRemote.executeConsoleCommand(params.command, options);
207
208
 
208
209
  default: {
209
- // For tools that don't have a direct mapping, try to execute via Python
210
+ // For all tools (including get_all_scene_objects, spawn_object, etc.),
211
+ // generate standalone UE Python and execute via Python subsystem
210
212
  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.`);
213
+ return webRemote.executePythonScript(script, options);
215
214
  }
216
215
  }
217
216
  }
@@ -238,7 +237,7 @@ async function executeViaRemoteExec(remoteExec, toolName, params, options) {
238
237
  case 'get_all_scene_objects': {
239
238
  const pyCode = `
240
239
  import unreal, json
241
- actors = unreal.EditorLevelLibrary.get_all_level_actors()
240
+ actors = unreal.get_editor_subsystem(unreal.EditorActorSubsystem).get_all_level_actors()
242
241
  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
242
  print(json.dumps(result))
244
243
  `;
@@ -257,7 +256,7 @@ import unreal, json
257
256
  loc = ${loc}
258
257
  rot = ${rot}
259
258
  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))
259
+ actor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem).spawn_actor_from_class(unreal.EditorAssetLibrary.load_asset('/Script/Engine.${params.actor_class}').get_class(), unreal.Vector(*loc), unreal.Rotator(*rot))
261
260
  if actor:
262
261
  actor.set_actor_scale3d(unreal.Vector(*scale))
263
262
  ${params.actor_label ? `actor.set_actor_label("${params.actor_label}")` : ''}
@@ -285,26 +284,127 @@ else:
285
284
  }
286
285
 
287
286
  /**
288
- * Build a generic Python script to execute a tool by name.
289
- * Falls back to importing from the MCP server module if available.
287
+ * Build a standalone UE Python script to execute a tool by name.
288
+ * These scripts use only the `unreal` module available inside UE's Python interpreter.
290
289
  */
291
290
  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
- `;
291
+ const paramsJson = JSON.stringify(params);
292
+
293
+ switch (toolName) {
294
+ case 'spawn_object': {
295
+ const actorClass = params.actor_class || 'StaticMeshActor';
296
+ const loc = params.location || '0,0,0';
297
+ const rot = params.rotation || '0,0,0';
298
+ const scale = params.scale || '1,1,1';
299
+ const label = params.actor_label || '';
300
+ const locParts = loc.split(',').map(s => s.trim());
301
+ const rotParts = rot.split(',').map(s => s.trim());
302
+ const scaleParts = scale.split(',').map(s => s.trim());
303
+ return `import unreal, json
304
+ actor = unreal.get_editor_subsystem(unreal.EditorActorSubsystem).spawn_actor_from_class(unreal.${actorClass}, unreal.Vector(${locParts.join(',')}), unreal.Rotator(${rotParts.join(',')}))
305
+ if actor:
306
+ actor.set_actor_scale3d(unreal.Vector(${scaleParts.join(',')}))
307
+ ${label ? `actor.set_actor_label("${label}")` : ''}
308
+ mesh_path = '/Engine/BasicShapes/Cube'
309
+ if hasattr(actor, 'static_mesh_component'):
310
+ mesh = unreal.EditorAssetLibrary.load_asset(mesh_path)
311
+ if mesh:
312
+ actor.static_mesh_component.set_static_mesh(mesh)
313
+ print(json.dumps({"success": True, "actor": actor.get_actor_label(), "class": "${actorClass}"}))
314
+ else:
315
+ print(json.dumps({"success": False, "error": "Failed to spawn ${actorClass}"}))`;
316
+ }
317
+
318
+ case 'delete_actor': {
319
+ const name = params.actor_name || params.name || '';
320
+ return `import unreal, json
321
+ actors = unreal.get_editor_subsystem(unreal.EditorActorSubsystem).get_all_level_actors()
322
+ deleted = []
323
+ for a in actors:
324
+ if a.get_actor_label() == "${name}":
325
+ a.destroy_actor()
326
+ deleted.append("${name}")
327
+ print(json.dumps({"success": len(deleted) > 0, "deleted": deleted}))`;
328
+ }
329
+
330
+ case 'find_actors_by_name': {
331
+ const pattern = params.pattern || params.name || '';
332
+ return `import unreal, json
333
+ actors = unreal.get_editor_subsystem(unreal.EditorActorSubsystem).get_all_level_actors()
334
+ found = []
335
+ for a in actors:
336
+ label = a.get_actor_label()
337
+ if "${pattern}".lower() in label.lower():
338
+ loc = a.get_actor_location()
339
+ found.append({"name": label, "class": a.get_class().get_name(), "location": [loc.x, loc.y, loc.z]})
340
+ print(json.dumps({"success": True, "actors": found, "count": len(found)}))`;
341
+ }
342
+
343
+ case 'set_actor_transform': {
344
+ const name = params.actor_name || params.name || '';
345
+ const loc = params.location || '';
346
+ const rot = params.rotation || '';
347
+ const scale = params.scale || '';
348
+ return `import unreal, json
349
+ actors = unreal.get_editor_subsystem(unreal.EditorActorSubsystem).get_all_level_actors()
350
+ target = None
351
+ for a in actors:
352
+ if a.get_actor_label() == "${name}":
353
+ target = a
354
+ break
355
+ if target:
356
+ ${loc ? `target.set_actor_location(unreal.Vector(${loc}), False, False)` : '# no location change'}
357
+ ${rot ? `target.set_actor_rotation(unreal.Rotator(${rot}), False, False)` : '# no rotation change'}
358
+ ${scale ? `target.set_actor_scale3d(unreal.Vector(${scale}))` : '# no scale change'}
359
+ print(json.dumps({"success": True, "actor": "${name}"}))
360
+ else:
361
+ print(json.dumps({"success": False, "error": "Actor '${name}' not found"}))`;
362
+ }
363
+
364
+ case 'create_blueprint': {
365
+ const name = params.name || 'NewBlueprint';
366
+ const parentClass = params.parent_class || 'Actor';
367
+ const path = params.path || '/Game/Blueprints';
368
+ return `import unreal, json
369
+ factory = unreal.BlueprintFactory()
370
+ factory.set_editor_property("parent_class", unreal.${parentClass})
371
+ asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
372
+ bp = asset_tools.create_asset("${name}", "${path}", unreal.Blueprint, factory)
373
+ if bp:
374
+ print(json.dumps({"success": True, "name": "${name}", "path": "${path}/${name}"}))
375
+ else:
376
+ print(json.dumps({"success": False, "error": "Failed to create blueprint"}))`;
377
+ }
378
+
379
+ case 'create_material': {
380
+ const name = params.name || 'NewMaterial';
381
+ const path = params.path || '/Game/Materials';
382
+ return `import unreal, json
383
+ factory = unreal.MaterialFactoryNew()
384
+ asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
385
+ mat = asset_tools.create_asset("${name}", "${path}", unreal.Material, factory)
386
+ if mat:
387
+ print(json.dumps({"success": True, "name": "${name}", "path": "${path}/${name}"}))
388
+ else:
389
+ print(json.dumps({"success": False, "error": "Failed to create material"}))`;
390
+ }
391
+
392
+ case 'get_all_scene_objects':
393
+ return `import unreal, json
394
+ subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
395
+ actors = subsystem.get_all_level_actors()
396
+ result = []
397
+ for a in actors:
398
+ loc = a.get_actor_location()
399
+ result.append({"name": a.get_actor_label(), "class": a.get_class().get_name(), "location": [loc.x, loc.y, loc.z]})
400
+ print(json.dumps(result))`;
401
+
402
+ default:
403
+ // Generic: pass tool name and params as JSON, let a generic handler try
404
+ return `import unreal, json
405
+ params = json.loads('${paramsJson.replace(/'/g, "\\'")}')
406
+ print(json.dumps({"success": False, "error": "Tool '${toolName}' is not directly supported in no-plugin mode via CLI exec. Use 'createlex serve' with an AI tool for full 71-tool support."}))`;
407
+ }
308
408
  }
309
409
 
310
410
  module.exports = {
@@ -15,12 +15,18 @@ function request(method, path, body = null, options = {}) {
15
15
  const timeout = options.timeout || REQUEST_TIMEOUT;
16
16
 
17
17
  return new Promise((resolve, reject) => {
18
+ const bodyStr = body ? JSON.stringify(body) : null;
19
+ const headers = { 'Content-Type': 'application/json' };
20
+ if (bodyStr) {
21
+ headers['Content-Length'] = Buffer.byteLength(bodyStr);
22
+ }
23
+
18
24
  const reqOptions = {
19
25
  hostname: host,
20
26
  port,
21
27
  path,
22
28
  method,
23
- headers: { 'Content-Type': 'application/json' },
29
+ headers,
24
30
  timeout
25
31
  };
26
32
 
@@ -49,8 +55,8 @@ function request(method, path, body = null, options = {}) {
49
55
  }
50
56
  });
51
57
 
52
- if (body) {
53
- req.write(JSON.stringify(body));
58
+ if (bodyStr) {
59
+ req.write(bodyStr);
54
60
  }
55
61
 
56
62
  req.end();
@@ -184,7 +190,7 @@ async function executePythonScript(script, options = {}) {
184
190
  'ExecutePythonCommandEx',
185
191
  {
186
192
  PythonCommand: script,
187
- ExecutionMode: 'EvaluateStatement',
193
+ ExecutionMode: 'ExecuteFile',
188
194
  FileExecutionScope: 'Public'
189
195
  },
190
196
  options