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