@vezlo/assistant-server 1.0.0 → 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.
Files changed (31) hide show
  1. package/README.md +130 -27
  2. package/database-schema.sql +48 -47
  3. package/dist/src/config/logger.d.ts.map +1 -1
  4. package/dist/src/config/logger.js +28 -17
  5. package/dist/src/config/logger.js.map +1 -1
  6. package/dist/src/config/swagger.js +2 -2
  7. package/dist/src/config/swagger.js.map +1 -1
  8. package/dist/src/controllers/KnowledgeController.d.ts +4 -1
  9. package/dist/src/controllers/KnowledgeController.d.ts.map +1 -1
  10. package/dist/src/controllers/KnowledgeController.js +34 -1
  11. package/dist/src/controllers/KnowledgeController.js.map +1 -1
  12. package/dist/src/schemas/KnowledgeSchemas.d.ts +25 -0
  13. package/dist/src/schemas/KnowledgeSchemas.d.ts.map +1 -1
  14. package/dist/src/schemas/KnowledgeSchemas.js +14 -0
  15. package/dist/src/schemas/KnowledgeSchemas.js.map +1 -1
  16. package/dist/src/schemas/index.d.ts +25 -0
  17. package/dist/src/schemas/index.d.ts.map +1 -1
  18. package/dist/src/server.js +30 -4
  19. package/dist/src/server.js.map +1 -1
  20. package/dist/src/services/KnowledgeBaseService.js +2 -2
  21. package/dist/src/services/KnowledgeBaseService.js.map +1 -1
  22. package/dist/src/storage/FeedbackRepository.js +4 -4
  23. package/dist/src/storage/FeedbackRepository.js.map +1 -1
  24. package/dist/src/storage/MessageRepository.js +4 -4
  25. package/dist/src/storage/MessageRepository.js.map +1 -1
  26. package/dist/src/storage/SupabaseStorage.js +9 -9
  27. package/dist/src/storage/SupabaseStorage.js.map +1 -1
  28. package/package.json +10 -3
  29. package/scripts/entrypoint.sh +21 -0
  30. package/scripts/setup.js +339 -0
  31. package/scripts/validate-db.js +171 -0
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Vezlo Assistant Server Setup Wizard
5
+ * Interactive CLI to configure database and environment
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const readline = require('readline');
11
+ const { createClient } = require('@supabase/supabase-js');
12
+
13
+ const rl = readline.createInterface({
14
+ input: process.stdin,
15
+ output: process.stdout
16
+ });
17
+
18
+ // ANSI color codes
19
+ const colors = {
20
+ reset: '\x1b[0m',
21
+ bright: '\x1b[1m',
22
+ green: '\x1b[32m',
23
+ yellow: '\x1b[33m',
24
+ blue: '\x1b[34m',
25
+ red: '\x1b[31m',
26
+ cyan: '\x1b[36m'
27
+ };
28
+
29
+ function log(message, color = 'reset') {
30
+ console.log(`${colors[color]}${message}${colors.reset}`);
31
+ }
32
+
33
+ function question(prompt) {
34
+ return new Promise(resolve => {
35
+ rl.question(`${colors.cyan}${prompt}${colors.reset} `, resolve);
36
+ });
37
+ }
38
+
39
+ async function main() {
40
+ console.clear();
41
+ log('\nšŸš€ Vezlo Assistant Server Setup Wizard\n', 'bright');
42
+ log('This wizard will help you configure your server in 3 easy steps:\n', 'blue');
43
+ log(' 1. Database Connection (Supabase or PostgreSQL)');
44
+ log(' 2. OpenAI API Configuration');
45
+ log(' 3. Automatic Table Creation\n');
46
+
47
+ // Step 1: Database Type Selection
48
+ log('\n═══════════════════════════════════════════════════════════', 'cyan');
49
+ log(' STEP 1: Database Configuration', 'bright');
50
+ log('═══════════════════════════════════════════════════════════\n', 'cyan');
51
+
52
+ log('Choose your database type:');
53
+ log(' [1] Supabase (Recommended)');
54
+ log(' [2] PostgreSQL (Direct Connection)');
55
+ log(' [3] Use existing .env file\n');
56
+
57
+ const dbChoice = await question('Enter your choice (1-3):');
58
+
59
+ let config = {};
60
+
61
+ if (dbChoice === '1') {
62
+ config = await setupSupabase();
63
+ } else if (dbChoice === '2') {
64
+ config = await setupPostgreSQL();
65
+ } else if (dbChoice === '3') {
66
+ config = await loadExistingConfig();
67
+ } else {
68
+ log('\nāŒ Invalid choice. Exiting...', 'red');
69
+ rl.close();
70
+ return;
71
+ }
72
+
73
+ // Step 2: OpenAI Configuration
74
+ log('\n═══════════════════════════════════════════════════════════', 'cyan');
75
+ log(' STEP 2: OpenAI API Configuration', 'bright');
76
+ log('═══════════════════════════════════════════════════════════\n', 'cyan');
77
+
78
+ const openaiKey = await question('Enter your OpenAI API key (sk-...):');
79
+ config.OPENAI_API_KEY = openaiKey.trim();
80
+
81
+ const aiModel = await question('AI Model (default: gpt-4o):') || 'gpt-4o';
82
+ config.AI_MODEL = aiModel.trim();
83
+
84
+ // Step 3: Save Configuration
85
+ log('\n═══════════════════════════════════════════════════════════', 'cyan');
86
+ log(' STEP 3: Save Configuration', 'bright');
87
+ log('═══════════════════════════════════════════════════════════\n', 'cyan');
88
+
89
+ const envPath = path.join(process.cwd(), '.env');
90
+ await saveEnvFile(envPath, config);
91
+
92
+ log('\nāœ… Configuration saved to .env', 'green');
93
+
94
+ // Step 4: Database Setup
95
+ log('\n═══════════════════════════════════════════════════════════', 'cyan');
96
+ log(' STEP 4: Database Setup', 'bright');
97
+ log('═══════════════════════════════════════════════════════════\n', 'cyan');
98
+
99
+ const setupDb = await question('Setup database tables now? (y/n):');
100
+
101
+ if (setupDb.toLowerCase() === 'y') {
102
+ await setupDatabase(config);
103
+ } else {
104
+ log('\nāš ļø Skipping database setup.', 'yellow');
105
+ log(' Run "npx vezlo-setup-db" later to create tables.\n', 'yellow');
106
+ }
107
+
108
+ // Final Instructions
109
+ log('\n═══════════════════════════════════════════════════════════', 'green');
110
+ log(' šŸŽ‰ Setup Complete!', 'bright');
111
+ log('═══════════════════════════════════════════════════════════\n', 'green');
112
+
113
+ log('Next steps:');
114
+ log(' 1. Review your .env file');
115
+ log(' 2. Start the server: ' + colors.bright + 'vezlo-server' + colors.reset);
116
+ log(' 3. Visit: ' + colors.bright + 'http://localhost:3000/health' + colors.reset);
117
+ log(' 4. API docs: ' + colors.bright + 'http://localhost:3000/docs' + colors.reset + '\n');
118
+
119
+ rl.close();
120
+ }
121
+
122
+ async function setupSupabase() {
123
+ log('\nšŸ“¦ Supabase Configuration\n', 'blue');
124
+ log('You can find these values in your Supabase Dashboard:', 'yellow');
125
+ log(' Settings > API > Project URL & API Keys\n', 'yellow');
126
+
127
+ const supabaseUrl = await question('Supabase Project URL (https://xxx.supabase.co):');
128
+ const supabaseAnonKey = await question('Supabase Anon Key:');
129
+ const supabaseServiceKey = await question('Supabase Service Role Key:');
130
+
131
+ // Validate connection
132
+ log('\nšŸ”„ Testing connection...', 'yellow');
133
+
134
+ try {
135
+ const client = createClient(supabaseUrl.trim(), supabaseServiceKey.trim());
136
+ const { data, error } = await client.from('_test').select('*').limit(1);
137
+
138
+ // This will fail but confirms we can connect
139
+ if (error && error.code !== 'PGRST204' && error.code !== '42P01') {
140
+ log(`\nāš ļø Warning: ${error.message}`, 'yellow');
141
+ log('Continuing with setup...\n', 'yellow');
142
+ } else {
143
+ log('āœ… Connection successful!\n', 'green');
144
+ }
145
+ } catch (err) {
146
+ log(`\nāš ļø Warning: Could not verify connection`, 'yellow');
147
+ log('Continuing with setup...\n', 'yellow');
148
+ }
149
+
150
+ // Extract database connection info from Supabase URL
151
+ const projectId = supabaseUrl.match(/https:\/\/(.+?)\.supabase\.co/)?.[1];
152
+ const dbHost = projectId ? `db.${projectId}.supabase.co` : '';
153
+
154
+ log('Database connection details:', 'blue');
155
+ log(` Host: ${dbHost}`);
156
+ log(` Port: 5432`);
157
+ log(` Database: postgres`);
158
+ log(` User: postgres\n`);
159
+
160
+ const dbPassword = await question('Supabase Database Password (from Settings > Database):');
161
+
162
+ return {
163
+ SUPABASE_URL: supabaseUrl.trim(),
164
+ SUPABASE_ANON_KEY: supabaseAnonKey.trim(),
165
+ SUPABASE_SERVICE_KEY: supabaseServiceKey.trim(),
166
+ SUPABASE_DB_HOST: dbHost,
167
+ SUPABASE_DB_PORT: '5432',
168
+ SUPABASE_DB_NAME: 'postgres',
169
+ SUPABASE_DB_USER: 'postgres',
170
+ SUPABASE_DB_PASSWORD: dbPassword.trim(),
171
+ PORT: '3000',
172
+ NODE_ENV: 'development',
173
+ CORS_ORIGINS: 'http://localhost:3000,http://localhost:5173'
174
+ };
175
+ }
176
+
177
+ async function setupPostgreSQL() {
178
+ log('\nšŸ—„ļø PostgreSQL Configuration\n', 'blue');
179
+
180
+ const host = await question('Database Host (localhost):') || 'localhost';
181
+ const port = await question('Database Port (5432):') || '5432';
182
+ const database = await question('Database Name (postgres):') || 'postgres';
183
+ const user = await question('Database User (postgres):') || 'postgres';
184
+ const password = await question('Database Password:');
185
+
186
+ return {
187
+ SUPABASE_DB_HOST: host.trim(),
188
+ SUPABASE_DB_PORT: port.trim(),
189
+ SUPABASE_DB_NAME: database.trim(),
190
+ SUPABASE_DB_USER: user.trim(),
191
+ SUPABASE_DB_PASSWORD: password.trim(),
192
+ PORT: '3000',
193
+ NODE_ENV: 'development',
194
+ CORS_ORIGINS: 'http://localhost:3000,http://localhost:5173'
195
+ };
196
+ }
197
+
198
+ async function loadExistingConfig() {
199
+ const envPath = path.join(process.cwd(), '.env');
200
+
201
+ if (!fs.existsSync(envPath)) {
202
+ log('\nāŒ No .env file found in current directory', 'red');
203
+ throw new Error('.env file not found');
204
+ }
205
+
206
+ log('\nāœ… Loading configuration from .env\n', 'green');
207
+
208
+ const envContent = fs.readFileSync(envPath, 'utf8');
209
+ const config = {};
210
+
211
+ envContent.split('\n').forEach(line => {
212
+ const match = line.match(/^([^=:#]+)=(.*)$/);
213
+ if (match) {
214
+ const key = match[1].trim();
215
+ const value = match[2].trim();
216
+ config[key] = value;
217
+ }
218
+ });
219
+
220
+ return config;
221
+ }
222
+
223
+ async function saveEnvFile(envPath, config) {
224
+ const envContent = `# Vezlo Assistant Server Configuration
225
+ # Generated by setup wizard on ${new Date().toISOString()}
226
+
227
+ # Server Configuration
228
+ PORT=${config.PORT || '3000'}
229
+ NODE_ENV=${config.NODE_ENV || 'development'}
230
+ LOG_LEVEL=info
231
+
232
+ # CORS Configuration
233
+ CORS_ORIGINS=${config.CORS_ORIGINS || 'http://localhost:3000,http://localhost:5173'}
234
+
235
+ # Rate Limiting
236
+ RATE_LIMIT_WINDOW=60000
237
+ RATE_LIMIT_MAX=100
238
+
239
+ # Supabase Configuration
240
+ ${config.SUPABASE_URL ? `SUPABASE_URL=${config.SUPABASE_URL}` : '# SUPABASE_URL=https://your-project.supabase.co'}
241
+ ${config.SUPABASE_ANON_KEY ? `SUPABASE_ANON_KEY=${config.SUPABASE_ANON_KEY}` : '# SUPABASE_ANON_KEY=your-anon-key'}
242
+ ${config.SUPABASE_SERVICE_KEY ? `SUPABASE_SERVICE_KEY=${config.SUPABASE_SERVICE_KEY}` : '# SUPABASE_SERVICE_KEY=your-service-role-key'}
243
+
244
+ # Database Configuration
245
+ SUPABASE_DB_HOST=${config.SUPABASE_DB_HOST || 'localhost'}
246
+ SUPABASE_DB_PORT=${config.SUPABASE_DB_PORT || '5432'}
247
+ SUPABASE_DB_NAME=${config.SUPABASE_DB_NAME || 'postgres'}
248
+ SUPABASE_DB_USER=${config.SUPABASE_DB_USER || 'postgres'}
249
+ SUPABASE_DB_PASSWORD=${config.SUPABASE_DB_PASSWORD || ''}
250
+
251
+ # OpenAI Configuration
252
+ OPENAI_API_KEY=${config.OPENAI_API_KEY || 'sk-your-openai-api-key'}
253
+ AI_MODEL=${config.AI_MODEL || 'gpt-4o'}
254
+ AI_TEMPERATURE=0.7
255
+ AI_MAX_TOKENS=1000
256
+
257
+ # Organization Settings
258
+ ORGANIZATION_NAME=Vezlo
259
+ ASSISTANT_NAME=Vezlo Assistant
260
+
261
+ # Knowledge Base
262
+ CHUNK_SIZE=1000
263
+ CHUNK_OVERLAP=200
264
+ `;
265
+
266
+ fs.writeFileSync(envPath, envContent, 'utf8');
267
+ }
268
+
269
+ async function setupDatabase(config) {
270
+ log('\nšŸ”„ Setting up database tables...', 'yellow');
271
+
272
+ try {
273
+ const { Client } = require('pg');
274
+
275
+ const client = new Client({
276
+ host: config.SUPABASE_DB_HOST,
277
+ port: parseInt(config.SUPABASE_DB_PORT || '5432'),
278
+ database: config.SUPABASE_DB_NAME,
279
+ user: config.SUPABASE_DB_USER,
280
+ password: config.SUPABASE_DB_PASSWORD,
281
+ ssl: { rejectUnauthorized: false }
282
+ });
283
+
284
+ await client.connect();
285
+ log('āœ… Connected to database', 'green');
286
+
287
+ // Read schema file
288
+ const schemaPath = path.join(__dirname, '..', 'database-schema.sql');
289
+
290
+ if (!fs.existsSync(schemaPath)) {
291
+ log('āŒ database-schema.sql not found', 'red');
292
+ return;
293
+ }
294
+
295
+ const schema = fs.readFileSync(schemaPath, 'utf8');
296
+
297
+ log('šŸ”„ Creating tables...', 'yellow');
298
+ await client.query(schema);
299
+
300
+ log('āœ… Database tables created successfully!', 'green');
301
+
302
+ // Verify tables
303
+ const result = await client.query(`
304
+ SELECT table_name
305
+ FROM information_schema.tables
306
+ WHERE table_schema = 'public'
307
+ AND table_name IN ('conversations', 'messages', 'message_feedback', 'knowledge_items')
308
+ ORDER BY table_name
309
+ `);
310
+
311
+ log('\nšŸ“Š Verified tables:', 'blue');
312
+ result.rows.forEach(row => {
313
+ log(` āœ“ ${row.table_name}`, 'green');
314
+ });
315
+ log('');
316
+
317
+ await client.end();
318
+
319
+ } catch (error) {
320
+ log(`\nāŒ Database setup failed: ${error.message}`, 'red');
321
+ log('\nYou can manually run the setup later:', 'yellow');
322
+ log(' 1. Copy database-schema.sql to your Supabase SQL Editor', 'yellow');
323
+ log(' 2. Execute the SQL to create tables\n', 'yellow');
324
+ }
325
+ }
326
+
327
+ // Handle errors and cleanup
328
+ process.on('SIGINT', () => {
329
+ log('\n\nāš ļø Setup cancelled by user', 'yellow');
330
+ rl.close();
331
+ process.exit(0);
332
+ });
333
+
334
+ // Run the wizard
335
+ main().catch(error => {
336
+ log(`\nāŒ Setup failed: ${error.message}`, 'red');
337
+ rl.close();
338
+ process.exit(1);
339
+ });
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Vezlo Assistant Server - Database Validation
5
+ * Validates database connection and table setup
6
+ */
7
+
8
+ const dotenv = require('dotenv');
9
+ const { createClient } = require('@supabase/supabase-js');
10
+
11
+ // Load environment variables
12
+ dotenv.config();
13
+
14
+ const colors = {
15
+ reset: '\x1b[0m',
16
+ green: '\x1b[32m',
17
+ yellow: '\x1b[33m',
18
+ red: '\x1b[31m',
19
+ cyan: '\x1b[36m',
20
+ bright: '\x1b[1m'
21
+ };
22
+
23
+ function log(message, color = 'reset') {
24
+ console.log(`${colors[color]}${message}${colors.reset}`);
25
+ }
26
+
27
+ async function validateDatabase() {
28
+ log('\nšŸ” Validating Database Configuration\n', 'cyan');
29
+
30
+ // Check environment variables
31
+ log('Checking environment variables...', 'yellow');
32
+
33
+ const requiredVars = [
34
+ 'SUPABASE_URL',
35
+ 'SUPABASE_SERVICE_KEY',
36
+ 'SUPABASE_DB_HOST',
37
+ 'SUPABASE_DB_PASSWORD'
38
+ ];
39
+
40
+ const missing = requiredVars.filter(key => !process.env[key]);
41
+
42
+ if (missing.length > 0) {
43
+ log(`\nāŒ Missing required environment variables:`, 'red');
44
+ missing.forEach(key => log(` - ${key}`, 'red'));
45
+ log('\nRun the setup wizard: ' + colors.bright + 'npx vezlo-setup' + colors.reset + '\n', 'yellow');
46
+ process.exit(1);
47
+ }
48
+
49
+ log('āœ… Environment variables configured\n', 'green');
50
+
51
+ // Test Supabase connection
52
+ log('Testing Supabase connection...', 'yellow');
53
+
54
+ try {
55
+ const supabase = createClient(
56
+ process.env.SUPABASE_URL,
57
+ process.env.SUPABASE_SERVICE_KEY
58
+ );
59
+
60
+ // Try to query a table
61
+ const { error } = await supabase.from('vezlo_conversations').select('count').limit(0);
62
+
63
+ if (error && error.code !== 'PGRST116') {
64
+ throw error;
65
+ }
66
+
67
+ log('āœ… Supabase connection successful\n', 'green');
68
+
69
+ } catch (error) {
70
+ log(`āŒ Supabase connection failed: ${error.message}\n`, 'red');
71
+ process.exit(1);
72
+ }
73
+
74
+ // Test database connection and validate tables
75
+ log('Validating database tables...', 'yellow');
76
+
77
+ try {
78
+ const { Client } = require('pg');
79
+
80
+ const client = new Client({
81
+ host: process.env.SUPABASE_DB_HOST,
82
+ port: parseInt(process.env.SUPABASE_DB_PORT || '5432'),
83
+ database: process.env.SUPABASE_DB_NAME || 'postgres',
84
+ user: process.env.SUPABASE_DB_USER || 'postgres',
85
+ password: process.env.SUPABASE_DB_PASSWORD,
86
+ ssl: { rejectUnauthorized: false }
87
+ });
88
+
89
+ await client.connect();
90
+
91
+ // Check required tables
92
+ const requiredTables = [
93
+ 'conversations',
94
+ 'messages',
95
+ 'message_feedback',
96
+ 'knowledge_items'
97
+ ];
98
+
99
+ const result = await client.query(`
100
+ SELECT table_name
101
+ FROM information_schema.tables
102
+ WHERE table_schema = 'public'
103
+ AND table_name = ANY($1)
104
+ ORDER BY table_name
105
+ `, [requiredTables]);
106
+
107
+ const existingTables = result.rows.map(row => row.table_name);
108
+ const missingTables = requiredTables.filter(t => !existingTables.includes(t));
109
+
110
+ if (missingTables.length > 0) {
111
+ log(`\nāŒ Missing required tables:`, 'red');
112
+ missingTables.forEach(table => log(` - ${table}`, 'red'));
113
+ log('\nRun the setup wizard: ' + colors.bright + 'npx vezlo-setup' + colors.reset + '\n', 'yellow');
114
+ await client.end();
115
+ process.exit(1);
116
+ }
117
+
118
+ log('āœ… All required tables exist\n', 'green');
119
+
120
+ // Check table structure
121
+ log('Checking table structure...', 'yellow');
122
+
123
+ const schemaCheck = await client.query(`
124
+ SELECT
125
+ t.table_name,
126
+ COUNT(c.column_name) as column_count
127
+ FROM information_schema.tables t
128
+ LEFT JOIN information_schema.columns c
129
+ ON c.table_name = t.table_name
130
+ AND c.table_schema = t.table_schema
131
+ WHERE t.table_schema = 'public'
132
+ AND t.table_name = ANY($1)
133
+ GROUP BY t.table_name
134
+ ORDER BY t.table_name
135
+ `, [requiredTables]);
136
+
137
+ log('\nšŸ“Š Table Structure:', 'cyan');
138
+ schemaCheck.rows.forEach(row => {
139
+ log(` āœ“ ${row.table_name} (${row.column_count} columns)`, 'green');
140
+ });
141
+
142
+ // Check for vector extension
143
+ const vectorCheck = await client.query(`
144
+ SELECT EXISTS(
145
+ SELECT 1 FROM pg_extension WHERE extname = 'vector'
146
+ ) as has_vector
147
+ `);
148
+
149
+ if (vectorCheck.rows[0].has_vector) {
150
+ log(' āœ“ pgvector extension enabled', 'green');
151
+ } else {
152
+ log(' āš ļø pgvector extension not enabled (semantic search disabled)', 'yellow');
153
+ }
154
+
155
+ log('\nāœ… Database validation complete!\n', 'green');
156
+ log('Your server is ready to start:', 'cyan');
157
+ log(' ' + colors.bright + 'vezlo-server' + colors.reset + '\n');
158
+
159
+ await client.end();
160
+
161
+ } catch (error) {
162
+ log(`\nāŒ Database validation failed: ${error.message}\n`, 'red');
163
+ process.exit(1);
164
+ }
165
+ }
166
+
167
+ // Run validation
168
+ validateDatabase().catch(error => {
169
+ log(`\nāŒ Validation failed: ${error.message}\n`, 'red');
170
+ process.exit(1);
171
+ });