@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.
Files changed (34) hide show
  1. package/CHANGELOG.md +195 -0
  2. package/README.md +65 -2
  3. package/dist/commands/auth.js +1 -1
  4. package/dist/commands/config.js +3 -2
  5. package/dist/commands/init.js +12 -0
  6. package/dist/commands/mcp.js +50 -3
  7. package/dist/commands/memory.js +49 -23
  8. package/dist/index.js +20 -0
  9. package/dist/mcp/access-control.js +2 -2
  10. package/dist/mcp/schemas/tool-schemas.d.ts +4 -4
  11. package/dist/mcp/server/lanonasis-server.js +26 -3
  12. package/dist/utils/api.js +10 -10
  13. package/dist/utils/config.js +40 -6
  14. package/dist/utils/mcp-client.d.ts +2 -0
  15. package/dist/utils/mcp-client.js +33 -15
  16. package/dist/ux/implementations/ConnectionManagerImpl.d.ts +72 -0
  17. package/dist/ux/implementations/ConnectionManagerImpl.js +352 -0
  18. package/dist/ux/implementations/OnboardingFlowImpl.d.ts +72 -0
  19. package/dist/ux/implementations/OnboardingFlowImpl.js +415 -0
  20. package/dist/ux/implementations/TextInputHandlerImpl.d.ts +74 -0
  21. package/dist/ux/implementations/TextInputHandlerImpl.js +342 -0
  22. package/dist/ux/implementations/index.d.ts +11 -0
  23. package/dist/ux/implementations/index.js +11 -0
  24. package/dist/ux/index.d.ts +15 -0
  25. package/dist/ux/index.js +22 -0
  26. package/dist/ux/interfaces/ConnectionManager.d.ts +112 -0
  27. package/dist/ux/interfaces/ConnectionManager.js +7 -0
  28. package/dist/ux/interfaces/OnboardingFlow.d.ts +103 -0
  29. package/dist/ux/interfaces/OnboardingFlow.js +7 -0
  30. package/dist/ux/interfaces/TextInputHandler.d.ts +87 -0
  31. package/dist/ux/interfaces/TextInputHandler.js +7 -0
  32. package/dist/ux/interfaces/index.d.ts +10 -0
  33. package/dist/ux/interfaces/index.js +8 -0
  34. 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
+ }