@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/src/cli.js ADDED
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const pkg = require('../package.json');
5
+
6
+ function run(argv) {
7
+ const program = new Command();
8
+
9
+ program
10
+ .name('createlex')
11
+ .description('CreatelexGenAI CLI — Unreal Engine AI integration')
12
+ .version(pkg.version);
13
+
14
+ // MCP server mode
15
+ program
16
+ .command('serve')
17
+ .description('Start MCP server (stdio mode for AI CLI tools)')
18
+ .option('--port <port>', 'Run in TCP mode on specified port')
19
+ .option('--debug', 'Enable debug logging')
20
+ .action((opts) => {
21
+ const { serve } = require('./commands/serve');
22
+ serve(opts);
23
+ });
24
+
25
+ // Direct tool execution
26
+ program
27
+ .command('exec <tool>')
28
+ .description('Execute a tool directly against Unreal Engine')
29
+ .option('--port <port>', 'UE socket port', '9878')
30
+ .option('--backend <backend>', 'Connection backend: plugin, web-remote, remote-exec (auto-detects if omitted)')
31
+ .allowUnknownOption(true)
32
+ .action((tool, opts, cmd) => {
33
+ const { exec } = require('./commands/exec');
34
+ // Collect extra args as tool arguments
35
+ const extraArgs = cmd.args.slice(1);
36
+ exec(tool, extraArgs, opts);
37
+ });
38
+
39
+ // List available tools
40
+ program
41
+ .command('tools')
42
+ .description('List all available MCP tools')
43
+ .option('--json', 'Output as JSON')
44
+ .action((opts) => {
45
+ const { tools } = require('./commands/tools');
46
+ tools(opts);
47
+ });
48
+
49
+ // Auth commands
50
+ program
51
+ .command('login')
52
+ .description('Authenticate with CreateLex')
53
+ .action(() => {
54
+ const { login } = require('./commands/login');
55
+ login();
56
+ });
57
+
58
+ program
59
+ .command('logout')
60
+ .description('Clear stored credentials')
61
+ .action(() => {
62
+ const { logout } = require('./commands/logout');
63
+ logout();
64
+ });
65
+
66
+ // Status & connection
67
+ program
68
+ .command('status')
69
+ .description('Show connection and subscription status')
70
+ .action(() => {
71
+ const { status } = require('./commands/status');
72
+ status();
73
+ });
74
+
75
+ program
76
+ .command('connect')
77
+ .description('Test connection to Unreal Engine')
78
+ .option('--port <port>', 'UE socket port', '9878')
79
+ .action((opts) => {
80
+ const { connect } = require('./commands/connect');
81
+ connect(opts);
82
+ });
83
+
84
+ // Configuration
85
+ program
86
+ .command('config')
87
+ .description('Manage configuration')
88
+ .argument('<action>', 'Action: get, set, list, reset')
89
+ .argument('[key]', 'Config key')
90
+ .argument('[value]', 'Config value')
91
+ .action((action, key, value) => {
92
+ const { config } = require('./commands/config');
93
+ config(action, key, value);
94
+ });
95
+
96
+ // IDE setup
97
+ program
98
+ .command('setup [ide]')
99
+ .description('Auto-configure MCP for an IDE (e.g., claude-code, cursor, windsurf)')
100
+ .option('--all', 'Configure all detected IDEs')
101
+ .action((ide, opts) => {
102
+ const { setup } = require('./commands/setup');
103
+ setup(ide, opts);
104
+ });
105
+
106
+ program.parse(argv);
107
+ }
108
+
109
+ module.exports = { run };
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ const log = require('../utils/logger');
4
+ const configStore = require('../core/config-store');
5
+
6
+ function config(action, key, value) {
7
+ switch (action) {
8
+ case 'list': {
9
+ const all = configStore.list();
10
+ log.header('Configuration');
11
+ for (const [k, v] of Object.entries(all)) {
12
+ log.keyValue(k, v);
13
+ }
14
+ break;
15
+ }
16
+
17
+ case 'get': {
18
+ if (!key) {
19
+ log.error('Usage: createlex config get <key>');
20
+ process.exit(1);
21
+ }
22
+ const val = configStore.get(key);
23
+ if (val === undefined) {
24
+ log.error(`Unknown config key: ${key}`);
25
+ process.exit(1);
26
+ }
27
+ console.log(val);
28
+ break;
29
+ }
30
+
31
+ case 'set': {
32
+ if (!key || value === undefined) {
33
+ log.error('Usage: createlex config set <key> <value>');
34
+ process.exit(1);
35
+ }
36
+ const updated = configStore.set(key, value);
37
+ log.success(`Set ${key} = ${updated[key]}`);
38
+ break;
39
+ }
40
+
41
+ case 'reset': {
42
+ const defaults = configStore.reset();
43
+ log.success('Configuration reset to defaults');
44
+ for (const [k, v] of Object.entries(defaults)) {
45
+ log.keyValue(k, v);
46
+ }
47
+ break;
48
+ }
49
+
50
+ default:
51
+ log.error(`Unknown action: ${action}. Use: get, set, list, reset`);
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ module.exports = { config };
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ const log = require('../utils/logger');
4
+ const chalk = require('chalk');
5
+ const { detectBackends, BACKEND } = require('../core/unreal-connection');
6
+
7
+ async function connect(opts = {}) {
8
+ const pluginPort = parseInt(opts.port, 10) || 9878;
9
+
10
+ log.header('Unreal Engine Connection Test');
11
+ log.info('Scanning all connection backends...');
12
+ log.blank();
13
+
14
+ const backends = await detectBackends({ pluginPort });
15
+
16
+ let anyConnected = false;
17
+
18
+ // 1. CreatelexGenAI Plugin (TCP 9878)
19
+ const plugin = backends[BACKEND.PLUGIN];
20
+ if (plugin.connected) {
21
+ log.success(`${plugin.name}: Connected (port ${pluginPort})`);
22
+ log.keyValue(' Features', 'Full 71+ tools, MCP server, direct exec');
23
+ anyConnected = true;
24
+ } else {
25
+ log.warn(`${plugin.name}: Not available`);
26
+ log.keyValue(' Requires', 'CreatelexGenAI plugin enabled in UE');
27
+ log.keyValue(' Port', pluginPort);
28
+ }
29
+
30
+ log.blank();
31
+
32
+ // 2. Web Remote Control (HTTP 30010)
33
+ const webRemote = backends[BACKEND.WEB_REMOTE];
34
+ if (webRemote.connected) {
35
+ log.success(`${webRemote.name}: Connected (port ${webRemote.port || 30010})`);
36
+ if (webRemote.verified) {
37
+ log.keyValue(' Features', 'Call UFunctions, get/set properties, console commands, Python exec');
38
+ } else {
39
+ log.keyValue(' Status', chalk.yellow('Server responds but function calls unverified'));
40
+ }
41
+ anyConnected = true;
42
+ } else {
43
+ log.warn(`${webRemote.name}: Not available`);
44
+ log.keyValue(' Requires', 'Web Remote Control plugin in UE (built-in)');
45
+ log.keyValue(' Enable', 'Edit > Plugins > "Web Remote Control" + run WebControl.StartServer');
46
+ }
47
+
48
+ log.blank();
49
+
50
+ // 3. Python Remote Execution (UDP 6766)
51
+ const remoteExec = backends[BACKEND.REMOTE_EXEC];
52
+ if (remoteExec.connected) {
53
+ log.success(`${remoteExec.name}: Available`);
54
+ if (remoteExec.nodes && remoteExec.nodes.length > 0) {
55
+ for (const node of remoteExec.nodes) {
56
+ log.keyValue(' Node', `${node.machine} (${node.engine_version})`);
57
+ }
58
+ }
59
+ log.keyValue(' Features', 'Execute Python scripts inside UE editor');
60
+ anyConnected = true;
61
+ } else {
62
+ log.warn(`${remoteExec.name}: Not available`);
63
+ log.keyValue(' Requires', 'Python Editor Script Plugin + "Allow Python Remote Execution" in Project Settings');
64
+ }
65
+
66
+ log.blank();
67
+
68
+ if (anyConnected) {
69
+ log.success('Unreal Engine is reachable!');
70
+
71
+ if (!plugin.connected && (webRemote.connected || remoteExec.connected)) {
72
+ log.blank();
73
+ log.info(chalk.yellow('Note: Without the CreatelexGenAI plugin, only a subset of tools are available.'));
74
+ log.info('Available without plugin:');
75
+ log.info(' - execute_python_script');
76
+ log.info(' - execute_unreal_command');
77
+ log.info(' - get_all_scene_objects');
78
+ log.info(' - spawn_object (basic)');
79
+ log.blank();
80
+ log.info('For full 71+ tools, enable the CreatelexGenAI plugin.');
81
+ }
82
+ } else {
83
+ log.error('No Unreal Engine connection found.');
84
+ log.blank();
85
+ log.info('To connect, you need at least one of:');
86
+ log.blank();
87
+ log.info(chalk.bold(' Option 1: CreatelexGenAI Plugin') + ' (recommended — full features)');
88
+ log.info(' Enable the plugin in your UE project');
89
+ log.blank();
90
+ log.info(chalk.bold(' Option 2: Web Remote Control') + ' (built-in UE plugin)');
91
+ log.info(' Edit > Plugins > "Web Remote Control" > Enable');
92
+ log.info(' Then run: WebControl.StartServer in console');
93
+ log.blank();
94
+ log.info(chalk.bold(' Option 3: Python Remote Execution') + ' (built-in UE plugin)');
95
+ log.info(' Edit > Plugins > "Python Editor Script Plugin" > Enable');
96
+ log.info(' Project Settings > Python > "Allow Python Remote Execution" > Check');
97
+ }
98
+ }
99
+
100
+ module.exports = { connect };
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ const log = require('../utils/logger');
4
+ const authManager = require('../core/auth-manager');
5
+ const { executeWithBackend, autoSelectBackend, BACKEND } = require('../core/unreal-connection');
6
+
7
+ async function exec(tool, extraArgs, opts = {}) {
8
+ // Auth check
9
+ const token = authManager.getToken();
10
+ if (!token) {
11
+ log.error('Not authenticated. Run `createlex login` first.');
12
+ process.exit(1);
13
+ }
14
+
15
+ const validation = authManager.validateTokenFormat(token);
16
+ if (!validation.valid) {
17
+ log.error('Token expired or invalid. Run `createlex login`.');
18
+ process.exit(1);
19
+ }
20
+
21
+ // Parse extra args into a params object
22
+ // Supports: --key value, --key=value, --flag (boolean true)
23
+ const params = {};
24
+ for (let i = 0; i < extraArgs.length; i++) {
25
+ const arg = extraArgs[i];
26
+ if (arg.startsWith('--')) {
27
+ const eqIndex = arg.indexOf('=');
28
+ if (eqIndex !== -1) {
29
+ const key = arg.slice(2, eqIndex).replace(/-/g, '_');
30
+ params[key] = arg.slice(eqIndex + 1);
31
+ } else {
32
+ const key = arg.slice(2).replace(/-/g, '_');
33
+ const next = extraArgs[i + 1];
34
+ if (next && !next.startsWith('--')) {
35
+ params[key] = next;
36
+ i++;
37
+ } else {
38
+ params[key] = true;
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ const port = parseInt(opts.port, 10) || 9878;
45
+
46
+ // Determine backend
47
+ let backendId = opts.backend || null;
48
+
49
+ if (!backendId) {
50
+ // Auto-detect best backend
51
+ const selected = await autoSelectBackend({ pluginPort: port });
52
+ if (!selected.backend) {
53
+ log.error('No Unreal Engine connection available. Run `createlex connect` to diagnose.');
54
+ process.exit(1);
55
+ }
56
+ backendId = selected.backend;
57
+ log.info(`Using backend: ${selected.info.name}`);
58
+ }
59
+
60
+ log.info(`Executing tool: ${tool}`);
61
+ if (Object.keys(params).length > 0) {
62
+ log.debug(`Parameters: ${JSON.stringify(params)}`);
63
+ }
64
+
65
+ try {
66
+ const result = await executeWithBackend(tool, params, {
67
+ port,
68
+ backend: backendId
69
+ });
70
+
71
+ // Format output based on backend response format
72
+ if (backendId === BACKEND.PLUGIN) {
73
+ formatPluginResult(result);
74
+ } else if (backendId === BACKEND.REMOTE_EXEC) {
75
+ formatRemoteExecResult(result);
76
+ } else if (backendId === BACKEND.WEB_REMOTE) {
77
+ formatWebRemoteResult(result);
78
+ } else {
79
+ console.log(JSON.stringify(result, null, 2));
80
+ }
81
+ } catch (err) {
82
+ log.error(err.message);
83
+ process.exit(1);
84
+ }
85
+ }
86
+
87
+ function formatPluginResult(result) {
88
+ if (result.error) {
89
+ log.error(`Tool error: ${result.error.message || JSON.stringify(result.error)}`);
90
+ process.exit(1);
91
+ }
92
+
93
+ if (result.result) {
94
+ const content = result.result.content || result.result;
95
+ if (Array.isArray(content)) {
96
+ for (const item of content) {
97
+ if (item.type === 'text') {
98
+ console.log(item.text);
99
+ } else {
100
+ console.log(JSON.stringify(item, null, 2));
101
+ }
102
+ }
103
+ } else if (typeof content === 'object') {
104
+ console.log(JSON.stringify(content, null, 2));
105
+ } else {
106
+ console.log(content);
107
+ }
108
+ } else {
109
+ console.log(JSON.stringify(result, null, 2));
110
+ }
111
+ }
112
+
113
+ function formatRemoteExecResult(result) {
114
+ if (!result.success) {
115
+ log.error('Execution failed');
116
+ if (result.outputLog) {
117
+ for (const entry of result.outputLog) {
118
+ if (entry.type === 'error' || entry.type === 'warning') {
119
+ log.warn(entry.message);
120
+ }
121
+ }
122
+ }
123
+ process.exit(1);
124
+ }
125
+
126
+ // Print output
127
+ if (result.output) {
128
+ console.log(result.output);
129
+ }
130
+ if (result.result !== null && result.result !== undefined) {
131
+ console.log(typeof result.result === 'string' ? result.result : JSON.stringify(result.result, null, 2));
132
+ }
133
+ if (result.outputLog && result.outputLog.length > 0) {
134
+ for (const entry of result.outputLog) {
135
+ console.log(`[${entry.type}] ${entry.message}`);
136
+ }
137
+ }
138
+ }
139
+
140
+ function formatWebRemoteResult(result) {
141
+ if (typeof result === 'object') {
142
+ console.log(JSON.stringify(result, null, 2));
143
+ } else {
144
+ console.log(result);
145
+ }
146
+ }
147
+
148
+ module.exports = { exec };
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+
3
+ const http = require('http');
4
+ const crypto = require('crypto');
5
+ const log = require('../utils/logger');
6
+ const authManager = require('../core/auth-manager');
7
+ const configStore = require('../core/config-store');
8
+
9
+ async function login() {
10
+ const existing = authManager.getToken();
11
+ if (existing) {
12
+ const validation = authManager.validateTokenFormat(existing);
13
+ if (validation.valid) {
14
+ log.info('Already logged in. Use `createlex logout` first to re-authenticate.');
15
+ return;
16
+ }
17
+ log.warn('Existing token is expired or invalid. Starting fresh login...');
18
+ }
19
+
20
+ const state = crypto.randomBytes(16).toString('hex');
21
+ const webBaseUrl = configStore.get('webBaseUrl') || 'https://createlex.com';
22
+
23
+ // Start a temporary local server to receive the auth callback
24
+ const server = http.createServer((req, res) => {
25
+ const url = new URL(req.url, 'http://localhost');
26
+
27
+ if (url.pathname === '/callback') {
28
+ const token = url.searchParams.get('token');
29
+ const returnedState = url.searchParams.get('state');
30
+
31
+ if (returnedState !== state) {
32
+ res.writeHead(400, { 'Content-Type': 'text/html' });
33
+ res.end('<html><body><h2>Authentication failed: state mismatch</h2></body></html>');
34
+ return;
35
+ }
36
+
37
+ if (!token) {
38
+ res.writeHead(400, { 'Content-Type': 'text/html' });
39
+ res.end('<html><body><h2>Authentication failed: no token received</h2></body></html>');
40
+ return;
41
+ }
42
+
43
+ const validation = authManager.validateTokenFormat(token);
44
+ if (!validation.valid) {
45
+ res.writeHead(400, { 'Content-Type': 'text/html' });
46
+ res.end(`<html><body><h2>Authentication failed: ${validation.reason}</h2></body></html>`);
47
+ return;
48
+ }
49
+
50
+ // Store the token
51
+ authManager.setToken(token, {
52
+ email: validation.payload?.email,
53
+ userId: validation.payload?.sub || validation.payload?.userId
54
+ });
55
+
56
+ res.writeHead(200, { 'Content-Type': 'text/html' });
57
+ res.end('<html><body><h2>Authentication successful!</h2><p>You can close this window and return to the terminal.</p></body></html>');
58
+
59
+ log.blank();
60
+ log.success('Login successful!');
61
+
62
+ if (validation.payload?.email) {
63
+ log.keyValue('Email', validation.payload.email);
64
+ }
65
+
66
+ server.close();
67
+ } else {
68
+ res.writeHead(404);
69
+ res.end();
70
+ }
71
+ });
72
+
73
+ // Find an available port
74
+ await new Promise((resolve, reject) => {
75
+ server.listen(0, '127.0.0.1', () => resolve());
76
+ server.on('error', reject);
77
+ });
78
+
79
+ const port = server.address().port;
80
+ const callbackUrl = encodeURIComponent(`http://localhost:${port}/callback`);
81
+ const authUrl = `${webBaseUrl}/auth/cli?callback=${callbackUrl}&state=${state}`;
82
+
83
+ log.header('CreateLex Login');
84
+ log.info('Opening browser for authentication...');
85
+ log.blank();
86
+ log.info(`If the browser doesn't open, visit:`);
87
+ log.info(authUrl);
88
+ log.blank();
89
+
90
+ try {
91
+ const open = require('open');
92
+ await open(authUrl);
93
+ } catch {
94
+ log.warn('Could not open browser automatically. Please visit the URL above.');
95
+ }
96
+
97
+ log.info('Waiting for authentication...');
98
+
99
+ // Timeout after 5 minutes
100
+ const timeout = setTimeout(() => {
101
+ log.error('Authentication timed out after 5 minutes.');
102
+ server.close();
103
+ process.exit(1);
104
+ }, 5 * 60 * 1000);
105
+
106
+ server.on('close', () => {
107
+ clearTimeout(timeout);
108
+ });
109
+ }
110
+
111
+ module.exports = { login };
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ const log = require('../utils/logger');
4
+ const authManager = require('../core/auth-manager');
5
+
6
+ function logout() {
7
+ const existing = authManager.getToken();
8
+ if (!existing) {
9
+ log.info('Not currently logged in.');
10
+ return;
11
+ }
12
+
13
+ authManager.clearAuth();
14
+ log.success('Logged out. Credentials cleared.');
15
+ }
16
+
17
+ module.exports = { logout };