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.
- 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/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-0.2.0.dist-info → webagents-0.2.2.dist-info}/METADATA +33 -9
- {webagents-0.2.0.dist-info → webagents-0.2.2.dist-info}/RECORD +20 -9
- {webagents-0.2.0.dist-info → webagents-0.2.2.dist-info}/WHEEL +0 -0
- {webagents-0.2.0.dist-info → webagents-0.2.2.dist-info}/entry_points.txt +0 -0
- {webagents-0.2.0.dist-info → webagents-0.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,428 @@
|
|
1
|
+
"""
|
2
|
+
Minimalistic MongoDB Skill for WebAgents
|
3
|
+
|
4
|
+
This skill allows users to:
|
5
|
+
- Connect to MongoDB databases (local, Atlas, or self-hosted)
|
6
|
+
- Perform CRUD operations on collections
|
7
|
+
- Execute aggregation pipelines and queries
|
8
|
+
- Manage database schemas and indexes
|
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 pymongo import MongoClient
|
24
|
+
from pymongo.errors import ConnectionFailure, OperationFailure, ConfigurationError
|
25
|
+
from bson import ObjectId
|
26
|
+
from bson.errors import InvalidId
|
27
|
+
MONGODB_AVAILABLE = True
|
28
|
+
except ImportError:
|
29
|
+
MONGODB_AVAILABLE = False
|
30
|
+
# Create dummy classes for type hints when pymongo is not available
|
31
|
+
class MongoClient:
|
32
|
+
pass
|
33
|
+
class ObjectId:
|
34
|
+
pass
|
35
|
+
class ConnectionFailure(Exception):
|
36
|
+
pass
|
37
|
+
|
38
|
+
|
39
|
+
class MongoDBSkill(Skill):
|
40
|
+
"""Minimalistic MongoDB skill for document database operations"""
|
41
|
+
|
42
|
+
def __init__(self):
|
43
|
+
super().__init__()
|
44
|
+
if not MONGODB_AVAILABLE:
|
45
|
+
raise ImportError("MongoDB dependencies not installed. Install with: pip install pymongo")
|
46
|
+
|
47
|
+
def get_dependencies(self) -> List[str]:
|
48
|
+
"""Skill dependencies"""
|
49
|
+
return ['auth', 'kv']
|
50
|
+
|
51
|
+
@prompt(priority=40, scope=["owner", "all"])
|
52
|
+
def mongodb_prompt(self) -> str:
|
53
|
+
"""Prompt describing MongoDB capabilities"""
|
54
|
+
return """
|
55
|
+
Minimalistic MongoDB integration for document database operations. Available tools:
|
56
|
+
|
57
|
+
• mongodb_setup(config) - Set up MongoDB connection securely (Atlas, local, or self-hosted)
|
58
|
+
• mongodb_query(database, collection, operation, query, data) - Execute database operations
|
59
|
+
• mongodb_aggregate(database, collection, pipeline) - Run aggregation pipelines
|
60
|
+
• mongodb_status() - Check database connection and configuration status
|
61
|
+
|
62
|
+
Features:
|
63
|
+
- MongoDB Atlas, local, and self-hosted support
|
64
|
+
- Document CRUD operations (Create, Read, Update, Delete)
|
65
|
+
- Aggregation pipeline execution
|
66
|
+
- Index management and optimization
|
67
|
+
- Secure connection string storage
|
68
|
+
- Per-user database isolation via Auth skill
|
69
|
+
|
70
|
+
Setup: Configure with your MongoDB connection string or Atlas credentials.
|
71
|
+
"""
|
72
|
+
|
73
|
+
# Helper methods for auth and kv skills
|
74
|
+
async def _get_auth_skill(self):
|
75
|
+
"""Get auth skill for user context"""
|
76
|
+
return self.agent.skills.get('auth')
|
77
|
+
|
78
|
+
async def _get_kv_skill(self):
|
79
|
+
"""Get KV skill for secure storage"""
|
80
|
+
return self.agent.skills.get('kv')
|
81
|
+
|
82
|
+
async def _get_authenticated_user_id(self) -> Optional[str]:
|
83
|
+
"""Get authenticated user ID from context"""
|
84
|
+
try:
|
85
|
+
context = get_context()
|
86
|
+
if context and context.auth and context.auth.authenticated:
|
87
|
+
return context.auth.user_id
|
88
|
+
return None
|
89
|
+
except Exception as e:
|
90
|
+
self.logger.error(f"Failed to get user context: {e}")
|
91
|
+
return None
|
92
|
+
|
93
|
+
async def _save_mongodb_config(self, user_id: str, config: Dict[str, Any]) -> bool:
|
94
|
+
"""Save MongoDB configuration securely using KV skill"""
|
95
|
+
try:
|
96
|
+
kv_skill = await self._get_kv_skill()
|
97
|
+
if kv_skill:
|
98
|
+
config_data = {
|
99
|
+
**config,
|
100
|
+
'created_at': datetime.now().isoformat(),
|
101
|
+
'updated_at': datetime.now().isoformat()
|
102
|
+
}
|
103
|
+
await kv_skill.kv_set(
|
104
|
+
key='config',
|
105
|
+
value=json.dumps(config_data),
|
106
|
+
namespace=f'mongodb:{user_id}'
|
107
|
+
)
|
108
|
+
return True
|
109
|
+
else:
|
110
|
+
# Fallback to in-memory storage
|
111
|
+
if not hasattr(self.agent, '_mongodb_configs'):
|
112
|
+
self.agent._mongodb_configs = {}
|
113
|
+
self.agent._mongodb_configs[user_id] = config
|
114
|
+
return True
|
115
|
+
except Exception as e:
|
116
|
+
self.logger.error(f"Failed to save MongoDB config: {e}")
|
117
|
+
return False
|
118
|
+
|
119
|
+
async def _load_mongodb_config(self, user_id: str) -> Optional[Dict[str, Any]]:
|
120
|
+
"""Load MongoDB configuration from KV skill"""
|
121
|
+
try:
|
122
|
+
kv_skill = await self._get_kv_skill()
|
123
|
+
if kv_skill:
|
124
|
+
config_json = await kv_skill.kv_get(
|
125
|
+
key='config',
|
126
|
+
namespace=f'mongodb:{user_id}'
|
127
|
+
)
|
128
|
+
if config_json:
|
129
|
+
return json.loads(config_json)
|
130
|
+
else:
|
131
|
+
# Fallback to in-memory storage
|
132
|
+
if hasattr(self.agent, '_mongodb_configs'):
|
133
|
+
return self.agent._mongodb_configs.get(user_id)
|
134
|
+
return None
|
135
|
+
except Exception as e:
|
136
|
+
self.logger.error(f"Failed to load MongoDB config: {e}")
|
137
|
+
return None
|
138
|
+
|
139
|
+
def _create_mongodb_client(self, config: Dict[str, Any]) -> Optional[MongoClient]:
|
140
|
+
"""Create MongoDB client from configuration"""
|
141
|
+
try:
|
142
|
+
connection_string = config.get('connection_string')
|
143
|
+
if connection_string:
|
144
|
+
# Use connection string (Atlas, local, or custom)
|
145
|
+
return MongoClient(connection_string)
|
146
|
+
|
147
|
+
# Alternative: individual connection parameters
|
148
|
+
host = config.get('host', 'localhost')
|
149
|
+
port = config.get('port', 27017)
|
150
|
+
username = config.get('username')
|
151
|
+
password = config.get('password')
|
152
|
+
database = config.get('database')
|
153
|
+
|
154
|
+
if username and password:
|
155
|
+
if database:
|
156
|
+
connection_string = f"mongodb://{username}:{password}@{host}:{port}/{database}"
|
157
|
+
else:
|
158
|
+
connection_string = f"mongodb://{username}:{password}@{host}:{port}"
|
159
|
+
else:
|
160
|
+
connection_string = f"mongodb://{host}:{port}"
|
161
|
+
|
162
|
+
return MongoClient(connection_string)
|
163
|
+
|
164
|
+
except Exception as e:
|
165
|
+
self.logger.error(f"Failed to create MongoDB client: {e}")
|
166
|
+
return None
|
167
|
+
|
168
|
+
def _serialize_mongodb_result(self, data: Any) -> Any:
|
169
|
+
"""Serialize MongoDB results for JSON output"""
|
170
|
+
if isinstance(data, ObjectId) or (hasattr(data, '__class__') and data.__class__.__name__ == 'ObjectId'):
|
171
|
+
return str(data)
|
172
|
+
elif isinstance(data, dict):
|
173
|
+
return {k: self._serialize_mongodb_result(v) for k, v in data.items()}
|
174
|
+
elif isinstance(data, list):
|
175
|
+
return [self._serialize_mongodb_result(item) for item in data]
|
176
|
+
elif isinstance(data, datetime):
|
177
|
+
return data.isoformat()
|
178
|
+
else:
|
179
|
+
return data
|
180
|
+
|
181
|
+
# Public tools
|
182
|
+
@tool(description="Set up MongoDB database connection", scope="owner")
|
183
|
+
async def mongodb_setup(self, config: Dict[str, Any]) -> str:
|
184
|
+
"""Set up MongoDB connection configuration"""
|
185
|
+
user_id = await self._get_authenticated_user_id()
|
186
|
+
if not user_id:
|
187
|
+
return "❌ Authentication required"
|
188
|
+
|
189
|
+
if not config:
|
190
|
+
return "❌ Configuration is required"
|
191
|
+
|
192
|
+
try:
|
193
|
+
# Validate configuration
|
194
|
+
if not config.get('connection_string') and not all([
|
195
|
+
config.get('host'), config.get('port')
|
196
|
+
]):
|
197
|
+
return "❌ MongoDB configuration requires either 'connection_string' or 'host' and 'port'"
|
198
|
+
|
199
|
+
# Test connection
|
200
|
+
client = self._create_mongodb_client(config)
|
201
|
+
if not client:
|
202
|
+
return "❌ Failed to create MongoDB client"
|
203
|
+
|
204
|
+
# Test connection with ping
|
205
|
+
try:
|
206
|
+
client.admin.command('ping')
|
207
|
+
client.close()
|
208
|
+
except ConnectionFailure:
|
209
|
+
return "❌ MongoDB connection test failed"
|
210
|
+
except Exception as e:
|
211
|
+
return f"❌ MongoDB connection error: {str(e)}"
|
212
|
+
|
213
|
+
# Save configuration
|
214
|
+
success = await self._save_mongodb_config(user_id, config)
|
215
|
+
|
216
|
+
if success:
|
217
|
+
connection_type = "Atlas" if "mongodb.net" in config.get('connection_string', '') else "MongoDB"
|
218
|
+
return f"✅ {connection_type} configuration saved successfully!\n🔧 Database type: MongoDB\n🔒 Connection details stored securely"
|
219
|
+
else:
|
220
|
+
return "❌ Failed to save configuration"
|
221
|
+
|
222
|
+
except Exception as e:
|
223
|
+
return f"❌ Setup failed: {str(e)}"
|
224
|
+
|
225
|
+
@tool(description="Execute MongoDB operations on collections")
|
226
|
+
async def mongodb_query(self, database: str, collection: str, operation: str,
|
227
|
+
query: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None) -> str:
|
228
|
+
"""Execute MongoDB operations (find, insert, update, delete)"""
|
229
|
+
user_id = await self._get_authenticated_user_id()
|
230
|
+
if not user_id:
|
231
|
+
return "❌ Authentication required"
|
232
|
+
|
233
|
+
if not database or not collection or not operation:
|
234
|
+
return "❌ Database, collection, and operation are required"
|
235
|
+
|
236
|
+
operation = operation.lower().strip()
|
237
|
+
valid_operations = ['find', 'find_one', 'insert_one', 'insert_many', 'update_one', 'update_many', 'delete_one', 'delete_many', 'count']
|
238
|
+
|
239
|
+
if operation not in valid_operations:
|
240
|
+
return f"❌ Invalid operation. Supported: {', '.join(valid_operations)}"
|
241
|
+
|
242
|
+
try:
|
243
|
+
# Load user configuration
|
244
|
+
config = await self._load_mongodb_config(user_id)
|
245
|
+
if not config:
|
246
|
+
return "❌ MongoDB not configured. Please run mongodb_setup() first."
|
247
|
+
|
248
|
+
# Create client and get collection
|
249
|
+
client = self._create_mongodb_client(config)
|
250
|
+
if not client:
|
251
|
+
return "❌ Failed to connect to MongoDB"
|
252
|
+
|
253
|
+
try:
|
254
|
+
db = client[database]
|
255
|
+
coll = db[collection]
|
256
|
+
|
257
|
+
# Execute operation
|
258
|
+
if operation == 'find':
|
259
|
+
query = query or {}
|
260
|
+
results = list(coll.find(query))
|
261
|
+
serialized_results = self._serialize_mongodb_result(results)
|
262
|
+
return f"✅ Find operation successful!\n📊 Results ({len(results)} documents):\n{json.dumps(serialized_results, indent=2, default=str)}"
|
263
|
+
|
264
|
+
elif operation == 'find_one':
|
265
|
+
query = query or {}
|
266
|
+
result = coll.find_one(query)
|
267
|
+
if result:
|
268
|
+
serialized_result = self._serialize_mongodb_result(result)
|
269
|
+
return f"✅ Find one operation successful!\n📊 Result:\n{json.dumps(serialized_result, indent=2, default=str)}"
|
270
|
+
else:
|
271
|
+
return "✅ Find one operation successful!\n📊 No document found"
|
272
|
+
|
273
|
+
elif operation == 'insert_one':
|
274
|
+
if not data:
|
275
|
+
return "❌ Data is required for insert_one operation"
|
276
|
+
|
277
|
+
result = coll.insert_one(data)
|
278
|
+
return f"✅ Insert one operation successful!\n📝 Inserted document ID: {result.inserted_id}"
|
279
|
+
|
280
|
+
elif operation == 'insert_many':
|
281
|
+
if not data or not isinstance(data.get('documents'), list):
|
282
|
+
return "❌ Data with 'documents' array is required for insert_many operation"
|
283
|
+
|
284
|
+
result = coll.insert_many(data['documents'])
|
285
|
+
return f"✅ Insert many operation successful!\n📝 Inserted {len(result.inserted_ids)} documents"
|
286
|
+
|
287
|
+
elif operation == 'update_one':
|
288
|
+
if not query:
|
289
|
+
return "❌ Query filter is required for update_one operation"
|
290
|
+
if not data:
|
291
|
+
return "❌ Update data is required for update_one operation"
|
292
|
+
|
293
|
+
result = coll.update_one(query, {'$set': data})
|
294
|
+
return f"✅ Update one operation successful!\n📝 Matched: {result.matched_count}, Modified: {result.modified_count}"
|
295
|
+
|
296
|
+
elif operation == 'update_many':
|
297
|
+
if not query:
|
298
|
+
return "❌ Query filter is required for update_many operation"
|
299
|
+
if not data:
|
300
|
+
return "❌ Update data is required for update_many operation"
|
301
|
+
|
302
|
+
result = coll.update_many(query, {'$set': data})
|
303
|
+
return f"✅ Update many operation successful!\n📝 Matched: {result.matched_count}, Modified: {result.modified_count}"
|
304
|
+
|
305
|
+
elif operation == 'delete_one':
|
306
|
+
if not query:
|
307
|
+
return "❌ Query filter is required for delete_one operation"
|
308
|
+
|
309
|
+
result = coll.delete_one(query)
|
310
|
+
return f"✅ Delete one operation successful!\n📝 Deleted: {result.deleted_count} document"
|
311
|
+
|
312
|
+
elif operation == 'delete_many':
|
313
|
+
if not query:
|
314
|
+
return "❌ Query filter is required for delete_many operation"
|
315
|
+
|
316
|
+
result = coll.delete_many(query)
|
317
|
+
return f"✅ Delete many operation successful!\n📝 Deleted: {result.deleted_count} documents"
|
318
|
+
|
319
|
+
elif operation == 'count':
|
320
|
+
query = query or {}
|
321
|
+
count = coll.count_documents(query)
|
322
|
+
return f"✅ Count operation successful!\n📊 Document count: {count}"
|
323
|
+
|
324
|
+
finally:
|
325
|
+
client.close()
|
326
|
+
|
327
|
+
except Exception as e:
|
328
|
+
return f"❌ MongoDB operation failed: {str(e)}"
|
329
|
+
|
330
|
+
@tool(description="Execute MongoDB aggregation pipelines")
|
331
|
+
async def mongodb_aggregate(self, database: str, collection: str, pipeline: List[Dict[str, Any]]) -> str:
|
332
|
+
"""Execute MongoDB aggregation pipeline"""
|
333
|
+
user_id = await self._get_authenticated_user_id()
|
334
|
+
if not user_id:
|
335
|
+
return "❌ Authentication required"
|
336
|
+
|
337
|
+
if not database or not collection or not pipeline:
|
338
|
+
return "❌ Database, collection, and pipeline are required"
|
339
|
+
|
340
|
+
if not isinstance(pipeline, list):
|
341
|
+
return "❌ Pipeline must be a list of aggregation stages"
|
342
|
+
|
343
|
+
try:
|
344
|
+
# Load user configuration
|
345
|
+
config = await self._load_mongodb_config(user_id)
|
346
|
+
if not config:
|
347
|
+
return "❌ MongoDB not configured. Please run mongodb_setup() first."
|
348
|
+
|
349
|
+
# Create client and get collection
|
350
|
+
client = self._create_mongodb_client(config)
|
351
|
+
if not client:
|
352
|
+
return "❌ Failed to connect to MongoDB"
|
353
|
+
|
354
|
+
try:
|
355
|
+
db = client[database]
|
356
|
+
coll = db[collection]
|
357
|
+
|
358
|
+
# Execute aggregation pipeline
|
359
|
+
results = list(coll.aggregate(pipeline))
|
360
|
+
serialized_results = self._serialize_mongodb_result(results)
|
361
|
+
|
362
|
+
return f"✅ Aggregation pipeline executed successfully!\n📊 Results ({len(results)} documents):\n{json.dumps(serialized_results, indent=2, default=str)}"
|
363
|
+
|
364
|
+
finally:
|
365
|
+
client.close()
|
366
|
+
|
367
|
+
except Exception as e:
|
368
|
+
return f"❌ Aggregation pipeline failed: {str(e)}"
|
369
|
+
|
370
|
+
@tool(description="Check MongoDB connection status and configuration")
|
371
|
+
async def mongodb_status(self) -> str:
|
372
|
+
"""Check MongoDB connection and configuration status"""
|
373
|
+
user_id = await self._get_authenticated_user_id()
|
374
|
+
if not user_id:
|
375
|
+
return "❌ Authentication required"
|
376
|
+
|
377
|
+
try:
|
378
|
+
# Load configuration
|
379
|
+
config = await self._load_mongodb_config(user_id)
|
380
|
+
|
381
|
+
result = ["📋 MongoDB Status:\n"]
|
382
|
+
|
383
|
+
if config:
|
384
|
+
connection_string = config.get('connection_string', '')
|
385
|
+
created_at = config.get('created_at', 'unknown')
|
386
|
+
|
387
|
+
# Determine connection type
|
388
|
+
if 'mongodb.net' in connection_string:
|
389
|
+
db_type = "MongoDB Atlas"
|
390
|
+
elif 'localhost' in connection_string or '127.0.0.1' in connection_string:
|
391
|
+
db_type = "Local MongoDB"
|
392
|
+
else:
|
393
|
+
db_type = "MongoDB"
|
394
|
+
|
395
|
+
result.append(f"✅ Configuration: Active")
|
396
|
+
result.append(f"🗄️ Database Type: {db_type}")
|
397
|
+
result.append(f"🕐 Created: {created_at}")
|
398
|
+
|
399
|
+
# Test connection
|
400
|
+
try:
|
401
|
+
client = self._create_mongodb_client(config)
|
402
|
+
if client:
|
403
|
+
client.admin.command('ping')
|
404
|
+
|
405
|
+
# Get server info
|
406
|
+
server_info = client.server_info()
|
407
|
+
version = server_info.get('version', 'unknown')
|
408
|
+
|
409
|
+
client.close()
|
410
|
+
result.append("🟢 Connection: Active")
|
411
|
+
result.append(f"📦 MongoDB Version: {version}")
|
412
|
+
else:
|
413
|
+
result.append("🔴 Connection: Failed")
|
414
|
+
|
415
|
+
except Exception as e:
|
416
|
+
result.append(f"🟡 Connection: Warning - {str(e)}")
|
417
|
+
|
418
|
+
else:
|
419
|
+
result.append("❌ No configuration found")
|
420
|
+
result.append("💡 Use mongodb_setup() to configure your MongoDB connection")
|
421
|
+
return "\n".join(result)
|
422
|
+
|
423
|
+
result.append("\n💡 Use mongodb_query() for CRUD operations or mongodb_aggregate() for pipelines")
|
424
|
+
|
425
|
+
return "\n".join(result)
|
426
|
+
|
427
|
+
except Exception as e:
|
428
|
+
return f"❌ Error checking status: {str(e)}"
|