@lanonasis/cli 3.8.0 → 3.9.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/CHANGELOG.md +195 -0
- package/README.md +65 -2
- package/dist/commands/auth.js +1 -1
- package/dist/commands/config.js +3 -2
- package/dist/commands/init.js +12 -0
- package/dist/commands/mcp.js +50 -3
- package/dist/commands/memory.js +49 -23
- package/dist/index.js +20 -0
- package/dist/mcp/access-control.js +2 -2
- package/dist/mcp/schemas/tool-schemas.d.ts +4 -4
- package/dist/mcp/server/lanonasis-server.js +26 -3
- package/dist/utils/api.js +10 -10
- package/dist/utils/config.js +40 -6
- package/dist/utils/mcp-client.d.ts +2 -0
- package/dist/utils/mcp-client.js +33 -15
- package/dist/ux/implementations/ConnectionManagerImpl.d.ts +72 -0
- package/dist/ux/implementations/ConnectionManagerImpl.js +352 -0
- package/dist/ux/implementations/OnboardingFlowImpl.d.ts +72 -0
- package/dist/ux/implementations/OnboardingFlowImpl.js +415 -0
- package/dist/ux/implementations/TextInputHandlerImpl.d.ts +74 -0
- package/dist/ux/implementations/TextInputHandlerImpl.js +342 -0
- package/dist/ux/implementations/index.d.ts +11 -0
- package/dist/ux/implementations/index.js +11 -0
- package/dist/ux/index.d.ts +15 -0
- package/dist/ux/index.js +22 -0
- package/dist/ux/interfaces/ConnectionManager.d.ts +112 -0
- package/dist/ux/interfaces/ConnectionManager.js +7 -0
- package/dist/ux/interfaces/OnboardingFlow.d.ts +103 -0
- package/dist/ux/interfaces/OnboardingFlow.js +7 -0
- package/dist/ux/interfaces/TextInputHandler.d.ts +87 -0
- package/dist/ux/interfaces/TextInputHandler.js +7 -0
- package/dist/ux/interfaces/index.d.ts +10 -0
- package/dist/ux/interfaces/index.js +8 -0
- package/package.json +34 -4
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection Manager Implementation
|
|
3
|
+
*
|
|
4
|
+
* Manages MCP server discovery, configuration, and connection lifecycle
|
|
5
|
+
* Implementation of the ConnectionManager interface.
|
|
6
|
+
*/
|
|
7
|
+
import { promises as fs, createWriteStream } from 'fs';
|
|
8
|
+
import { join, dirname, resolve } from 'path';
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
/**
|
|
12
|
+
* Default MCP configuration
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_MCP_CONFIG = {
|
|
15
|
+
localServerPath: '',
|
|
16
|
+
serverPort: 3000,
|
|
17
|
+
autoStart: true,
|
|
18
|
+
connectionTimeout: 10000,
|
|
19
|
+
retryAttempts: 3,
|
|
20
|
+
logLevel: 'info',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* ConnectionManagerImpl manages MCP server discovery, configuration, and connection lifecycle
|
|
24
|
+
*
|
|
25
|
+
* This implementation automatically detects the embedded MCP server location within the CLI package,
|
|
26
|
+
* generates configuration files with correct server paths, and manages server processes.
|
|
27
|
+
*/
|
|
28
|
+
export class ConnectionManagerImpl {
|
|
29
|
+
config;
|
|
30
|
+
connectionStatus;
|
|
31
|
+
serverProcess = null;
|
|
32
|
+
configPath;
|
|
33
|
+
constructor(configPath) {
|
|
34
|
+
this.config = { ...DEFAULT_MCP_CONFIG };
|
|
35
|
+
this.configPath = configPath || join(process.cwd(), '.lanonasis', 'mcp-config.json');
|
|
36
|
+
this.connectionStatus = {
|
|
37
|
+
isConnected: false,
|
|
38
|
+
connectionAttempts: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Initialize the connection manager by loading persisted configuration
|
|
43
|
+
*/
|
|
44
|
+
async init() {
|
|
45
|
+
await this.loadConfig();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Connect to the local embedded MCP server
|
|
49
|
+
*/
|
|
50
|
+
async connectLocal() {
|
|
51
|
+
try {
|
|
52
|
+
// Load persisted configuration first
|
|
53
|
+
await this.loadConfig();
|
|
54
|
+
// First, try to detect the server path
|
|
55
|
+
const configuredPath = this.config.localServerPath?.trim();
|
|
56
|
+
const serverPath = configuredPath || (await this.detectServerPath());
|
|
57
|
+
if (!serverPath) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: 'Could not detect local MCP server path',
|
|
61
|
+
suggestions: [
|
|
62
|
+
'Ensure the CLI package is properly installed',
|
|
63
|
+
'Check that the MCP server files are present',
|
|
64
|
+
'Try running: lanonasis init',
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Update configuration with detected path
|
|
69
|
+
this.config.localServerPath = serverPath;
|
|
70
|
+
await this.saveConfig();
|
|
71
|
+
// Start the server if not already running
|
|
72
|
+
if (!this.isServerRunning()) {
|
|
73
|
+
const serverInstance = await this.startLocalServer();
|
|
74
|
+
this.connectionStatus.serverInstance = serverInstance;
|
|
75
|
+
}
|
|
76
|
+
// Verify the connection
|
|
77
|
+
const isConnected = await this.verifyConnection(serverPath);
|
|
78
|
+
if (isConnected) {
|
|
79
|
+
this.connectionStatus.isConnected = true;
|
|
80
|
+
this.connectionStatus.lastConnected = new Date();
|
|
81
|
+
this.connectionStatus.connectionAttempts++;
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
serverPath,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.connectionStatus.connectionAttempts++;
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
error: 'Failed to verify MCP server connection',
|
|
92
|
+
suggestions: [
|
|
93
|
+
'Check server logs for errors',
|
|
94
|
+
'Ensure no other process is using the port',
|
|
95
|
+
'Try restarting the CLI',
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
this.connectionStatus.connectionAttempts++;
|
|
102
|
+
this.connectionStatus.lastError = error instanceof Error ? error.message : String(error);
|
|
103
|
+
return {
|
|
104
|
+
success: false,
|
|
105
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
106
|
+
suggestions: [
|
|
107
|
+
'Check your network connection',
|
|
108
|
+
'Verify the MCP server is installed correctly',
|
|
109
|
+
'Try running: lanonasis mcp diagnose',
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Automatically configure the local MCP server with correct paths
|
|
116
|
+
*/
|
|
117
|
+
async autoConfigureLocalServer() {
|
|
118
|
+
try {
|
|
119
|
+
const serverPath = await this.detectServerPath();
|
|
120
|
+
if (!serverPath) {
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
error: 'Could not detect MCP server path for auto-configuration',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// Update configuration
|
|
127
|
+
this.config.localServerPath = serverPath;
|
|
128
|
+
// Ensure config directory exists
|
|
129
|
+
await fs.mkdir(dirname(this.configPath), { recursive: true });
|
|
130
|
+
// Save configuration
|
|
131
|
+
await this.saveConfig();
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
configPath: this.configPath,
|
|
135
|
+
serverPath,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: `Auto-configuration failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Detect the embedded MCP server path within the CLI package
|
|
147
|
+
*/
|
|
148
|
+
async detectServerPath() {
|
|
149
|
+
try {
|
|
150
|
+
// Get the current module directory
|
|
151
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
152
|
+
// Common paths to check for the MCP server
|
|
153
|
+
const candidatePaths = [
|
|
154
|
+
// Relative to current CLI source
|
|
155
|
+
resolve(currentDir, '../../mcp-server-entry.js'),
|
|
156
|
+
resolve(currentDir, '../../../dist/mcp-server-entry.js'),
|
|
157
|
+
// Relative to CLI package root
|
|
158
|
+
resolve(currentDir, '../../../mcp-server-entry.js'),
|
|
159
|
+
resolve(currentDir, '../../dist/mcp-server-entry.js'),
|
|
160
|
+
// In node_modules (if installed as dependency)
|
|
161
|
+
resolve(process.cwd(), 'node_modules/@lanonasis/cli/dist/mcp-server-entry.js'),
|
|
162
|
+
// Global installation paths
|
|
163
|
+
resolve(process.cwd(), 'dist/mcp-server-entry.js'),
|
|
164
|
+
// Development paths
|
|
165
|
+
resolve(process.cwd(), 'cli/dist/mcp-server-entry.js'),
|
|
166
|
+
resolve(process.cwd(), '../cli/dist/mcp-server-entry.js'),
|
|
167
|
+
];
|
|
168
|
+
// Check each candidate path
|
|
169
|
+
for (const candidatePath of candidatePaths) {
|
|
170
|
+
try {
|
|
171
|
+
await fs.access(candidatePath);
|
|
172
|
+
// Verify it's actually the MCP server by checking file content
|
|
173
|
+
const content = await fs.readFile(candidatePath, 'utf-8');
|
|
174
|
+
if (content.includes('mcp') || content.includes('server')) {
|
|
175
|
+
return candidatePath;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Path doesn't exist or isn't accessible, continue
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
console.error('Error detecting server path:', error);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Start the local MCP server process
|
|
191
|
+
*/
|
|
192
|
+
async startLocalServer() {
|
|
193
|
+
if (!this.config.localServerPath) {
|
|
194
|
+
throw new Error('No local server path configured');
|
|
195
|
+
}
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
const serverProcess = spawn('node', [this.config.localServerPath], {
|
|
198
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
199
|
+
env: {
|
|
200
|
+
...process.env,
|
|
201
|
+
PORT: this.config.serverPort?.toString() || '3000',
|
|
202
|
+
LOG_LEVEL: this.config.logLevel,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
const serverInstance = {
|
|
206
|
+
pid: serverProcess.pid,
|
|
207
|
+
port: this.config.serverPort || 3000,
|
|
208
|
+
status: 'starting',
|
|
209
|
+
startTime: new Date(),
|
|
210
|
+
logPath: join(dirname(this.configPath), 'mcp-server.log'),
|
|
211
|
+
};
|
|
212
|
+
// Set up logging
|
|
213
|
+
const logStream = createWriteStream(serverInstance.logPath, { flags: 'a' });
|
|
214
|
+
serverProcess.stdout?.pipe(logStream);
|
|
215
|
+
serverProcess.stderr?.pipe(logStream);
|
|
216
|
+
// Handle server startup
|
|
217
|
+
const startupTimeout = setTimeout(() => {
|
|
218
|
+
serverInstance.status = 'error';
|
|
219
|
+
reject(new Error('Server startup timeout'));
|
|
220
|
+
}, this.config.connectionTimeout);
|
|
221
|
+
serverProcess.on('spawn', () => {
|
|
222
|
+
clearTimeout(startupTimeout);
|
|
223
|
+
serverInstance.status = 'running';
|
|
224
|
+
this.serverProcess = serverProcess;
|
|
225
|
+
resolve(serverInstance);
|
|
226
|
+
});
|
|
227
|
+
serverProcess.on('error', (error) => {
|
|
228
|
+
clearTimeout(startupTimeout);
|
|
229
|
+
serverInstance.status = 'error';
|
|
230
|
+
reject(error);
|
|
231
|
+
});
|
|
232
|
+
serverProcess.on('exit', (code) => {
|
|
233
|
+
serverInstance.status = code === 0 ? 'stopped' : 'error';
|
|
234
|
+
this.serverProcess = null;
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Verify that the MCP server connection is working
|
|
240
|
+
*/
|
|
241
|
+
async verifyConnection(serverPath) {
|
|
242
|
+
try {
|
|
243
|
+
// Simple verification - check if server path exists and is accessible
|
|
244
|
+
await fs.access(serverPath);
|
|
245
|
+
// If we have a running server instance, check if it's responsive
|
|
246
|
+
if (this.connectionStatus.serverInstance) {
|
|
247
|
+
const { status, pid } = this.connectionStatus.serverInstance;
|
|
248
|
+
// Explicitly check for error/stopped states
|
|
249
|
+
if (status === 'error' || status === 'stopped') {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
// Only verify process for running servers
|
|
253
|
+
if (status === 'running') {
|
|
254
|
+
try {
|
|
255
|
+
process.kill(pid, 0); // Signal 0 checks if process exists
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// Process doesn't exist despite status being 'running'
|
|
260
|
+
this.connectionStatus.serverInstance.status = 'stopped';
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Starting state is not yet ready
|
|
265
|
+
if (status === 'starting') {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// No server instance means we haven't started it yet
|
|
270
|
+
// This is okay for initial connection attempts
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get the current connection status
|
|
279
|
+
*/
|
|
280
|
+
getConnectionStatus() {
|
|
281
|
+
return { ...this.connectionStatus };
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Stop the local MCP server if running
|
|
285
|
+
*/
|
|
286
|
+
async stopLocalServer() {
|
|
287
|
+
if (this.serverProcess) {
|
|
288
|
+
return new Promise((resolve) => {
|
|
289
|
+
const forceKillTimeout = setTimeout(() => {
|
|
290
|
+
if (this.serverProcess) {
|
|
291
|
+
this.serverProcess.kill('SIGKILL');
|
|
292
|
+
}
|
|
293
|
+
}, 5000);
|
|
294
|
+
this.serverProcess.on('exit', () => {
|
|
295
|
+
clearTimeout(forceKillTimeout);
|
|
296
|
+
this.serverProcess = null;
|
|
297
|
+
if (this.connectionStatus.serverInstance) {
|
|
298
|
+
this.connectionStatus.serverInstance.status = 'stopped';
|
|
299
|
+
}
|
|
300
|
+
this.connectionStatus.isConnected = false;
|
|
301
|
+
resolve();
|
|
302
|
+
});
|
|
303
|
+
this.serverProcess.kill('SIGTERM');
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get the current MCP configuration
|
|
309
|
+
*/
|
|
310
|
+
getConfig() {
|
|
311
|
+
return { ...this.config };
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Update the MCP configuration
|
|
315
|
+
*/
|
|
316
|
+
async updateConfig(config) {
|
|
317
|
+
this.config = { ...this.config, ...config };
|
|
318
|
+
await this.saveConfig();
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Check if the server is currently running
|
|
322
|
+
*/
|
|
323
|
+
isServerRunning() {
|
|
324
|
+
return (this.serverProcess !== null && this.connectionStatus.serverInstance?.status === 'running');
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Save the current configuration to disk
|
|
328
|
+
*/
|
|
329
|
+
async saveConfig() {
|
|
330
|
+
try {
|
|
331
|
+
await fs.mkdir(dirname(this.configPath), { recursive: true });
|
|
332
|
+
await fs.writeFile(this.configPath, JSON.stringify(this.config, null, 2));
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
throw new Error(`Failed to save configuration: ${error instanceof Error ? error.message : String(error)}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Load configuration from disk
|
|
340
|
+
*/
|
|
341
|
+
async loadConfig() {
|
|
342
|
+
try {
|
|
343
|
+
const configData = await fs.readFile(this.configPath, 'utf-8');
|
|
344
|
+
const loadedConfig = JSON.parse(configData);
|
|
345
|
+
this.config = { ...DEFAULT_MCP_CONFIG, ...loadedConfig };
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// Config file doesn't exist or is invalid, use defaults
|
|
349
|
+
this.config = { ...DEFAULT_MCP_CONFIG };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding Flow Implementation
|
|
3
|
+
*
|
|
4
|
+
* Guides new users through initial setup and configuration
|
|
5
|
+
* Implementation of the OnboardingFlow interface.
|
|
6
|
+
*/
|
|
7
|
+
import { OnboardingFlow, SetupResult, TestResult, UserPreferences, OnboardingState } from '../interfaces/OnboardingFlow.js';
|
|
8
|
+
/**
|
|
9
|
+
* OnboardingFlowImpl guides new users through initial setup and configuration
|
|
10
|
+
*
|
|
11
|
+
* This implementation detects first-run scenarios, creates working default configurations,
|
|
12
|
+
* tests all major functionality, and provides interactive demonstrations.
|
|
13
|
+
*/
|
|
14
|
+
export declare class OnboardingFlowImpl implements OnboardingFlow {
|
|
15
|
+
private onboardingState;
|
|
16
|
+
private configPath;
|
|
17
|
+
private connectionManager;
|
|
18
|
+
private textInputHandler;
|
|
19
|
+
constructor(configPath?: string);
|
|
20
|
+
/**
|
|
21
|
+
* Run the complete initial setup process for new users
|
|
22
|
+
*/
|
|
23
|
+
runInitialSetup(): Promise<SetupResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Detect if this is a first-run scenario
|
|
26
|
+
*/
|
|
27
|
+
detectFirstRun(): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Configure working default settings for immediate productivity
|
|
30
|
+
*/
|
|
31
|
+
configureDefaults(): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Test connectivity and functionality of all major components
|
|
34
|
+
*/
|
|
35
|
+
testConnectivity(): Promise<TestResult[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Show welcome demonstration of key features
|
|
38
|
+
*/
|
|
39
|
+
showWelcomeDemo(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Get the current onboarding state
|
|
42
|
+
*/
|
|
43
|
+
getOnboardingState(): OnboardingState;
|
|
44
|
+
/**
|
|
45
|
+
* Update user preferences during onboarding
|
|
46
|
+
*/
|
|
47
|
+
updateUserPreferences(preferences: Partial<UserPreferences>): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Skip the current onboarding step
|
|
50
|
+
*/
|
|
51
|
+
skipCurrentStep(reason?: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Complete the onboarding process
|
|
54
|
+
*/
|
|
55
|
+
completeOnboarding(): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Reset onboarding state (for testing or re-running)
|
|
58
|
+
*/
|
|
59
|
+
resetOnboarding(): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Gather user preferences interactively
|
|
62
|
+
*/
|
|
63
|
+
private gatherUserPreferences;
|
|
64
|
+
/**
|
|
65
|
+
* Save the current onboarding state to disk
|
|
66
|
+
*/
|
|
67
|
+
private saveOnboardingState;
|
|
68
|
+
/**
|
|
69
|
+
* Load onboarding state from disk
|
|
70
|
+
*/
|
|
71
|
+
private loadOnboardingState;
|
|
72
|
+
}
|