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.
- webagents/__init__.py +1 -1
- webagents/__main__.py +55 -0
- webagents/agents/__init__.py +1 -1
- webagents/agents/core/__init__.py +1 -1
- webagents/agents/core/base_agent.py +15 -15
- webagents/agents/core/handoffs.py +1 -1
- webagents/agents/skills/__init__.py +11 -11
- webagents/agents/skills/base.py +1 -1
- webagents/agents/skills/core/llm/litellm/__init__.py +1 -1
- webagents/agents/skills/core/llm/litellm/skill.py +1 -1
- webagents/agents/skills/core/mcp/README.md +2 -2
- webagents/agents/skills/core/mcp/skill.py +2 -2
- webagents/agents/skills/core/memory/long_term_memory/memory_skill.py +14 -14
- webagents/agents/skills/core/memory/short_term_memory/__init__.py +1 -1
- webagents/agents/skills/core/memory/short_term_memory/skill.py +1 -1
- webagents/agents/skills/core/memory/vector_memory/skill.py +6 -6
- webagents/agents/skills/core/planning/__init__.py +1 -1
- webagents/agents/skills/ecosystem/crewai/__init__.py +3 -1
- webagents/agents/skills/ecosystem/crewai/skill.py +158 -0
- webagents/agents/skills/ecosystem/database/__init__.py +3 -1
- webagents/agents/skills/ecosystem/database/skill.py +522 -0
- webagents/agents/skills/ecosystem/google/calendar/skill.py +1 -1
- webagents/agents/skills/ecosystem/mongodb/__init__.py +3 -0
- webagents/agents/skills/ecosystem/mongodb/skill.py +428 -0
- webagents/agents/skills/ecosystem/n8n/README.md +287 -0
- webagents/agents/skills/ecosystem/n8n/__init__.py +3 -0
- webagents/agents/skills/ecosystem/n8n/skill.py +341 -0
- webagents/agents/skills/ecosystem/x_com/README.md +401 -0
- webagents/agents/skills/ecosystem/x_com/__init__.py +3 -0
- webagents/agents/skills/ecosystem/x_com/skill.py +1048 -0
- webagents/agents/skills/ecosystem/zapier/README.md +363 -0
- webagents/agents/skills/ecosystem/zapier/__init__.py +3 -0
- webagents/agents/skills/ecosystem/zapier/skill.py +337 -0
- webagents/agents/skills/robutler/__init__.py +2 -2
- webagents/agents/skills/robutler/auth/__init__.py +3 -3
- webagents/agents/skills/robutler/auth/skill.py +16 -16
- webagents/agents/skills/robutler/crm/__init__.py +2 -2
- webagents/agents/skills/robutler/crm/skill.py +5 -5
- webagents/agents/skills/robutler/discovery/README.md +5 -5
- webagents/agents/skills/robutler/discovery/__init__.py +2 -2
- webagents/agents/skills/robutler/discovery/skill.py +21 -21
- webagents/agents/skills/robutler/message_history/__init__.py +2 -2
- webagents/agents/skills/robutler/message_history/skill.py +5 -5
- webagents/agents/skills/robutler/nli/__init__.py +1 -1
- webagents/agents/skills/robutler/nli/skill.py +9 -9
- webagents/agents/skills/robutler/payments/__init__.py +3 -3
- webagents/agents/skills/robutler/payments/exceptions.py +1 -1
- webagents/agents/skills/robutler/payments/skill.py +23 -23
- webagents/agents/skills/robutler/storage/__init__.py +2 -2
- webagents/agents/skills/robutler/storage/files/__init__.py +2 -2
- webagents/agents/skills/robutler/storage/files/skill.py +4 -4
- webagents/agents/skills/robutler/storage/json/__init__.py +1 -1
- webagents/agents/skills/robutler/storage/json/skill.py +3 -3
- webagents/agents/skills/robutler/storage/kv/skill.py +3 -3
- webagents/agents/skills/robutler/storage.py +6 -6
- webagents/agents/tools/decorators.py +12 -12
- webagents/server/__init__.py +3 -3
- webagents/server/context/context_vars.py +2 -2
- webagents/server/core/app.py +13 -13
- webagents/server/core/middleware.py +3 -3
- webagents/server/core/models.py +1 -1
- webagents/server/core/monitoring.py +2 -2
- webagents/server/middleware.py +1 -1
- webagents/server/models.py +2 -2
- webagents/server/monitoring.py +15 -15
- webagents/utils/logging.py +20 -20
- webagents-0.2.2.dist-info/METADATA +266 -0
- webagents-0.2.2.dist-info/RECORD +105 -0
- webagents-0.2.2.dist-info/licenses/LICENSE +20 -0
- webagents/api/__init__.py +0 -17
- webagents/api/client.py +0 -1207
- webagents/api/types.py +0 -253
- webagents-0.1.13.dist-info/METADATA +0 -32
- webagents-0.1.13.dist-info/RECORD +0 -96
- webagents-0.1.13.dist-info/licenses/LICENSE +0 -1
- {webagents-0.1.13.dist-info → webagents-0.2.2.dist-info}/WHEEL +0 -0
- {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>
|
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"; }
|