@lanonasis/cli 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/dist/index.js ADDED
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import { config } from 'dotenv';
5
+ import { initCommand } from './commands/init.js';
6
+ import { loginCommand } from './commands/auth.js';
7
+ import { memoryCommands } from './commands/memory.js';
8
+ import { topicCommands } from './commands/topics.js';
9
+ import { configCommands } from './commands/config.js';
10
+ import { orgCommands } from './commands/organization.js';
11
+ import { CLIConfig } from './utils/config.js';
12
+ // Load environment variables
13
+ config();
14
+ const program = new Command();
15
+ // CLI Configuration
16
+ const cliConfig = new CLIConfig();
17
+ program
18
+ .name('memory')
19
+ .alias('maas')
20
+ .description('Enterprise Memory as a Service (MaaS) CLI')
21
+ .version('1.0.0')
22
+ .option('-v, --verbose', 'enable verbose logging')
23
+ .option('--api-url <url>', 'override API URL')
24
+ .option('--output <format>', 'output format (json, table, yaml)', 'table')
25
+ .hook('preAction', (thisCommand, actionCommand) => {
26
+ const opts = thisCommand.opts();
27
+ if (opts.verbose) {
28
+ process.env.CLI_VERBOSE = 'true';
29
+ }
30
+ if (opts.apiUrl) {
31
+ process.env.MEMORY_API_URL = opts.apiUrl;
32
+ }
33
+ process.env.CLI_OUTPUT_FORMAT = opts.output;
34
+ });
35
+ // Global error handler
36
+ process.on('uncaughtException', (error) => {
37
+ console.error(chalk.red('✖ Unexpected error:'), error.message);
38
+ if (process.env.CLI_VERBOSE === 'true') {
39
+ console.error(error.stack);
40
+ }
41
+ process.exit(1);
42
+ });
43
+ process.on('unhandledRejection', (reason, promise) => {
44
+ console.error(chalk.red('✖ Unhandled promise rejection:'), reason);
45
+ if (process.env.CLI_VERBOSE === 'true') {
46
+ console.error(promise);
47
+ }
48
+ process.exit(1);
49
+ });
50
+ // Welcome message for first-time users
51
+ const showWelcome = () => {
52
+ console.log(chalk.blue.bold('🧠 Memory as a Service (MaaS) CLI'));
53
+ console.log(chalk.gray('Enterprise-grade memory management for AI applications'));
54
+ console.log();
55
+ console.log(chalk.yellow('Get started:'));
56
+ console.log(chalk.white(' memory init # Initialize CLI configuration'));
57
+ console.log(chalk.white(' memory login # Authenticate with Supabase account'));
58
+ console.log(chalk.white(' memory --help # Show all available commands'));
59
+ console.log();
60
+ };
61
+ // Check if user is authenticated for protected commands
62
+ const requireAuth = (command) => {
63
+ command.hook('preAction', async () => {
64
+ const isAuthenticated = await cliConfig.isAuthenticated();
65
+ if (!isAuthenticated) {
66
+ console.error(chalk.red('✖ Authentication required'));
67
+ console.log(chalk.yellow('Please run:'), chalk.white('memory login'));
68
+ process.exit(1);
69
+ }
70
+ });
71
+ };
72
+ // Initialize command (no auth required)
73
+ program
74
+ .command('init')
75
+ .description('Initialize CLI configuration')
76
+ .option('-f, --force', 'overwrite existing configuration')
77
+ .action(initCommand);
78
+ // Authentication commands (no auth required)
79
+ const authCmd = program
80
+ .command('auth')
81
+ .alias('login')
82
+ .description('Authentication commands');
83
+ authCmd
84
+ .command('login')
85
+ .description('Login to your MaaS account')
86
+ .option('-e, --email <email>', 'email address')
87
+ .option('-p, --password <password>', 'password')
88
+ .action(loginCommand);
89
+ authCmd
90
+ .command('logout')
91
+ .description('Logout from your account')
92
+ .action(async () => {
93
+ await cliConfig.logout();
94
+ console.log(chalk.green('✓ Logged out successfully'));
95
+ });
96
+ authCmd
97
+ .command('status')
98
+ .description('Show authentication status')
99
+ .action(async () => {
100
+ const isAuth = await cliConfig.isAuthenticated();
101
+ const user = await cliConfig.getCurrentUser();
102
+ if (isAuth && user) {
103
+ console.log(chalk.green('✓ Authenticated'));
104
+ console.log(`Email: ${user.email}`);
105
+ console.log(`Organization: ${user.organization_id}`);
106
+ console.log(`Plan: ${user.plan}`);
107
+ }
108
+ else {
109
+ console.log(chalk.red('✖ Not authenticated'));
110
+ console.log(chalk.yellow('Run:'), chalk.white('memory login'));
111
+ }
112
+ });
113
+ // Memory commands (require auth)
114
+ const memoryCmd = program
115
+ .command('memory')
116
+ .alias('mem')
117
+ .description('Memory management commands');
118
+ requireAuth(memoryCmd);
119
+ memoryCommands(memoryCmd);
120
+ // Topic commands (require auth)
121
+ const topicCmd = program
122
+ .command('topic')
123
+ .alias('topics')
124
+ .description('Topic management commands');
125
+ requireAuth(topicCmd);
126
+ topicCommands(topicCmd);
127
+ // Configuration commands (require auth)
128
+ const configCmd = program
129
+ .command('config')
130
+ .description('Configuration management');
131
+ requireAuth(configCmd);
132
+ configCommands(configCmd);
133
+ // Organization commands (require auth)
134
+ const orgCmd = program
135
+ .command('org')
136
+ .alias('organization')
137
+ .description('Organization management');
138
+ requireAuth(orgCmd);
139
+ orgCommands(orgCmd);
140
+ // Global commands that don't require auth
141
+ program
142
+ .command('status')
143
+ .description('Show overall system status')
144
+ .action(async () => {
145
+ const isAuth = await cliConfig.isAuthenticated();
146
+ const apiUrl = cliConfig.getApiUrl();
147
+ console.log(chalk.blue.bold('MaaS CLI Status'));
148
+ console.log(`API URL: ${apiUrl}`);
149
+ console.log(`Authenticated: ${isAuth ? chalk.green('Yes') : chalk.red('No')}`);
150
+ if (isAuth) {
151
+ const user = await cliConfig.getCurrentUser();
152
+ if (user) {
153
+ console.log(`User: ${user.email}`);
154
+ console.log(`Plan: ${user.plan}`);
155
+ }
156
+ }
157
+ });
158
+ program
159
+ .command('docs')
160
+ .description('Open documentation in browser')
161
+ .action(() => {
162
+ const url = 'https://docs.seyederick.com/memory-service';
163
+ console.log(chalk.blue(`Opening documentation: ${url}`));
164
+ // Try to open in browser
165
+ import('open').then(open => {
166
+ open.default(url).catch(() => {
167
+ console.log(chalk.yellow('Could not open browser automatically.'));
168
+ console.log(chalk.white(`Please visit: ${url}`));
169
+ });
170
+ }).catch(() => {
171
+ console.log(chalk.white(`Please visit: ${url}`));
172
+ });
173
+ });
174
+ // Help customization
175
+ program.configureHelp({
176
+ formatHelp: (cmd, helper) => {
177
+ const term = helper.termWidth || 80;
178
+ const helpWidth = Math.min(term - 2, 80);
179
+ let help = chalk.blue.bold('🧠 Memory as a Service CLI\n\n');
180
+ help += helper.commandUsage(cmd) + '\n\n';
181
+ if (cmd.description()) {
182
+ help += chalk.yellow('Description:\n');
183
+ help += ` ${cmd.description()}\n\n`;
184
+ }
185
+ const commands = helper.visibleCommands(cmd);
186
+ if (commands.length > 0) {
187
+ help += chalk.yellow('Commands:\n');
188
+ const maxNameLength = Math.max(...commands.map(c => c.name().length));
189
+ commands.forEach(c => {
190
+ const name = c.name().padEnd(maxNameLength);
191
+ help += ` ${chalk.white(name)} ${c.description()}\n`;
192
+ });
193
+ help += '\n';
194
+ }
195
+ const options = helper.visibleOptions(cmd);
196
+ if (options.length > 0) {
197
+ help += chalk.yellow('Options:\n');
198
+ options.forEach(option => {
199
+ help += ` ${option.flags.padEnd(20)} ${option.description}\n`;
200
+ });
201
+ help += '\n';
202
+ }
203
+ help += chalk.gray('For more help on a specific command, run: memory <command> --help\n');
204
+ help += chalk.gray('Documentation: https://docs.seyederick.com/memory-service\n');
205
+ return help;
206
+ }
207
+ });
208
+ // Parse CLI arguments
209
+ async function main() {
210
+ // Show welcome message if no arguments provided
211
+ if (process.argv.length <= 2) {
212
+ showWelcome();
213
+ return;
214
+ }
215
+ try {
216
+ await program.parseAsync(process.argv);
217
+ }
218
+ catch (error) {
219
+ if (error instanceof Error) {
220
+ console.error(chalk.red('✖ Error:'), error.message);
221
+ if (process.env.CLI_VERBOSE === 'true') {
222
+ console.error(error.stack);
223
+ }
224
+ }
225
+ process.exit(1);
226
+ }
227
+ }
228
+ main();
@@ -0,0 +1,24 @@
1
+ import { AxiosRequestConfig } from 'axios';
2
+ export declare class APIClient {
3
+ private client;
4
+ private config;
5
+ 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>;
12
+ 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>;
20
+ deleteTopic(id: string): Promise<void>;
21
+ getHealth(): Promise<any>;
22
+ request<T = any>(config: AxiosRequestConfig): Promise<T>;
23
+ }
24
+ export declare const apiClient: APIClient;
@@ -0,0 +1,141 @@
1
+ import axios from 'axios';
2
+ import chalk from 'chalk';
3
+ import { CLIConfig } from './config.js';
4
+ export class APIClient {
5
+ client;
6
+ config;
7
+ constructor() {
8
+ this.config = new CLIConfig();
9
+ this.client = axios.create();
10
+ // Setup request interceptor to add auth token
11
+ this.client.interceptors.request.use(async (config) => {
12
+ await this.config.init();
13
+ const token = this.config.getToken();
14
+ if (token) {
15
+ config.headers.Authorization = `Bearer ${token}`;
16
+ }
17
+ config.baseURL = this.config.getApiUrl();
18
+ if (process.env.CLI_VERBOSE === 'true') {
19
+ console.log(chalk.dim(`→ ${config.method?.toUpperCase()} ${config.url}`));
20
+ }
21
+ return config;
22
+ });
23
+ // Setup response interceptor for error handling
24
+ this.client.interceptors.response.use((response) => {
25
+ if (process.env.CLI_VERBOSE === 'true') {
26
+ console.log(chalk.dim(`← ${response.status} ${response.statusText}`));
27
+ }
28
+ return response;
29
+ }, (error) => {
30
+ if (error.response) {
31
+ const { status, data } = error.response;
32
+ if (status === 401) {
33
+ console.error(chalk.red('✖ Authentication failed'));
34
+ console.log(chalk.yellow('Please run:'), chalk.white('memory login'));
35
+ process.exit(1);
36
+ }
37
+ if (status === 403) {
38
+ console.error(chalk.red('✖ Permission denied'));
39
+ if (data.message) {
40
+ console.error(chalk.gray(data.message));
41
+ }
42
+ process.exit(1);
43
+ }
44
+ if (status === 429) {
45
+ console.error(chalk.red('✖ Rate limit exceeded'));
46
+ console.error(chalk.gray('Please wait a moment before trying again'));
47
+ process.exit(1);
48
+ }
49
+ if (process.env.CLI_VERBOSE === 'true') {
50
+ console.error(chalk.dim(`← ${status} ${error.response.statusText}`));
51
+ console.error(chalk.dim(JSON.stringify(data, null, 2)));
52
+ }
53
+ }
54
+ return Promise.reject(error);
55
+ });
56
+ }
57
+ // Authentication - aligned with Supabase auth
58
+ async login(email, password) {
59
+ const response = await this.client.post('/api/v1/auth/login', {
60
+ email,
61
+ password
62
+ });
63
+ return response.data;
64
+ }
65
+ async register(email, password, organizationName) {
66
+ const response = await this.client.post('/api/v1/auth/register', {
67
+ email,
68
+ password,
69
+ organization_name: organizationName
70
+ });
71
+ return response.data;
72
+ }
73
+ // Memory operations - aligned with existing schema
74
+ async createMemory(data) {
75
+ const response = await this.client.post('/api/v1/memory', data);
76
+ return response.data;
77
+ }
78
+ async getMemories(params = {}) {
79
+ const response = await this.client.get('/api/v1/memory', { params });
80
+ return response.data;
81
+ }
82
+ async getMemory(id) {
83
+ const response = await this.client.get(`/api/v1/memory/${id}`);
84
+ return response.data;
85
+ }
86
+ async updateMemory(id, data) {
87
+ const response = await this.client.put(`/api/v1/memory/${id}`, data);
88
+ return response.data;
89
+ }
90
+ async deleteMemory(id) {
91
+ await this.client.delete(`/api/v1/memory/${id}`);
92
+ }
93
+ async searchMemories(query, options = {}) {
94
+ const response = await this.client.post('/api/v1/memory/search', {
95
+ query,
96
+ ...options
97
+ });
98
+ return response.data;
99
+ }
100
+ async getMemoryStats() {
101
+ const response = await this.client.get('/api/v1/memory/stats');
102
+ return response.data;
103
+ }
104
+ async bulkDeleteMemories(memoryIds) {
105
+ const response = await this.client.post('/api/v1/memory/bulk/delete', {
106
+ memory_ids: memoryIds
107
+ });
108
+ return response.data;
109
+ }
110
+ // Topic operations - working with existing memory_topics table
111
+ async createTopic(data) {
112
+ const response = await this.client.post('/api/v1/topics', data);
113
+ return response.data;
114
+ }
115
+ async getTopics() {
116
+ const response = await this.client.get('/api/v1/topics');
117
+ return response.data;
118
+ }
119
+ async getTopic(id) {
120
+ const response = await this.client.get(`/api/v1/topics/${id}`);
121
+ return response.data;
122
+ }
123
+ async updateTopic(id, data) {
124
+ const response = await this.client.put(`/api/v1/topics/${id}`, data);
125
+ return response.data;
126
+ }
127
+ async deleteTopic(id) {
128
+ await this.client.delete(`/api/v1/topics/${id}`);
129
+ }
130
+ // Health check
131
+ async getHealth() {
132
+ const response = await this.client.get('/api/v1/health');
133
+ return response.data;
134
+ }
135
+ // Generic request method
136
+ async request(config) {
137
+ const response = await this.client.request(config);
138
+ return response.data;
139
+ }
140
+ }
141
+ export const apiClient = new APIClient();
@@ -0,0 +1,26 @@
1
+ interface UserProfile {
2
+ email: string;
3
+ organization_id: string;
4
+ role: string;
5
+ plan: string;
6
+ }
7
+ export declare class CLIConfig {
8
+ private configDir;
9
+ private configPath;
10
+ private config;
11
+ constructor();
12
+ init(): Promise<void>;
13
+ load(): Promise<void>;
14
+ save(): Promise<void>;
15
+ getApiUrl(): string;
16
+ setApiUrl(url: string): Promise<void>;
17
+ setToken(token: string): Promise<void>;
18
+ getToken(): string | undefined;
19
+ getCurrentUser(): Promise<UserProfile | undefined>;
20
+ isAuthenticated(): Promise<boolean>;
21
+ logout(): Promise<void>;
22
+ clear(): Promise<void>;
23
+ getConfigPath(): string;
24
+ exists(): Promise<boolean>;
25
+ }
26
+ export {};
@@ -0,0 +1,104 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { jwtDecode } from 'jwt-decode';
5
+ export class CLIConfig {
6
+ configDir;
7
+ configPath;
8
+ config = {};
9
+ constructor() {
10
+ this.configDir = path.join(os.homedir(), '.maas');
11
+ this.configPath = path.join(this.configDir, 'config.json');
12
+ }
13
+ async init() {
14
+ try {
15
+ await fs.mkdir(this.configDir, { recursive: true });
16
+ await this.load();
17
+ }
18
+ catch (error) {
19
+ // Config doesn't exist yet, that's ok
20
+ }
21
+ }
22
+ async load() {
23
+ try {
24
+ const data = await fs.readFile(this.configPath, 'utf-8');
25
+ this.config = JSON.parse(data);
26
+ }
27
+ catch (error) {
28
+ this.config = {};
29
+ }
30
+ }
31
+ async save() {
32
+ await fs.mkdir(this.configDir, { recursive: true });
33
+ this.config.lastUpdated = new Date().toISOString();
34
+ await fs.writeFile(this.configPath, JSON.stringify(this.config, null, 2));
35
+ }
36
+ getApiUrl() {
37
+ return process.env.MEMORY_API_URL ||
38
+ this.config.apiUrl ||
39
+ 'http://localhost:3000/api/v1';
40
+ }
41
+ async setApiUrl(url) {
42
+ this.config.apiUrl = url;
43
+ await this.save();
44
+ }
45
+ async setToken(token) {
46
+ this.config.token = token;
47
+ // Decode token to get user info
48
+ try {
49
+ const decoded = jwtDecode(token);
50
+ // We'll need to fetch full user details from the API
51
+ // For now, store what we can decode
52
+ this.config.user = {
53
+ email: decoded.email || '',
54
+ organization_id: decoded.organizationId || '',
55
+ role: decoded.role || '',
56
+ plan: decoded.plan || ''
57
+ };
58
+ }
59
+ catch (error) {
60
+ // Invalid token, don't store user info
61
+ }
62
+ await this.save();
63
+ }
64
+ getToken() {
65
+ return this.config.token;
66
+ }
67
+ async getCurrentUser() {
68
+ return this.config.user;
69
+ }
70
+ async isAuthenticated() {
71
+ const token = this.getToken();
72
+ if (!token)
73
+ return false;
74
+ try {
75
+ const decoded = jwtDecode(token);
76
+ const now = Date.now() / 1000;
77
+ return decoded.exp > now;
78
+ }
79
+ catch (error) {
80
+ return false;
81
+ }
82
+ }
83
+ async logout() {
84
+ this.config.token = undefined;
85
+ this.config.user = undefined;
86
+ await this.save();
87
+ }
88
+ async clear() {
89
+ this.config = {};
90
+ await this.save();
91
+ }
92
+ getConfigPath() {
93
+ return this.configPath;
94
+ }
95
+ async exists() {
96
+ try {
97
+ await fs.access(this.configPath);
98
+ return true;
99
+ }
100
+ catch {
101
+ return false;
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,4 @@
1
+ export declare function formatOutput(data: any, format?: string): void;
2
+ export declare function formatBytes(bytes: number): string;
3
+ export declare function truncateText(text: string, maxLength: number): string;
4
+ export declare function formatDuration(ms: number): string;
@@ -0,0 +1,34 @@
1
+ export function formatOutput(data, format = 'table') {
2
+ switch (format) {
3
+ case 'json':
4
+ console.log(JSON.stringify(data, null, 2));
5
+ break;
6
+ case 'yaml':
7
+ // Would need to import yaml library
8
+ console.log('YAML output not implemented yet');
9
+ break;
10
+ default:
11
+ // Table format is handled by calling code
12
+ break;
13
+ }
14
+ }
15
+ export function formatBytes(bytes) {
16
+ if (bytes === 0)
17
+ return '0 Bytes';
18
+ const k = 1024;
19
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
20
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
21
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
22
+ }
23
+ export function truncateText(text, maxLength) {
24
+ if (text.length <= maxLength)
25
+ return text;
26
+ return text.substring(0, maxLength - 3) + '...';
27
+ }
28
+ export function formatDuration(ms) {
29
+ if (ms < 1000)
30
+ return `${ms}ms`;
31
+ if (ms < 60000)
32
+ return `${(ms / 1000).toFixed(1)}s`;
33
+ return `${(ms / 60000).toFixed(1)}m`;
34
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@lanonasis/cli",
3
+ "version": "1.0.0",
4
+ "description": "Lanonasis Enterprise CLI - Memory as a Service and Infrastructure Management",
5
+ "main": "dist/index-simple.js",
6
+ "bin": {
7
+ "lanonasis": "dist/index-simple.js",
8
+ "memory": "dist/index-simple.js",
9
+ "maas": "dist/index-simple.js"
10
+ },
11
+ "type": "module",
12
+ "scripts": {
13
+ "dev": "tsx src/index.ts",
14
+ "build": "tsc && chmod +x dist/index-simple.js",
15
+ "start": "node dist/index.js",
16
+ "test": "jest",
17
+ "lint": "eslint src/**/*.ts",
18
+ "type-check": "tsc --noEmit"
19
+ },
20
+ "keywords": [
21
+ "lanonasis",
22
+ "cli",
23
+ "memory",
24
+ "ai",
25
+ "maas",
26
+ "enterprise",
27
+ "infrastructure",
28
+ "orchestrator"
29
+ ],
30
+ "author": "Lanonasis (Seye Derick)",
31
+ "license": "MIT",
32
+ "files": [
33
+ "dist",
34
+ "README.md"
35
+ ],
36
+ "dependencies": {
37
+ "commander": "^12.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.10.2",
41
+ "@types/inquirer": "^9.0.7",
42
+ "@typescript-eslint/eslint-plugin": "^8.18.1",
43
+ "@typescript-eslint/parser": "^8.18.1",
44
+ "eslint": "^9.17.0",
45
+ "tsx": "^4.19.2",
46
+ "typescript": "^5.7.2"
47
+ },
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ }
51
+ }