webagents 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl

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 +1,3 @@
1
- # TODO: Implement CrewAI skill
1
+ from .skill import CrewAISkill
2
+
3
+ __all__ = ['CrewAISkill']
@@ -0,0 +1,158 @@
1
+ """
2
+ Simplified CrewAI Skill for WebAgents
3
+
4
+ This skill runs a pre-configured CrewAI crew with agents and tasks.
5
+ The crew configuration is provided during skill initialization.
6
+
7
+ Main use case: Execute a specific crew workflow on demand.
8
+ """
9
+
10
+ import os
11
+ import json
12
+ from typing import Dict, Any, Optional, List, Union
13
+ from datetime import datetime
14
+
15
+ from webagents.agents.skills.base import Skill
16
+ from webagents.agents.tools.decorators import tool, prompt
17
+
18
+ try:
19
+ from crewai import Agent, Task, Crew, Process
20
+ CREWAI_AVAILABLE = True
21
+ except ImportError:
22
+ CREWAI_AVAILABLE = False
23
+ Agent, Task, Crew, Process = None, None, None, None # type: ignore
24
+
25
+
26
+ class CrewAISkill(Skill):
27
+ """Simplified CrewAI skill for running pre-configured crews"""
28
+
29
+ def __init__(self, crew_or_config: Optional[Union[Dict[str, Any], Any]] = None):
30
+ super().__init__({}, scope="all")
31
+ if not CREWAI_AVAILABLE:
32
+ raise ImportError("CrewAI is not installed. Install with: pip install crewai")
33
+
34
+ # Handle both Crew object and configuration dictionary
35
+ if crew_or_config is None:
36
+ self.crew_config = {}
37
+ self.crew = None
38
+ elif hasattr(crew_or_config, 'agents') and hasattr(crew_or_config, 'tasks'):
39
+ # It's a Crew object
40
+ self.crew_config = {}
41
+ self.crew = crew_or_config
42
+ else:
43
+ # It's a configuration dictionary
44
+ self.crew_config = crew_or_config or {}
45
+ self.crew = None
46
+ self._setup_crew()
47
+
48
+ def get_dependencies(self) -> List[str]:
49
+ """Skill dependencies"""
50
+ return [] # No dependencies needed for simple crew execution
51
+
52
+ def _setup_crew(self):
53
+ """Set up the CrewAI crew from configuration"""
54
+ if not self.crew_config:
55
+ return
56
+
57
+ try:
58
+ # Get agents configuration
59
+ agents_config = self.crew_config.get('agents', [])
60
+ tasks_config = self.crew_config.get('tasks', [])
61
+ process_type = self.crew_config.get('process', 'sequential')
62
+
63
+ if not agents_config or not tasks_config:
64
+ return
65
+
66
+ # Create agents
67
+ agents = []
68
+ for agent_config in agents_config:
69
+ agent = Agent(
70
+ role=agent_config.get('role', 'Agent'),
71
+ goal=agent_config.get('goal', 'Complete assigned tasks'),
72
+ backstory=agent_config.get('backstory', 'An AI agent ready to help'),
73
+ verbose=agent_config.get('verbose', True),
74
+ allow_delegation=agent_config.get('allow_delegation', False)
75
+ )
76
+ agents.append(agent)
77
+
78
+ # Create tasks
79
+ tasks = []
80
+ for i, task_config in enumerate(tasks_config):
81
+ # Assign agent to task (default to first agent if not specified)
82
+ agent_index = task_config.get('agent_index', 0)
83
+ if agent_index >= len(agents):
84
+ agent_index = 0 # Fallback to first agent
85
+
86
+ task = Task(
87
+ description=task_config.get('description', f'Task {i+1}'),
88
+ agent=agents[agent_index],
89
+ expected_output=task_config.get('expected_output', 'Task completion')
90
+ )
91
+ tasks.append(task)
92
+
93
+ # Create crew
94
+ process = Process.sequential
95
+ if process_type.lower() == 'hierarchical':
96
+ process = Process.hierarchical
97
+
98
+ self.crew = Crew(
99
+ agents=agents,
100
+ tasks=tasks,
101
+ process=process,
102
+ verbose=self.crew_config.get('verbose', True)
103
+ )
104
+
105
+ # CrewAI crew initialized successfully
106
+
107
+ except Exception as e:
108
+ self.crew = None
109
+
110
+ @prompt(priority=40, scope=["owner", "all"])
111
+ def crewai_prompt(self) -> str:
112
+ """Prompt describing CrewAI capabilities"""
113
+ if not self.crew:
114
+ return """
115
+ CrewAI skill is available but no crew is configured.
116
+
117
+ To use CrewAI, initialize the skill with either:
118
+ 1. A CrewAI Crew object: CrewAISkill(crew)
119
+ 2. A crew configuration dictionary with agents, tasks, and process settings
120
+ """
121
+
122
+ agents_count = len(self.crew.agents) if self.crew else 0
123
+ tasks_count = len(self.crew.tasks) if self.crew else 0
124
+
125
+ return f"""
126
+ CrewAI multi-agent orchestration is ready. Available tool:
127
+
128
+ • crewai_run(inputs) - Execute the configured crew with the given inputs
129
+
130
+ Configured crew:
131
+ - {agents_count} agents with specialized roles
132
+ - {tasks_count} tasks in the workflow
133
+ - Ready to process your requests through collaborative AI agents
134
+
135
+ Provide inputs as a dictionary to run the crew workflow.
136
+ """
137
+
138
+ # Public tool
139
+ @tool(description="Execute the configured CrewAI crew with the given inputs")
140
+ async def crewai_run(self, inputs: Dict[str, Any]) -> str:
141
+ """Execute the configured CrewAI crew with the provided inputs"""
142
+ if not self.crew:
143
+ return "❌ No CrewAI crew configured. Please initialize the skill with a crew configuration."
144
+
145
+ if not inputs:
146
+ return "❌ Inputs are required to run the crew"
147
+
148
+ try:
149
+ # Execute the crew with inputs
150
+ result = self.crew.kickoff(inputs=inputs)
151
+
152
+ agents_count = len(self.crew.agents)
153
+ tasks_count = len(self.crew.tasks)
154
+
155
+ return f"✅ CrewAI execution completed successfully!\n👥 Agents: {agents_count}\n📝 Tasks: {tasks_count}\n\n📊 Result:\n{result}"
156
+
157
+ except Exception as e:
158
+ return f"❌ CrewAI execution failed: {str(e)}"
@@ -1 +1,3 @@
1
- # TODO: Implement Database skill
1
+ from .skill import SupabaseSkill
2
+
3
+ __all__ = ['SupabaseSkill']
@@ -0,0 +1,522 @@
1
+ """
2
+ Minimalistic Supabase/PostgreSQL Skill for WebAgents
3
+
4
+ This skill allows users to:
5
+ - Connect to Supabase or PostgreSQL databases
6
+ - Execute queries and manage data (CRUD operations)
7
+ - Handle authentication and real-time subscriptions
8
+ - Manage database schemas and tables
9
+
10
+ Uses auth skill for user context and kv skill for secure connection storage.
11
+ """
12
+
13
+ import os
14
+ import json
15
+ from typing import Dict, Any, Optional, List, Union
16
+ from datetime import datetime
17
+
18
+ from webagents.agents.skills.base import Skill
19
+ from webagents.agents.tools.decorators import tool, prompt
20
+ from webagents.server.context.context_vars import get_context
21
+
22
+ try:
23
+ from supabase import create_client, Client
24
+ import psycopg2
25
+ from psycopg2.extras import RealDictCursor
26
+ SUPABASE_AVAILABLE = True
27
+ except ImportError:
28
+ SUPABASE_AVAILABLE = False
29
+
30
+
31
+ class SupabaseSkill(Skill):
32
+ """Minimalistic Supabase/PostgreSQL skill for database operations"""
33
+
34
+ def __init__(self):
35
+ super().__init__()
36
+ if not SUPABASE_AVAILABLE:
37
+ raise ImportError("Supabase dependencies not installed. Install with: pip install supabase psycopg2-binary")
38
+
39
+ def get_dependencies(self) -> List[str]:
40
+ """Skill dependencies"""
41
+ return ['auth', 'kv']
42
+
43
+ @prompt(priority=40, scope=["owner", "all"])
44
+ def supabase_prompt(self) -> str:
45
+ """Prompt describing Supabase capabilities"""
46
+ return """
47
+ Minimalistic Supabase/PostgreSQL integration for database operations. Available tools:
48
+
49
+ • supabase_setup(config) - Set up Supabase or PostgreSQL connection securely
50
+ • supabase_query(sql, params) - Execute SQL queries with optional parameters
51
+ • supabase_table_ops(operation, table, data) - Perform CRUD operations on tables
52
+ • supabase_status() - Check database connection and configuration status
53
+
54
+ Features:
55
+ - Supabase and PostgreSQL database support
56
+ - Secure connection string storage
57
+ - SQL query execution with parameterization
58
+ - CRUD operations (Create, Read, Update, Delete)
59
+ - Real-time subscription support (Supabase)
60
+ - Per-user database isolation via Auth skill
61
+
62
+ Setup: Configure with your Supabase URL/key or PostgreSQL connection string.
63
+ """
64
+
65
+ # Helper methods for auth and kv skills
66
+ async def _get_auth_skill(self):
67
+ """Get auth skill for user context"""
68
+ return self.agent.skills.get('auth')
69
+
70
+ async def _get_kv_skill(self):
71
+ """Get KV skill for secure storage"""
72
+ return self.agent.skills.get('kv')
73
+
74
+ async def _get_authenticated_user_id(self) -> Optional[str]:
75
+ """Get authenticated user ID from context"""
76
+ try:
77
+ context = get_context()
78
+ if context and context.auth and context.auth.authenticated:
79
+ return context.auth.user_id
80
+ return None
81
+ except Exception as e:
82
+ self.logger.error(f"Failed to get user context: {e}")
83
+ return None
84
+
85
+ async def _save_db_config(self, user_id: str, config: Dict[str, Any]) -> bool:
86
+ """Save database configuration securely using KV skill"""
87
+ try:
88
+ kv_skill = await self._get_kv_skill()
89
+ if kv_skill:
90
+ config_data = {
91
+ **config,
92
+ 'created_at': datetime.now().isoformat(),
93
+ 'updated_at': datetime.now().isoformat()
94
+ }
95
+ await kv_skill.kv_set(
96
+ key='config',
97
+ value=json.dumps(config_data),
98
+ namespace=f'supabase:{user_id}'
99
+ )
100
+ return True
101
+ else:
102
+ # Fallback to in-memory storage
103
+ if not hasattr(self.agent, '_supabase_configs'):
104
+ self.agent._supabase_configs = {}
105
+ self.agent._supabase_configs[user_id] = config
106
+ return True
107
+ except Exception as e:
108
+ self.logger.error(f"Failed to save database config: {e}")
109
+ return False
110
+
111
+ async def _load_db_config(self, user_id: str) -> Optional[Dict[str, Any]]:
112
+ """Load database configuration from KV skill"""
113
+ try:
114
+ kv_skill = await self._get_kv_skill()
115
+ if kv_skill:
116
+ config_json = await kv_skill.kv_get(
117
+ key='config',
118
+ namespace=f'supabase:{user_id}'
119
+ )
120
+ if config_json:
121
+ return json.loads(config_json)
122
+ else:
123
+ # Fallback to in-memory storage
124
+ if hasattr(self.agent, '_supabase_configs'):
125
+ return self.agent._supabase_configs.get(user_id)
126
+ return None
127
+ except Exception as e:
128
+ self.logger.error(f"Failed to load database config: {e}")
129
+ return None
130
+
131
+ def _create_supabase_client(self, config: Dict[str, Any]) -> Optional[Client]:
132
+ """Create Supabase client from configuration"""
133
+ try:
134
+ url = config.get('supabase_url')
135
+ key = config.get('supabase_key')
136
+ if url and key:
137
+ return create_client(url, key)
138
+ return None
139
+ except Exception as e:
140
+ self.logger.error(f"Failed to create Supabase client: {e}")
141
+ return None
142
+
143
+ def _create_postgres_connection(self, config: Dict[str, Any]):
144
+ """Create PostgreSQL connection from configuration"""
145
+ try:
146
+ connection_string = config.get('postgres_url')
147
+ if connection_string:
148
+ return psycopg2.connect(connection_string, cursor_factory=RealDictCursor)
149
+
150
+ # Alternative: individual connection parameters
151
+ conn_params = {
152
+ 'host': config.get('host'),
153
+ 'port': config.get('port', 5432),
154
+ 'database': config.get('database'),
155
+ 'user': config.get('user'),
156
+ 'password': config.get('password')
157
+ }
158
+ if all(conn_params.values()):
159
+ return psycopg2.connect(cursor_factory=RealDictCursor, **conn_params)
160
+
161
+ return None
162
+ except Exception as e:
163
+ self.logger.error(f"Failed to create PostgreSQL connection: {e}")
164
+ return None
165
+
166
+ # Public tools
167
+ @tool(description="Set up Supabase or PostgreSQL database connection", scope="owner")
168
+ async def supabase_setup(self, config: Dict[str, Any]) -> str:
169
+ """Set up database connection configuration"""
170
+ user_id = await self._get_authenticated_user_id()
171
+ if not user_id:
172
+ return "❌ Authentication required"
173
+
174
+ if not config:
175
+ return "❌ Configuration is required"
176
+
177
+ try:
178
+ # Validate configuration type
179
+ db_type = config.get('type', 'supabase').lower()
180
+
181
+ if db_type == 'supabase':
182
+ if 'supabase_url' not in config or 'supabase_key' not in config:
183
+ return "❌ Supabase configuration requires 'supabase_url' and 'supabase_key'"
184
+
185
+ # Test connection
186
+ client = self._create_supabase_client(config)
187
+ if not client:
188
+ return "❌ Failed to create Supabase client"
189
+
190
+ # Test with a simple query
191
+ try:
192
+ # This will fail gracefully if no tables exist
193
+ client.table('_supabase_test_').select('*').limit(1).execute()
194
+ except:
195
+ pass # Expected if table doesn't exist
196
+
197
+ elif db_type == 'postgresql':
198
+ if not config.get('postgres_url') and not all([
199
+ config.get('host'), config.get('database'),
200
+ config.get('user'), config.get('password')
201
+ ]):
202
+ return "❌ PostgreSQL configuration requires either 'postgres_url' or host/database/user/password"
203
+
204
+ # Test connection
205
+ conn = self._create_postgres_connection(config)
206
+ if not conn:
207
+ return "❌ Failed to create PostgreSQL connection"
208
+
209
+ # Test with a simple query
210
+ try:
211
+ cursor = conn.cursor()
212
+ cursor.execute("SELECT 1")
213
+ cursor.close()
214
+ conn.close()
215
+ except Exception as e:
216
+ return f"❌ Database connection test failed: {str(e)}"
217
+
218
+ else:
219
+ return "❌ Unsupported database type. Use 'supabase' or 'postgresql'"
220
+
221
+ # Save configuration
222
+ success = await self._save_db_config(user_id, config)
223
+
224
+ if success:
225
+ return f"✅ {db_type.title()} configuration saved successfully!\n🔧 Database type: {db_type}\n🔒 Connection details stored securely"
226
+ else:
227
+ return "❌ Failed to save configuration"
228
+
229
+ except Exception as e:
230
+ return f"❌ Setup failed: {str(e)}"
231
+
232
+ @tool(description="Execute SQL queries on the configured database")
233
+ async def supabase_query(self, sql: str, params: Optional[List[Any]] = None) -> str:
234
+ """Execute SQL queries with optional parameters"""
235
+ user_id = await self._get_authenticated_user_id()
236
+ if not user_id:
237
+ return "❌ Authentication required"
238
+
239
+ if not sql or not sql.strip():
240
+ return "❌ SQL query is required"
241
+
242
+ try:
243
+ # Load user configuration
244
+ config = await self._load_db_config(user_id)
245
+ if not config:
246
+ return "❌ Database not configured. Please run supabase_setup() first."
247
+
248
+ db_type = config.get('type', 'supabase').lower()
249
+
250
+ if db_type == 'supabase':
251
+ client = self._create_supabase_client(config)
252
+ if not client:
253
+ return "❌ Failed to connect to Supabase"
254
+
255
+ # For Supabase, we'll use the PostgREST API via rpc or direct table operations
256
+ # This is a simplified approach - in practice, you'd want more sophisticated SQL parsing
257
+ sql_lower = sql.lower().strip()
258
+
259
+ if sql_lower.startswith('select'):
260
+ return "❌ For SELECT queries, use supabase_table_ops() with operation='select'"
261
+ elif sql_lower.startswith(('insert', 'update', 'delete')):
262
+ return "❌ For data modifications, use supabase_table_ops() for better Supabase integration"
263
+ else:
264
+ # For other operations, we'd need to use the underlying PostgreSQL connection
265
+ return "❌ Complex SQL operations not supported in Supabase mode. Use PostgreSQL mode for full SQL support."
266
+
267
+ elif db_type == 'postgresql':
268
+ conn = self._create_postgres_connection(config)
269
+ if not conn:
270
+ return "❌ Failed to connect to PostgreSQL"
271
+
272
+ try:
273
+ cursor = conn.cursor()
274
+
275
+ # Execute query with parameters
276
+ if params:
277
+ cursor.execute(sql, params)
278
+ else:
279
+ cursor.execute(sql)
280
+
281
+ # Handle different query types
282
+ if sql.lower().strip().startswith('select'):
283
+ results = cursor.fetchall()
284
+ if results:
285
+ # Convert to list of dicts
286
+ rows = [dict(row) for row in results]
287
+ return f"✅ Query executed successfully!\n📊 Results ({len(rows)} rows):\n{json.dumps(rows, indent=2, default=str)}"
288
+ else:
289
+ return "✅ Query executed successfully!\n📊 No results returned"
290
+ else:
291
+ # For INSERT, UPDATE, DELETE, etc.
292
+ conn.commit()
293
+ affected_rows = cursor.rowcount
294
+ return f"✅ Query executed successfully!\n📝 Affected rows: {affected_rows}"
295
+
296
+ finally:
297
+ cursor.close()
298
+ conn.close()
299
+
300
+ else:
301
+ return "❌ Unknown database type in configuration"
302
+
303
+ except Exception as e:
304
+ return f"❌ Query execution failed: {str(e)}"
305
+
306
+ @tool(description="Perform CRUD operations on database tables")
307
+ async def supabase_table_ops(self, operation: str, table: str, data: Optional[Dict[str, Any]] = None,
308
+ filters: Optional[Dict[str, Any]] = None) -> str:
309
+ """Perform Create, Read, Update, Delete operations on tables"""
310
+ user_id = await self._get_authenticated_user_id()
311
+ if not user_id:
312
+ return "❌ Authentication required"
313
+
314
+ if not operation or not table:
315
+ return "❌ Operation and table name are required"
316
+
317
+ operation = operation.lower().strip()
318
+ valid_operations = ['select', 'insert', 'update', 'delete']
319
+
320
+ if operation not in valid_operations:
321
+ return f"❌ Invalid operation. Supported: {', '.join(valid_operations)}"
322
+
323
+ try:
324
+ # Load user configuration
325
+ config = await self._load_db_config(user_id)
326
+ if not config:
327
+ return "❌ Database not configured. Please run supabase_setup() first."
328
+
329
+ db_type = config.get('type', 'supabase').lower()
330
+
331
+ if db_type == 'supabase':
332
+ client = self._create_supabase_client(config)
333
+ if not client:
334
+ return "❌ Failed to connect to Supabase"
335
+
336
+ # Perform Supabase operations
337
+ if operation == 'select':
338
+ query = client.table(table).select('*')
339
+ if filters:
340
+ for key, value in filters.items():
341
+ query = query.eq(key, value)
342
+
343
+ response = query.execute()
344
+ results = response.data
345
+
346
+ return f"✅ Select operation successful!\n📊 Results ({len(results)} rows):\n{json.dumps(results, indent=2, default=str)}"
347
+
348
+ elif operation == 'insert':
349
+ if not data:
350
+ return "❌ Data is required for insert operation"
351
+
352
+ response = client.table(table).insert(data).execute()
353
+ return f"✅ Insert operation successful!\n📝 Inserted data: {json.dumps(response.data, indent=2, default=str)}"
354
+
355
+ elif operation == 'update':
356
+ if not data:
357
+ return "❌ Data is required for update operation"
358
+ if not filters:
359
+ return "❌ Filters are required for update operation to prevent updating all rows"
360
+
361
+ query = client.table(table).update(data)
362
+ for key, value in filters.items():
363
+ query = query.eq(key, value)
364
+
365
+ response = query.execute()
366
+ return f"✅ Update operation successful!\n📝 Updated rows: {len(response.data)}"
367
+
368
+ elif operation == 'delete':
369
+ if not filters:
370
+ return "❌ Filters are required for delete operation to prevent deleting all rows"
371
+
372
+ query = client.table(table).delete()
373
+ for key, value in filters.items():
374
+ query = query.eq(key, value)
375
+
376
+ response = query.execute()
377
+ return f"✅ Delete operation successful!\n📝 Deleted rows: {len(response.data)}"
378
+
379
+ elif db_type == 'postgresql':
380
+ conn = self._create_postgres_connection(config)
381
+ if not conn:
382
+ return "❌ Failed to connect to PostgreSQL"
383
+
384
+ try:
385
+ cursor = conn.cursor()
386
+
387
+ if operation == 'select':
388
+ sql = f"SELECT * FROM {table}"
389
+ params = []
390
+
391
+ if filters:
392
+ where_clauses = []
393
+ for key, value in filters.items():
394
+ where_clauses.append(f"{key} = %s")
395
+ params.append(value)
396
+ sql += " WHERE " + " AND ".join(where_clauses)
397
+
398
+ cursor.execute(sql, params)
399
+ results = cursor.fetchall()
400
+ rows = [dict(row) for row in results]
401
+
402
+ return f"✅ Select operation successful!\n📊 Results ({len(rows)} rows):\n{json.dumps(rows, indent=2, default=str)}"
403
+
404
+ elif operation == 'insert':
405
+ if not data:
406
+ return "❌ Data is required for insert operation"
407
+
408
+ columns = list(data.keys())
409
+ values = list(data.values())
410
+ placeholders = ', '.join(['%s'] * len(values))
411
+
412
+ sql = f"INSERT INTO {table} ({', '.join(columns)}) VALUES ({placeholders})"
413
+ cursor.execute(sql, values)
414
+ conn.commit()
415
+
416
+ return f"✅ Insert operation successful!\n📝 Affected rows: {cursor.rowcount}"
417
+
418
+ elif operation == 'update':
419
+ if not data:
420
+ return "❌ Data is required for update operation"
421
+ if not filters:
422
+ return "❌ Filters are required for update operation"
423
+
424
+ set_clauses = []
425
+ params = []
426
+ for key, value in data.items():
427
+ set_clauses.append(f"{key} = %s")
428
+ params.append(value)
429
+
430
+ where_clauses = []
431
+ for key, value in filters.items():
432
+ where_clauses.append(f"{key} = %s")
433
+ params.append(value)
434
+
435
+ sql = f"UPDATE {table} SET {', '.join(set_clauses)} WHERE {' AND '.join(where_clauses)}"
436
+ cursor.execute(sql, params)
437
+ conn.commit()
438
+
439
+ return f"✅ Update operation successful!\n📝 Affected rows: {cursor.rowcount}"
440
+
441
+ elif operation == 'delete':
442
+ if not filters:
443
+ return "❌ Filters are required for delete operation"
444
+
445
+ where_clauses = []
446
+ params = []
447
+ for key, value in filters.items():
448
+ where_clauses.append(f"{key} = %s")
449
+ params.append(value)
450
+
451
+ sql = f"DELETE FROM {table} WHERE {' AND '.join(where_clauses)}"
452
+ cursor.execute(sql, params)
453
+ conn.commit()
454
+
455
+ return f"✅ Delete operation successful!\n📝 Affected rows: {cursor.rowcount}"
456
+
457
+ finally:
458
+ cursor.close()
459
+ conn.close()
460
+
461
+ else:
462
+ return "❌ Unknown database type in configuration"
463
+
464
+ except Exception as e:
465
+ return f"❌ Table operation failed: {str(e)}"
466
+
467
+ @tool(description="Check database connection status and configuration")
468
+ async def supabase_status(self) -> str:
469
+ """Check database connection and configuration status"""
470
+ user_id = await self._get_authenticated_user_id()
471
+ if not user_id:
472
+ return "❌ Authentication required"
473
+
474
+ try:
475
+ # Load configuration
476
+ config = await self._load_db_config(user_id)
477
+
478
+ result = ["📋 Database Status:\n"]
479
+
480
+ if config:
481
+ db_type = config.get('type', 'supabase')
482
+ created_at = config.get('created_at', 'unknown')
483
+ result.append(f"✅ Configuration: Active")
484
+ result.append(f"🗄️ Database Type: {db_type.title()}")
485
+ result.append(f"🕐 Created: {created_at}")
486
+
487
+ # Test connection
488
+ try:
489
+ if db_type.lower() == 'supabase':
490
+ client = self._create_supabase_client(config)
491
+ if client:
492
+ # Try a simple operation
493
+ client.table('_test_').select('*').limit(1).execute()
494
+ result.append("🟢 Connection: Active")
495
+ else:
496
+ result.append("🔴 Connection: Failed")
497
+
498
+ elif db_type.lower() == 'postgresql':
499
+ conn = self._create_postgres_connection(config)
500
+ if conn:
501
+ cursor = conn.cursor()
502
+ cursor.execute("SELECT 1")
503
+ cursor.close()
504
+ conn.close()
505
+ result.append("🟢 Connection: Active")
506
+ else:
507
+ result.append("🔴 Connection: Failed")
508
+
509
+ except Exception as e:
510
+ result.append(f"🟡 Connection: Warning - {str(e)}")
511
+
512
+ else:
513
+ result.append("❌ No configuration found")
514
+ result.append("💡 Use supabase_setup() to configure your database")
515
+ return "\n".join(result)
516
+
517
+ result.append("\n💡 Use supabase_query() for SQL or supabase_table_ops() for CRUD operations")
518
+
519
+ return "\n".join(result)
520
+
521
+ except Exception as e:
522
+ return f"❌ Error checking status: {str(e)}"