@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,415 @@
1
+ /**
2
+ * Onboarding Flow Implementation
3
+ *
4
+ * Guides new users through initial setup and configuration
5
+ * Implementation of the OnboardingFlow interface.
6
+ */
7
+ import { promises as fs, accessSync } from 'fs';
8
+ import { join, dirname } from 'path';
9
+ import { ConnectionManagerImpl } from './ConnectionManagerImpl.js';
10
+ import { TextInputHandlerImpl } from './TextInputHandlerImpl.js';
11
+ /**
12
+ * Default user preferences
13
+ */
14
+ const DEFAULT_USER_PREFERENCES = {
15
+ inputMode: 'inline',
16
+ preferredEditor: undefined,
17
+ autoStartMCP: true,
18
+ showOnboardingTips: true,
19
+ verboseErrors: false,
20
+ };
21
+ /**
22
+ * OnboardingFlowImpl guides new users through initial setup and configuration
23
+ *
24
+ * This implementation detects first-run scenarios, creates working default configurations,
25
+ * tests all major functionality, and provides interactive demonstrations.
26
+ */
27
+ export class OnboardingFlowImpl {
28
+ onboardingState;
29
+ configPath;
30
+ connectionManager;
31
+ textInputHandler;
32
+ constructor(configPath) {
33
+ this.configPath = configPath || join(process.cwd(), '.lanonasis', 'onboarding.json');
34
+ this.connectionManager = new ConnectionManagerImpl();
35
+ this.textInputHandler = new TextInputHandlerImpl();
36
+ this.onboardingState = {
37
+ isFirstRun: true,
38
+ currentStep: 0,
39
+ totalSteps: 5,
40
+ completedSteps: [],
41
+ skippedSteps: [],
42
+ userPreferences: { ...DEFAULT_USER_PREFERENCES },
43
+ };
44
+ }
45
+ /**
46
+ * Run the complete initial setup process for new users
47
+ */
48
+ async runInitialSetup() {
49
+ console.log('๐Ÿš€ Welcome to LanOnasis CLI!');
50
+ console.log("Let's get you set up for success...\n");
51
+ const result = {
52
+ completed: false,
53
+ mcpConfigured: false,
54
+ memorySystemReady: false,
55
+ issues: [],
56
+ };
57
+ try {
58
+ // Step 1: Detect first run and load existing state
59
+ this.onboardingState.isFirstRun = this.detectFirstRun();
60
+ if (!this.onboardingState.isFirstRun) {
61
+ await this.loadOnboardingState();
62
+ }
63
+ // Step 2: Configure defaults
64
+ console.log('๐Ÿ“‹ Step 1/5: Configuring default settings...');
65
+ await this.configureDefaults();
66
+ this.onboardingState.completedSteps.push('configure-defaults');
67
+ this.onboardingState.currentStep = 1;
68
+ // Step 3: Set up MCP connection
69
+ console.log('๐Ÿ”Œ Step 2/5: Setting up MCP server connection...');
70
+ const mcpResult = await this.connectionManager.autoConfigureLocalServer();
71
+ if (mcpResult.success) {
72
+ result.mcpConfigured = true;
73
+ console.log('โœ… MCP server configured successfully');
74
+ }
75
+ else {
76
+ result.issues?.push(`MCP configuration failed: ${mcpResult.error}`);
77
+ console.log(`โš ๏ธ MCP configuration issue: ${mcpResult.error}`);
78
+ }
79
+ this.onboardingState.completedSteps.push('configure-mcp');
80
+ this.onboardingState.currentStep = 2;
81
+ // Step 4: Test connectivity
82
+ console.log('๐Ÿงช Step 3/5: Testing system connectivity...');
83
+ const testResults = await this.testConnectivity();
84
+ const failedTests = testResults.filter((t) => t.status === 'fail');
85
+ if (failedTests.length === 0) {
86
+ result.memorySystemReady = true;
87
+ console.log('โœ… All connectivity tests passed');
88
+ }
89
+ else {
90
+ failedTests.forEach((test) => {
91
+ result.issues?.push(`${test.component}: ${test.message}`);
92
+ });
93
+ console.log(`โš ๏ธ ${failedTests.length} connectivity test(s) failed`);
94
+ }
95
+ this.onboardingState.completedSteps.push('test-connectivity');
96
+ this.onboardingState.currentStep = 3;
97
+ // Step 5: Gather user preferences
98
+ console.log('โš™๏ธ Step 4/5: Configuring your preferences...');
99
+ await this.gatherUserPreferences();
100
+ this.onboardingState.completedSteps.push('gather-preferences');
101
+ this.onboardingState.currentStep = 4;
102
+ // Step 6: Show welcome demo
103
+ console.log('๐ŸŽ‰ Step 5/5: Welcome demonstration...');
104
+ await this.showWelcomeDemo();
105
+ this.onboardingState.completedSteps.push('welcome-demo');
106
+ this.onboardingState.currentStep = 5;
107
+ // Complete onboarding
108
+ await this.completeOnboarding();
109
+ result.completed = true;
110
+ console.log('\n๐ŸŽŠ Onboarding completed successfully!');
111
+ console.log("You're ready to start using LanOnasis CLI.");
112
+ }
113
+ catch (error) {
114
+ result.issues?.push(`Onboarding failed: ${error instanceof Error ? error.message : String(error)}`);
115
+ console.error('โŒ Onboarding failed:', error);
116
+ }
117
+ return result;
118
+ }
119
+ /**
120
+ * Detect if this is a first-run scenario
121
+ */
122
+ detectFirstRun() {
123
+ try {
124
+ const configDir = dirname(this.configPath);
125
+ // Check for existing configuration files in the configured directory
126
+ const configPaths = [
127
+ join(configDir, 'config.json'),
128
+ join(configDir, 'onboarding.json'),
129
+ join(configDir, 'mcp-config.json'),
130
+ ];
131
+ for (const configPath of configPaths) {
132
+ try {
133
+ accessSync(configPath);
134
+ return false; // Config exists, not first run
135
+ }
136
+ catch {
137
+ // Config doesn't exist, continue checking
138
+ }
139
+ }
140
+ return true; // No configs found, first run
141
+ }
142
+ catch {
143
+ return true; // Error checking, assume first run
144
+ }
145
+ }
146
+ /**
147
+ * Configure working default settings for immediate productivity
148
+ */
149
+ async configureDefaults() {
150
+ try {
151
+ // Ensure config directory exists
152
+ await fs.mkdir(dirname(this.configPath), { recursive: true });
153
+ // Set up default user preferences
154
+ this.onboardingState.userPreferences = { ...DEFAULT_USER_PREFERENCES };
155
+ // Create default CLI configuration
156
+ const defaultConfig = {
157
+ apiUrl: 'https://api.lanonasis.com',
158
+ outputFormat: 'table',
159
+ verboseLogging: false,
160
+ autoMcpConnect: true,
161
+ inputMode: 'inline',
162
+ };
163
+ const cliConfigPath = join(dirname(this.configPath), 'config.json');
164
+ await fs.writeFile(cliConfigPath, JSON.stringify(defaultConfig, null, 2));
165
+ console.log('โœ… Default configuration created');
166
+ }
167
+ catch (error) {
168
+ throw new Error(`Failed to configure defaults: ${error instanceof Error ? error.message : String(error)}`);
169
+ }
170
+ }
171
+ /**
172
+ * Test connectivity and functionality of all major components
173
+ */
174
+ async testConnectivity() {
175
+ const results = [];
176
+ // Test 1: MCP Server Detection
177
+ try {
178
+ const serverPath = await this.connectionManager.detectServerPath();
179
+ if (serverPath) {
180
+ results.push({
181
+ component: 'MCP Server Detection',
182
+ status: 'pass',
183
+ message: 'MCP server found and accessible',
184
+ details: `Server path: ${serverPath}`,
185
+ });
186
+ }
187
+ else {
188
+ results.push({
189
+ component: 'MCP Server Detection',
190
+ status: 'fail',
191
+ message: 'MCP server not found',
192
+ details: 'Could not locate embedded MCP server',
193
+ });
194
+ }
195
+ }
196
+ catch (error) {
197
+ results.push({
198
+ component: 'MCP Server Detection',
199
+ status: 'fail',
200
+ message: 'MCP server detection failed',
201
+ details: error instanceof Error ? error.message : String(error),
202
+ });
203
+ }
204
+ // Test 2: Text Input Handler
205
+ try {
206
+ const handler = new TextInputHandlerImpl();
207
+ // Test basic functionality without actually prompting user
208
+ const session = handler.getCurrentSession();
209
+ results.push({
210
+ component: 'Text Input Handler',
211
+ status: 'pass',
212
+ message: 'Text input handler initialized successfully',
213
+ });
214
+ }
215
+ catch (error) {
216
+ results.push({
217
+ component: 'Text Input Handler',
218
+ status: 'fail',
219
+ message: 'Text input handler initialization failed',
220
+ details: error instanceof Error ? error.message : String(error),
221
+ });
222
+ }
223
+ // Test 3: File System Permissions
224
+ try {
225
+ const testPath = join(dirname(this.configPath), 'test-write.tmp');
226
+ await fs.writeFile(testPath, 'test');
227
+ await fs.unlink(testPath);
228
+ results.push({
229
+ component: 'File System Permissions',
230
+ status: 'pass',
231
+ message: 'Configuration directory is writable',
232
+ });
233
+ }
234
+ catch (error) {
235
+ results.push({
236
+ component: 'File System Permissions',
237
+ status: 'fail',
238
+ message: 'Cannot write to configuration directory',
239
+ details: error instanceof Error ? error.message : String(error),
240
+ });
241
+ }
242
+ // Test 4: Terminal Capabilities
243
+ try {
244
+ const isTTY = process.stdin.isTTY && process.stdout.isTTY;
245
+ if (isTTY) {
246
+ results.push({
247
+ component: 'Terminal Capabilities',
248
+ status: 'pass',
249
+ message: 'Terminal supports interactive input',
250
+ });
251
+ }
252
+ else {
253
+ results.push({
254
+ component: 'Terminal Capabilities',
255
+ status: 'warning',
256
+ message: 'Terminal may not support interactive input',
257
+ details: 'Some features may be limited in non-TTY environments',
258
+ });
259
+ }
260
+ }
261
+ catch (error) {
262
+ results.push({
263
+ component: 'Terminal Capabilities',
264
+ status: 'fail',
265
+ message: 'Terminal capability check failed',
266
+ details: error instanceof Error ? error.message : String(error),
267
+ });
268
+ }
269
+ return results;
270
+ }
271
+ /**
272
+ * Show welcome demonstration of key features
273
+ */
274
+ async showWelcomeDemo() {
275
+ console.log('\n๐ŸŽฏ Welcome to LanOnasis CLI!');
276
+ console.log('Here are some key features you can use:');
277
+ console.log('');
278
+ console.log('๐Ÿ“ Memory Management:');
279
+ console.log(' โ€ข lanonasis memory create - Create new memories with inline text input');
280
+ console.log(' โ€ข lanonasis memory list - List your existing memories');
281
+ console.log(' โ€ข lanonasis memory search - Search through your memories');
282
+ console.log('');
283
+ console.log('๐Ÿ”Œ MCP Integration:');
284
+ console.log(' โ€ข lanonasis mcp connect --local - Connect to local MCP server');
285
+ console.log(' โ€ข lanonasis mcp status - Check MCP connection status');
286
+ console.log('');
287
+ console.log('โš™๏ธ Configuration:');
288
+ console.log(' โ€ข lanonasis config show - View current configuration');
289
+ console.log(' โ€ข lanonasis health - Check system health');
290
+ console.log('');
291
+ console.log('๐Ÿ’ก Pro Tips:');
292
+ console.log(' โ€ข Use Ctrl+D to finish multi-line input');
293
+ console.log(' โ€ข Use Ctrl+C to cancel operations');
294
+ console.log(' โ€ข Add --help to any command for detailed usage');
295
+ console.log('');
296
+ console.log('๐Ÿ“š Need help? Visit: https://api.lanonasis.com/docs');
297
+ console.log('');
298
+ // Simulate a brief pause for user to read
299
+ await new Promise((resolve) => setTimeout(resolve, 2000));
300
+ }
301
+ /**
302
+ * Get the current onboarding state
303
+ */
304
+ getOnboardingState() {
305
+ return { ...this.onboardingState };
306
+ }
307
+ /**
308
+ * Update user preferences during onboarding
309
+ */
310
+ async updateUserPreferences(preferences) {
311
+ const sanitizedPreferences = Object.fromEntries(Object.entries(preferences).filter(([, value]) => value !== undefined && value !== null));
312
+ this.onboardingState.userPreferences = {
313
+ ...this.onboardingState.userPreferences,
314
+ ...sanitizedPreferences,
315
+ };
316
+ await this.saveOnboardingState();
317
+ }
318
+ /**
319
+ * Skip the current onboarding step
320
+ */
321
+ async skipCurrentStep(reason) {
322
+ const stepName = `step-${this.onboardingState.currentStep}`;
323
+ this.onboardingState.skippedSteps.push(stepName);
324
+ if (reason) {
325
+ console.log(`โญ๏ธ Skipping step: ${reason}`);
326
+ }
327
+ this.onboardingState.currentStep++;
328
+ await this.saveOnboardingState();
329
+ }
330
+ /**
331
+ * Complete the onboarding process
332
+ */
333
+ async completeOnboarding() {
334
+ this.onboardingState.currentStep = this.onboardingState.totalSteps;
335
+ this.onboardingState.isFirstRun = false;
336
+ // Save final state
337
+ await this.saveOnboardingState();
338
+ // Save user preferences to CLI config
339
+ const cliConfigPath = join(dirname(this.configPath), 'config.json');
340
+ try {
341
+ const existingConfig = JSON.parse(await fs.readFile(cliConfigPath, 'utf-8'));
342
+ const updatedConfig = {
343
+ ...existingConfig,
344
+ userPreferences: this.onboardingState.userPreferences,
345
+ };
346
+ await fs.writeFile(cliConfigPath, JSON.stringify(updatedConfig, null, 2));
347
+ }
348
+ catch {
349
+ // If config doesn't exist, create it with preferences
350
+ const config = {
351
+ userPreferences: this.onboardingState.userPreferences,
352
+ };
353
+ await fs.writeFile(cliConfigPath, JSON.stringify(config, null, 2));
354
+ }
355
+ }
356
+ /**
357
+ * Reset onboarding state (for testing or re-running)
358
+ */
359
+ async resetOnboarding() {
360
+ this.onboardingState = {
361
+ isFirstRun: true,
362
+ currentStep: 0,
363
+ totalSteps: 5,
364
+ completedSteps: [],
365
+ skippedSteps: [],
366
+ userPreferences: { ...DEFAULT_USER_PREFERENCES },
367
+ };
368
+ try {
369
+ await fs.unlink(this.configPath);
370
+ }
371
+ catch {
372
+ // File doesn't exist, that's fine
373
+ }
374
+ }
375
+ /**
376
+ * Gather user preferences interactively
377
+ */
378
+ async gatherUserPreferences() {
379
+ console.log("Let's configure your preferences...");
380
+ // For now, use defaults. In a full implementation, this would
381
+ // use the TextInputHandler to gather user preferences interactively
382
+ console.log('โœ… Using recommended default preferences');
383
+ console.log(` โ€ข Input mode: ${this.onboardingState.userPreferences.inputMode}`);
384
+ console.log(` โ€ข Auto-start MCP: ${this.onboardingState.userPreferences.autoStartMCP}`);
385
+ console.log(` โ€ข Show tips: ${this.onboardingState.userPreferences.showOnboardingTips}`);
386
+ }
387
+ /**
388
+ * Save the current onboarding state to disk
389
+ */
390
+ async saveOnboardingState() {
391
+ try {
392
+ await fs.mkdir(dirname(this.configPath), { recursive: true });
393
+ await fs.writeFile(this.configPath, JSON.stringify(this.onboardingState, null, 2));
394
+ }
395
+ catch (error) {
396
+ console.warn('Warning: Could not save onboarding state:', error);
397
+ }
398
+ }
399
+ /**
400
+ * Load onboarding state from disk
401
+ */
402
+ async loadOnboardingState() {
403
+ try {
404
+ const stateData = await fs.readFile(this.configPath, 'utf-8');
405
+ const loadedState = JSON.parse(stateData);
406
+ this.onboardingState = {
407
+ ...this.onboardingState,
408
+ ...loadedState,
409
+ };
410
+ }
411
+ catch {
412
+ // State file doesn't exist or is invalid, use defaults
413
+ }
414
+ }
415
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Text Input Handler Implementation
3
+ *
4
+ * Provides seamless multi-line text input without external editors
5
+ * Implementation of the TextInputHandler interface.
6
+ */
7
+ import { TextInputHandler, KeyEvent, InputOptions, InputSession } from '../interfaces/TextInputHandler.js';
8
+ /**
9
+ * TextInputHandlerImpl provides seamless multi-line text input without external editors
10
+ *
11
+ * This implementation uses process.stdin.setRawMode(true) to capture individual keystrokes
12
+ * and handles special characters manually to provide a smooth editing experience.
13
+ */
14
+ export declare class TextInputHandlerImpl implements TextInputHandler {
15
+ private currentSession;
16
+ private isRawModeEnabled;
17
+ private originalStdinMode;
18
+ /**
19
+ * Collect multi-line text input from the user
20
+ */
21
+ collectMultilineInput(prompt: string, options?: InputOptions): Promise<string>;
22
+ /**
23
+ * Enable raw mode for direct keystroke capture
24
+ */
25
+ enableRawMode(): void;
26
+ /**
27
+ * Disable raw mode and return to normal terminal behavior
28
+ */
29
+ disableRawMode(): void;
30
+ /**
31
+ * Handle special keyboard events
32
+ */
33
+ handleSpecialKeys(key: KeyEvent): boolean;
34
+ /**
35
+ * Display the input prompt with current content
36
+ */
37
+ displayInputPrompt(content: string): void;
38
+ /**
39
+ * Get the current input session
40
+ */
41
+ getCurrentSession(): InputSession | null;
42
+ /**
43
+ * Cancel the current input session
44
+ */
45
+ cancelInput(): void;
46
+ /**
47
+ * Parse raw key event from buffer
48
+ */
49
+ private parseKeyEvent;
50
+ /**
51
+ * Check if a key event matches a key pattern
52
+ */
53
+ private matchesKey;
54
+ /**
55
+ * Add a character to the current input
56
+ */
57
+ private addCharacterToInput;
58
+ /**
59
+ * Add a new line to the input
60
+ */
61
+ private addNewLine;
62
+ /**
63
+ * Handle backspace key
64
+ */
65
+ private handleBackspace;
66
+ /**
67
+ * Handle arrow key navigation
68
+ */
69
+ private handleArrowKey;
70
+ /**
71
+ * Get the current content as a string
72
+ */
73
+ private getCurrentContent;
74
+ }