@lanonasis/cli 1.0.1 → 1.2.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.
@@ -1,24 +1,164 @@
1
1
  import { AxiosRequestConfig } from 'axios';
2
+ export interface AuthResponse {
3
+ user: {
4
+ id: string;
5
+ email: string;
6
+ organization_id: string;
7
+ role: 'admin' | 'user' | 'viewer';
8
+ plan: 'free' | 'pro' | 'enterprise';
9
+ created_at: string;
10
+ updated_at: string;
11
+ };
12
+ token: string;
13
+ expires_at: string;
14
+ }
15
+ export interface RegisterRequest {
16
+ email: string;
17
+ password: string;
18
+ organization_name?: string;
19
+ }
20
+ export interface LoginRequest {
21
+ email: string;
22
+ password: string;
23
+ }
24
+ export type MemoryType = 'context' | 'project' | 'knowledge' | 'reference' | 'personal' | 'workflow';
25
+ export interface MemoryEntry {
26
+ id: string;
27
+ title: string;
28
+ content: string;
29
+ memory_type: MemoryType;
30
+ tags: string[];
31
+ topic_id?: string | null;
32
+ user_id: string;
33
+ organization_id: string;
34
+ metadata?: Record<string, unknown>;
35
+ created_at: string;
36
+ updated_at: string;
37
+ last_accessed?: string;
38
+ access_count: number;
39
+ }
40
+ export interface CreateMemoryRequest {
41
+ title: string;
42
+ content: string;
43
+ memory_type?: MemoryType;
44
+ tags?: string[];
45
+ topic_id?: string;
46
+ metadata?: Record<string, unknown>;
47
+ }
48
+ export interface UpdateMemoryRequest {
49
+ title?: string;
50
+ content?: string;
51
+ memory_type?: MemoryType;
52
+ tags?: string[];
53
+ topic_id?: string | null;
54
+ metadata?: Record<string, unknown>;
55
+ }
56
+ export interface GetMemoriesParams {
57
+ limit?: number;
58
+ offset?: number;
59
+ memory_type?: MemoryType;
60
+ tags?: string[];
61
+ topic_id?: string;
62
+ sort_by?: 'created_at' | 'updated_at' | 'last_accessed' | 'access_count';
63
+ sort_order?: 'asc' | 'desc';
64
+ }
65
+ export interface SearchMemoryRequest {
66
+ query: string;
67
+ memory_types?: MemoryType[];
68
+ tags?: string[];
69
+ topic_id?: string;
70
+ limit?: number;
71
+ threshold?: number;
72
+ }
73
+ export interface MemorySearchResult extends MemoryEntry {
74
+ relevance_score: number;
75
+ }
76
+ export interface MemoryStats {
77
+ total_memories: number;
78
+ memories_by_type: Record<MemoryType, number>;
79
+ total_size_bytes: number;
80
+ avg_access_count: number;
81
+ most_accessed_memory?: MemoryEntry;
82
+ recent_memories: MemoryEntry[];
83
+ }
84
+ export interface BulkDeleteRequest {
85
+ memory_ids: string[];
86
+ }
87
+ export interface BulkDeleteResponse {
88
+ deleted_count: number;
89
+ failed_deletes?: string[];
90
+ }
91
+ export interface MemoryTopic {
92
+ id: string;
93
+ name: string;
94
+ description?: string;
95
+ color?: string;
96
+ icon?: string;
97
+ user_id: string;
98
+ parent_topic_id?: string;
99
+ is_system: boolean;
100
+ metadata?: Record<string, unknown>;
101
+ created_at: string;
102
+ updated_at: string;
103
+ }
104
+ export interface CreateTopicRequest {
105
+ name: string;
106
+ description?: string;
107
+ color?: string;
108
+ icon?: string;
109
+ parent_topic_id?: string;
110
+ }
111
+ export interface UpdateTopicRequest {
112
+ name?: string;
113
+ description?: string;
114
+ color?: string;
115
+ icon?: string;
116
+ parent_topic_id?: string;
117
+ }
118
+ export interface HealthStatus {
119
+ status: 'healthy' | 'unhealthy';
120
+ version: string;
121
+ timestamp: string;
122
+ dependencies: Record<string, {
123
+ status: string;
124
+ latency_ms?: number;
125
+ }>;
126
+ }
127
+ export interface PaginatedResponse<T> {
128
+ data: T[];
129
+ pagination: {
130
+ total: number;
131
+ limit: number;
132
+ offset: number;
133
+ has_more: boolean;
134
+ };
135
+ }
136
+ export interface ApiErrorResponse {
137
+ error: string;
138
+ message: string;
139
+ status_code: number;
140
+ details?: Record<string, unknown>;
141
+ }
2
142
  export declare class APIClient {
3
143
  private client;
4
144
  private config;
5
145
  constructor();
6
- login(email: string, password: string): Promise<any>;
7
- register(email: string, password: string, organizationName?: string): Promise<any>;
8
- createMemory(data: any): Promise<any>;
9
- getMemories(params?: any): Promise<any>;
10
- getMemory(id: string): Promise<any>;
11
- updateMemory(id: string, data: any): Promise<any>;
146
+ login(email: string, password: string): Promise<AuthResponse>;
147
+ register(email: string, password: string, organizationName?: string): Promise<AuthResponse>;
148
+ createMemory(data: CreateMemoryRequest): Promise<MemoryEntry>;
149
+ getMemories(params?: GetMemoriesParams): Promise<PaginatedResponse<MemoryEntry>>;
150
+ getMemory(id: string): Promise<MemoryEntry>;
151
+ updateMemory(id: string, data: UpdateMemoryRequest): Promise<MemoryEntry>;
12
152
  deleteMemory(id: string): Promise<void>;
13
- searchMemories(query: string, options?: any): Promise<any>;
14
- getMemoryStats(): Promise<any>;
15
- bulkDeleteMemories(memoryIds: string[]): Promise<any>;
16
- createTopic(data: any): Promise<any>;
17
- getTopics(): Promise<any>;
18
- getTopic(id: string): Promise<any>;
19
- updateTopic(id: string, data: any): Promise<any>;
153
+ searchMemories(query: string, options?: Omit<SearchMemoryRequest, 'query'>): Promise<PaginatedResponse<MemorySearchResult>>;
154
+ getMemoryStats(): Promise<MemoryStats>;
155
+ bulkDeleteMemories(memoryIds: string[]): Promise<BulkDeleteResponse>;
156
+ createTopic(data: CreateTopicRequest): Promise<MemoryTopic>;
157
+ getTopics(): Promise<MemoryTopic[]>;
158
+ getTopic(id: string): Promise<MemoryTopic>;
159
+ updateTopic(id: string, data: UpdateTopicRequest): Promise<MemoryTopic>;
20
160
  deleteTopic(id: string): Promise<void>;
21
- getHealth(): Promise<any>;
22
- request<T = any>(config: AxiosRequestConfig): Promise<T>;
161
+ getHealth(): Promise<HealthStatus>;
162
+ request<T = Record<string, unknown>>(config: AxiosRequestConfig): Promise<T>;
23
163
  }
