webagents 0.1.13__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.
Files changed (77) hide show
  1. webagents/__init__.py +1 -1
  2. webagents/__main__.py +55 -0
  3. webagents/agents/__init__.py +1 -1
  4. webagents/agents/core/__init__.py +1 -1
  5. webagents/agents/core/base_agent.py +15 -15
  6. webagents/agents/core/handoffs.py +1 -1
  7. webagents/agents/skills/__init__.py +11 -11
  8. webagents/agents/skills/base.py +1 -1
  9. webagents/agents/skills/core/llm/litellm/__init__.py +1 -1
  10. webagents/agents/skills/core/llm/litellm/skill.py +1 -1
  11. webagents/agents/skills/core/mcp/README.md +2 -2
  12. webagents/agents/skills/core/mcp/skill.py +2 -2
  13. webagents/agents/skills/core/memory/long_term_memory/memory_skill.py +14 -14
  14. webagents/agents/skills/core/memory/short_term_memory/__init__.py +1 -1
  15. webagents/agents/skills/core/memory/short_term_memory/skill.py +1 -1
  16. webagents/agents/skills/core/memory/vector_memory/skill.py +6 -6
  17. webagents/agents/skills/core/planning/__init__.py +1 -1
  18. webagents/agents/skills/ecosystem/crewai/__init__.py +3 -1
  19. webagents/agents/skills/ecosystem/crewai/skill.py +158 -0
  20. webagents/agents/skills/ecosystem/database/__init__.py +3 -1
  21. webagents/agents/skills/ecosystem/database/skill.py +522 -0
  22. webagents/agents/skills/ecosystem/google/calendar/skill.py +1 -1
  23. webagents/agents/skills/ecosystem/mongodb/__init__.py +3 -0
  24. webagents/agents/skills/ecosystem/mongodb/skill.py +428 -0
  25. webagents/agents/skills/ecosystem/n8n/README.md +287 -0
  26. webagents/agents/skills/ecosystem/n8n/__init__.py +3 -0
  27. webagents/agents/skills/ecosystem/n8n/skill.py +341 -0
  28. webagents/agents/skills/ecosystem/x_com/README.md +401 -0
  29. webagents/agents/skills/ecosystem/x_com/__init__.py +3 -0
  30. webagents/agents/skills/ecosystem/x_com/skill.py +1048 -0
  31. webagents/agents/skills/ecosystem/zapier/README.md +363 -0
  32. webagents/agents/skills/ecosystem/zapier/__init__.py +3 -0
  33. webagents/agents/skills/ecosystem/zapier/skill.py +337 -0
  34. webagents/agents/skills/robutler/__init__.py +2 -2
  35. webagents/agents/skills/robutler/auth/__init__.py +3 -3
  36. webagents/agents/skills/robutler/auth/skill.py +16 -16
  37. webagents/agents/skills/robutler/crm/__init__.py +2 -2
  38. webagents/agents/skills/robutler/crm/skill.py +5 -5
  39. webagents/agents/skills/robutler/discovery/README.md +5 -5
  40. webagents/agents/skills/robutler/discovery/__init__.py +2 -2
  41. webagents/agents/skills/robutler/discovery/skill.py +21 -21
  42. webagents/agents/skills/robutler/message_history/__init__.py +2 -2
  43. webagents/agents/skills/robutler/message_history/skill.py +5 -5
  44. webagents/agents/skills/robutler/nli/__init__.py +1 -1
  45. webagents/agents/skills/robutler/nli/skill.py +9 -9
  46. webagents/agents/skills/robutler/payments/__init__.py +3 -3
  47. webagents/agents/skills/robutler/payments/exceptions.py +1 -1
  48. webagents/agents/skills/robutler/payments/skill.py +23 -23
  49. webagents/agents/skills/robutler/storage/__init__.py +2 -2
  50. webagents/agents/skills/robutler/storage/files/__init__.py +2 -2
  51. webagents/agents/skills/robutler/storage/files/skill.py +4 -4
  52. webagents/agents/skills/robutler/storage/json/__init__.py +1 -1
  53. webagents/agents/skills/robutler/storage/json/skill.py +3 -3
  54. webagents/agents/skills/robutler/storage/kv/skill.py +3 -3
  55. webagents/agents/skills/robutler/storage.py +6 -6
  56. webagents/agents/tools/decorators.py +12 -12
  57. webagents/server/__init__.py +3 -3
  58. webagents/server/context/context_vars.py +2 -2
  59. webagents/server/core/app.py +13 -13
  60. webagents/server/core/middleware.py +3 -3
  61. webagents/server/core/models.py +1 -1
  62. webagents/server/core/monitoring.py +2 -2
  63. webagents/server/middleware.py +1 -1
  64. webagents/server/models.py +2 -2
  65. webagents/server/monitoring.py +15 -15
  66. webagents/utils/logging.py +20 -20
  67. webagents-0.2.2.dist-info/METADATA +266 -0
  68. webagents-0.2.2.dist-info/RECORD +105 -0
  69. webagents-0.2.2.dist-info/licenses/LICENSE +20 -0
  70. webagents/api/__init__.py +0 -17
  71. webagents/api/client.py +0 -1207
  72. webagents/api/types.py +0 -253
  73. webagents-0.1.13.dist-info/METADATA +0 -32
  74. webagents-0.1.13.dist-info/RECORD +0 -96
  75. webagents-0.1.13.dist-info/licenses/LICENSE +0 -1
  76. {webagents-0.1.13.dist-info → webagents-0.2.2.dist-info}/WHEEL +0 -0
  77. {webagents-0.1.13.dist-info → webagents-0.2.2.dist-info}/entry_points.txt +0 -0
@@ -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)}"
@@ -178,7 +178,7 @@ class GoogleCalendarSkill(Skill):
178
178
  <head>
179
179
  <meta charset=\"utf-8\" />
180
180
  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />
181
- <title>Robutler – Google Calendar</title>
181
+ <title>WebAgents – Google Calendar</title>
182
182
  <style>
183
183
  :root { color-scheme: light dark; }
184
184
  html, body { height: 100%; margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; }
@@ -0,0 +1,3 @@
1
+ from .skill import MongoDBSkill
2
+
3
+ __all__ = ['MongoDBSkill']