24
164
  export declare const apiClient: APIClient;
@@ -22,5 +22,11 @@ export declare class CLIConfig {
22
22
  clear(): Promise<void>;
23
23
  getConfigPath(): string;
24
24
  exists(): Promise<boolean>;
25
+ get(key: string): any;
26
+ set(key: string, value: any): void;
27
+ setAndSave(key: string, value: any): Promise<void>;
28
+ getMCPServerPath(): string;
29
+ getMCPServerUrl(): string;
30
+ shouldUseRemoteMCP(): boolean;
25
31
  }
26
32
  export {};
@@ -1,7 +1,10 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import os from 'os';
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
4
  import { jwtDecode } from 'jwt-decode';
5
+ import { fileURLToPath } from 'url';
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
5
8
  export class CLIConfig {
6
9
  configDir;
7
10
  configPath;
@@ -15,7 +18,7 @@ export class CLIConfig {
15
18
  await fs.mkdir(this.configDir, { recursive: true });
16
19
  await this.load();
17
20
  }
18
- catch (error) {
21
+ catch {
19
22
  // Config doesn't exist yet, that's ok
20
23
  }
21
24
  }
@@ -24,7 +27,7 @@ export class CLIConfig {
24
27
  const data = await fs.readFile(this.configPath, 'utf-8');
25
28
  this.config = JSON.parse(data);
26
29
  }
27
- catch (error) {
30
+ catch {
28
31
  this.config = {};
29
32
  }
30
33
  }
@@ -50,13 +53,13 @@ export class CLIConfig {
50
53
  // We'll need to fetch full user details from the API
51
54
  // For now, store what we can decode
52
55
  this.config.user = {
53
- email: decoded.email || '',
54
- organization_id: decoded.organizationId || '',
55
- role: decoded.role || '',
56
- plan: decoded.plan || ''
56
+ email: String(decoded.email || ''),
57
+ organization_id: String(decoded.organizationId || ''),
58
+ role: String(decoded.role || ''),
59
+ plan: String(decoded.plan || '')
57
60
  };
58
61
  }
59
- catch (error) {
62
+ catch {
60
63
  // Invalid token, don't store user info
61
64
  }
62
65
  await this.save();
@@ -74,9 +77,9 @@ export class CLIConfig {
74
77
  try {
75
78
  const decoded = jwtDecode(token);
76
79
  const now = Date.now() / 1000;
77
- return decoded.exp > now;
80
+ return typeof decoded.exp === 'number' && decoded.exp > now;
78
81
  }
79
- catch (error) {
82
+ catch {
80
83
  return false;
81
84
  }
82
85
  }
@@ -101,4 +104,35 @@ export class CLIConfig {
101
104
  return false;
102
105
  }
103
106
  }
107
+ // Generic get/set methods for MCP and other dynamic config
108
+ get(key) {
109
+ return this.config[key];
110
+ }
111
+ set(key, value) {
112
+ this.config[key] = value;
113
+ }
114
+ async setAndSave(key, value) {
115
+ this.set(key, value);
116
+ await this.save();
117
+ }
118
+ // MCP-specific helpers
119
+ getMCPServerPath() {
120
+ return this.config.mcpServerPath || path.join(__dirname, '../../../../onasis-gateway/mcp-server/server.js');
121
+ }
122
+ getMCPServerUrl() {
123
+ return this.config.mcpServerUrl || 'https://api.lanonasis.com';
124
+ }
125
+ shouldUseRemoteMCP() {
126
+ const preference = this.config.mcpPreference || 'auto';
127
+ switch (preference) {
128
+ case 'remote':
129
+ return true;
130
+ case 'local':
131
+ return false;
132
+ case 'auto':
133
+ default:
134
+ // Use remote if authenticated, otherwise local
135
+ return !!this.config.token;
136
+ }
137
+ }
104
138
  }
@@ -1,4 +1,4 @@
1
- export declare function formatOutput(data: any, format?: string): void;
1
+ export declare function formatOutput(data: unknown, format?: string): void;
2
2
  export declare function formatBytes(bytes: number): string;
3
3
  export declare function truncateText(text: string, maxLength: number): string;
4
4
  export declare function formatDuration(ms: number): string;
@@ -0,0 +1,53 @@
1
+ interface MCPConnectionOptions {
2
+ serverPath?: string;
3
+ serverUrl?: string;
4
+ useRemote?: boolean;
5
+ }
6
+ export declare class MCPClient {
7
+ private client;
8
+ private config;
9
+ private isConnected;
10
+ private sseConnection;
11
+ constructor();
12
+ /**
13
+ * Connect to MCP server (local or remote)
14
+ */
15
+ connect(options?: MCPConnectionOptions): Promise<boolean>;
16
+ /**
17
+ * Initialize SSE connection for real-time updates
18
+ */
19
+ private initializeSSE;
20
+ /**
21
+ * Disconnect from MCP server
22
+ */
23
+ disconnect(): Promise<void>;
24
+ /**
25
+ * Call an MCP tool
26
+ */
27
+ callTool(toolName: string, args: any): Promise<any>;
28
+ /**
29
+ * Call remote tool via REST API with MCP interface
30
+ */
31
+ private callRemoteTool;
32
+ /**
33
+ * List available tools
34
+ */
35
+ listTools(): Promise<Array<{
36
+ name: string;
37
+ description: string;
38
+ }>>;
39
+ /**
40
+ * Check if connected to MCP server
41
+ */
42
+ isConnectedToServer(): boolean;
43
+ /**
44
+ * Get connection status details
45
+ */
46
+ getConnectionStatus(): {
47
+ connected: boolean;
48
+ mode: string;
49
+ server?: string;
50
+ };
51
+ }
52
+ export declare function getMCPClient(): MCPClient;
53
+ export {};
@@ -0,0 +1,249 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
+ import chalk from 'chalk';
4
+ import { CLIConfig } from './config.js';
5
+ import * as path from 'path';
6
+ import { EventSource } from 'eventsource';
7
+ import { fileURLToPath } from 'url';
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ export class MCPClient {
11
+ client = null;
12
+ config;
13
+ isConnected = false;
14
+ sseConnection = null;
15
+ constructor() {
16
+ this.config = new CLIConfig();
17
+ }
18
+ /**
19
+ * Connect to MCP server (local or remote)
20
+ */
21
+ async connect(options = {}) {
22
+ try {
23
+ const useRemote = options.useRemote ?? this.config.get('mcpUseRemote') ?? false;
24
+ if (useRemote) {
25
+ // For remote MCP, we'll use the REST API with MCP-style interface
26
+ const serverUrl = options.serverUrl ?? this.config.get('mcpServerUrl') ?? 'https://api.lanonasis.com';
27
+ console.log(chalk.cyan(`Connecting to remote MCP server at ${serverUrl}...`));
28
+ // Initialize SSE connection for real-time updates
29
+ await this.initializeSSE(serverUrl);
30
+ this.isConnected = true;
31
+ return true;
32
+ }
33
+ else {
34
+ // Local MCP server connection
35
+ const serverPath = options.serverPath ?? this.config.get('mcpServerPath') ?? path.join(__dirname, '../../../../onasis-gateway/mcp-server/server.js');
36
+ console.log(chalk.cyan(`Connecting to local MCP server at ${serverPath}...`));
37
+ const transport = new StdioClientTransport({
38
+ command: 'node',
39
+ args: [serverPath]
40
+ });
41
+ this.client = new Client({
42
+ name: '@lanonasis/cli',
43
+ version: '1.0.0'
44
+ }, {
45
+ capabilities: {}
46
+ });
47
+ await this.client.connect(transport);
48
+ this.isConnected = true;
49
+ console.log(chalk.green('✓ Connected to MCP server'));
50
+ return true;
51
+ }
52
+ }
53
+ catch (error) {
54
+ console.error(chalk.red('Failed to connect to MCP server:'), error);
55
+ this.isConnected = false;
56
+ return false;
57
+ }
58
+ }
59
+ /**
60
+ * Initialize SSE connection for real-time updates
61
+ */
62
+ async initializeSSE(serverUrl) {
63
+ const sseUrl = `${serverUrl}/sse`;
64
+ const token = this.config.get('token');
65
+ if (token) {
66
+ // EventSource doesn't support headers directly, append token to URL
67
+ this.sseConnection = new EventSource(`${sseUrl}?token=${encodeURIComponent(token)}`);
68
+ this.sseConnection.onmessage = (event) => {
69
+ try {
70
+ const data = JSON.parse(event.data);
71
+ console.log(chalk.blue('📡 Real-time update:'), data.type);
72
+ }
73
+ catch (error) {
74
+ // Ignore parse errors
75
+ }
76
+ };
77
+ this.sseConnection.onerror = (error) => {
78
+ console.error(chalk.yellow('⚠️ SSE connection error (will retry)'));
79
+ };
80
+ }
81
+ }
82
+ /**
83
+ * Disconnect from MCP server
84
+ */
85
+ async disconnect() {
86
+ if (this.client) {
87
+ await this.client.close();
88
+ this.client = null;
89
+ }
90
+ if (this.sseConnection) {
91
+ this.sseConnection.close();
92
+ this.sseConnection = null;
93
+ }
94
+ this.isConnected = false;
95
+ }
96
+ /**
97
+ * Call an MCP tool
98
+ */
99
+ async callTool(toolName, args) {
100
+ if (!this.isConnected) {
101
+ throw new Error('Not connected to MCP server. Run "lanonasis mcp connect" first.');
102
+ }
103
+ const useRemote = this.config.get('mcpUseRemote') ?? false;
104
+ if (useRemote) {
105
+ // Remote MCP calls are translated to REST API calls
106
+ return await this.callRemoteTool(toolName, args);
107
+ }
108
+ else {
109
+ // Local MCP server call
110
+ if (!this.client) {
111
+ throw new Error('MCP client not initialized');
112
+ }
113
+ try {
114
+ const result = await this.client.callTool({
115
+ name: toolName,
116
+ arguments: args
117
+ });
118
+ return result;
119
+ }
120
+ catch (error) {
121
+ throw new Error(`MCP tool call failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
122
+ }
123
+ }
124
+ }
125
+ /**
126
+ * Call remote tool via REST API with MCP interface
127
+ */
128
+ async callRemoteTool(toolName, args) {
129
+ const apiUrl = this.config.get('apiUrl') ?? 'https://api.lanonasis.com';
130
+ const token = this.config.get('token');
131
+ if (!token) {
132
+ throw new Error('Authentication required. Run "lanonasis auth login" first.');
133
+ }
134
+ // Map MCP tool names to REST API endpoints
135
+ const toolMappings = {
136
+ 'memory_create_memory': {
137
+ method: 'POST',
138
+ endpoint: '/api/v1/memory',
139
+ transform: (args) => args
140
+ },
141
+ 'memory_search_memories': {
142
+ method: 'POST',
143
+ endpoint: '/api/v1/memory/search',
144
+ transform: (args) => args
145
+ },
146
+ 'memory_get_memory': {
147
+ method: 'GET',
148
+ endpoint: `/api/v1/memory/${args.memory_id}`,
149
+ transform: () => undefined
150
+ },
151
+ 'memory_update_memory': {
152
+ method: 'PUT',
153
+ endpoint: `/api/v1/memory/${args.memory_id}`,
154
+ transform: (args) => {
155
+ const { memory_id, ...data } = args;
156
+ return data;
157
+ }
158
+ },
159
+ 'memory_delete_memory': {
160
+ method: 'DELETE',
161
+ endpoint: `/api/v1/memory/${args.memory_id}`,
162
+ transform: () => undefined
163
+ },
164
+ 'memory_list_memories': {
165
+ method: 'GET',
166
+ endpoint: '/api/v1/memory',
167
+ transform: (args) => args
168
+ }
169
+ };
170
+ const mapping = toolMappings[toolName];
171
+ if (!mapping) {
172
+ throw new Error(`Unknown tool: ${toolName}`);
173
+ }
174
+ try {
175
+ const axios = (await import('axios')).default;
176
+ const response = await axios({
177
+ method: mapping.method,
178
+ url: `${apiUrl}${mapping.endpoint}`,
179
+ headers: {
180
+ 'Authorization': `Bearer ${token}`,
181
+ 'Content-Type': 'application/json'
182
+ },
183
+ data: mapping.transform ? mapping.transform(args) : undefined,
184
+ params: mapping.method === 'GET' ? args : undefined
185
+ });
186
+ return response.data;
187
+ }
188
+ catch (error) {
189
+ throw new Error(`Remote tool call failed: ${error.response?.data?.error || error.message}`);
190
+ }
191
+ }
192
+ /**
193
+ * List available tools
194
+ */
195
+ async listTools() {
196
+ if (!this.isConnected) {
197
+ throw new Error('Not connected to MCP server');
198
+ }
199
+ const useRemote = this.config.get('mcpUseRemote') ?? false;
200
+ if (useRemote) {
201
+ // Return hardcoded list for remote mode
202
+ return [
203
+ { name: 'memory_create_memory', description: 'Create a new memory entry' },
204
+ { name: 'memory_search_memories', description: 'Search memories using semantic search' },
205
+ { name: 'memory_get_memory', description: 'Get a specific memory by ID' },
206
+ { name: 'memory_update_memory', description: 'Update an existing memory' },
207
+ { name: 'memory_delete_memory', description: 'Delete a memory' },
208
+ { name: 'memory_list_memories', description: 'List all memories with pagination' }
209
+ ];
210
+ }
211
+ else {
212
+ if (!this.client) {
213
+ throw new Error('MCP client not initialized');
214
+ }
215
+ const tools = await this.client.listTools();
216
+ return tools.tools.map(tool => ({
217
+ name: tool.name,
218
+ description: tool.description || 'No description available'
219
+ }));
220
+ }
221
+ }
222
+ /**
223
+ * Check if connected to MCP server
224
+ */
225
+ isConnectedToServer() {
226
+ return this.isConnected;
227
+ }
228
+ /**
229
+ * Get connection status details
230
+ */
231
+ getConnectionStatus() {
232
+ const useRemote = this.config.get('mcpUseRemote') ?? false;
233
+ return {
234
+ connected: this.isConnected,
235
+ mode: useRemote ? 'remote' : 'local',
236
+ server: useRemote
237
+ ? (this.config.get('mcpServerUrl') ?? 'https://api.lanonasis.com')
238
+ : (this.config.get('mcpServerPath') ?? 'local MCP server')
239
+ };
240
+ }
241
+ }
242
+ // Singleton instance
243
+ let mcpClientInstance = null;
244
+ export function getMCPClient() {
245
+ if (!mcpClientInstance) {
246
+ mcpClientInstance = new MCPClient();
247
+ }
248
+ return mcpClientInstance;
249
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lanonasis/cli",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Lanonasis Enterprise CLI - Memory as a Service and Infrastructure Management",
5
5
  "main": "dist/index-simple.js",
6
6
  "bin": {
@@ -34,11 +34,24 @@
34
34
  "README.md"
35
35
  ],
36
36
  "dependencies": {
37
- "commander": "^12.1.0"
37
+ "@modelcontextprotocol/sdk": "^1.17.0",
38
+ "@types/eventsource": "^1.1.15",
39
+ "axios": "^1.11.0",
40
+ "chalk": "^5.4.1",
41
+ "commander": "^12.1.0",
42
+ "date-fns": "^4.1.0",
43
+ "dotenv": "^17.2.1",
44
+ "eventsource": "^4.0.0",
45
+ "inquirer": "^12.9.0",
46
+ "jwt-decode": "^4.0.0",
47
+ "open": "^10.2.0",
48
+ "ora": "^8.2.0",
49
+ "table": "^6.9.0",
50
+ "word-wrap": "^1.2.5"
38
51
  },
39
52
  "devDependencies": {
40
- "@types/node": "^22.10.2",
41
53
  "@types/inquirer": "^9.0.7",
54
+ "@types/node": "^22.10.2",
42
55
  "@typescript-eslint/eslint-plugin": "^8.18.1",
43
56
  "@typescript-eslint/parser": "^8.18.1",
44
57
  "eslint": "^9.17.0",
@@ -48,4 +61,4 @@
48
61
  "engines": {
49
62
  "node": ">=18.0.0"
50
63
  }
51
- }
64
+ }