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.
@@ -0,0 +1,287 @@
1
+ # n8n Skill for WebAgents
2
+
3
+ Minimalistic n8n integration for workflow automation. This skill allows you to securely manage n8n API credentials and execute workflows from your WebAgent.
4
+
5
+ ## Features
6
+
7
+ - 🔐 **Secure credential storage** via auth and KV skills
8
+ - 🚀 **Execute workflows** with custom input data
9
+ - 📋 **List workflows** from your n8n instance
10
+ - 📊 **Monitor execution status** in real-time
11
+ - 🔒 **Per-user isolation** with authentication
12
+ - 🛡️ **API key validation** during setup
13
+
14
+ ## Prerequisites
15
+
16
+ 1. **n8n instance** (self-hosted or cloud)
17
+ 2. **n8n API key** (generated from n8n Settings > n8n API)
18
+ 3. **WebAgents framework** with auth and kv skills enabled
19
+
20
+ ## Quick Start
21
+
22
+ ### 1. Set up n8n API credentials
23
+
24
+ ```python
25
+ # First, configure your n8n credentials
26
+ await agent.call_tool("n8n_setup",
27
+ api_key="n8n_api_key_here",
28
+ base_url="https://your-n8n-instance.com" # Optional, defaults to localhost:5678
29
+ )
30
+ ```
31
+
32
+ ### 2. List available workflows
33
+
34
+ ```python
35
+ # See all workflows in your n8n instance
36
+ await agent.call_tool("n8n_list_workflows")
37
+ ```
38
+
39
+ ### 3. Execute a workflow
40
+
41
+ ```python
42
+ # Execute a workflow with optional input data
43
+ await agent.call_tool("n8n_execute",
44
+ workflow_id="workflow_id_here",
45
+ data={"input_param": "value"} # Optional
46
+ )
47
+ ```
48
+
49
+ ### 4. Check execution status
50
+
51
+ ```python
52
+ # Monitor workflow execution
53
+ await agent.call_tool("n8n_status",
54
+ execution_id="execution_id_from_execute_response"
55
+ )
56
+ ```
57
+
58
+ ## Tools Reference
59
+
60
+ ### `n8n_setup(api_key, base_url=None)`
61
+ Set up n8n API credentials securely.
62
+
63
+ **Parameters:**
64
+ - `api_key` (str): Your n8n API key from Settings > n8n API
65
+ - `base_url` (str, optional): n8n instance URL (defaults to http://localhost:5678)
66
+
67
+ **Returns:**
68
+ - Success message with configuration details
69
+ - Error message if API key is invalid or instance unreachable
70
+
71
+ **Example:**
72
+ ```python
73
+ result = await agent.call_tool("n8n_setup",
74
+ api_key="n8n_1a2b3c4d5e6f7g8h9i0j",
75
+ base_url="https://n8n.example.com"
76
+ )
77
+ # ✅ n8n credentials saved successfully!
78
+ # 🌐 Base URL: https://n8n.example.com
79
+ # 🔑 API key configured
80
+ ```
81
+
82
+ ### `n8n_execute(workflow_id, data=None)`
83
+ Execute an n8n workflow with optional input data.
84
+
85
+ **Parameters:**
86
+ - `workflow_id` (str): The ID of the workflow to execute
87
+ - `data` (dict, optional): Input data to pass to the workflow
88
+
89
+ **Returns:**
90
+ - Success message with execution ID and status
91
+ - Error message if workflow not found or execution fails
92
+
93
+ **Example:**
94
+ ```python
95
+ result = await agent.call_tool("n8n_execute",
96
+ workflow_id="123",
97
+ data={"customer_email": "user@example.com", "order_id": "ORD-456"}
98
+ )
99
+ # ✅ Workflow executed successfully!
100
+ # 📋 Execution ID: exec_789
101
+ # 📊 Status: running
102
+ ```
103
+
104
+ ### `n8n_list_workflows()`
105
+ List all available workflows in your n8n instance.
106
+
107
+ **Parameters:** None
108
+
109
+ **Returns:**
110
+ - List of workflows with names, IDs, status, and tags
111
+ - Empty message if no workflows found
112
+
113
+ **Example:**
114
+ ```python
115
+ result = await agent.call_tool("n8n_list_workflows")
116
+ # 📋 Available n8n Workflows:
117
+ #
118
+ # 🟢 **Customer Onboarding** (ID: 123)
119
+ # 🏷️ Tags: automation, customers
120
+ #
121
+ # 🔴 **Data Backup** (ID: 456)
122
+ # 🏷️ Tags: maintenance
123
+ #
124
+ # 💡 Use n8n_execute(workflow_id, data) to run a workflow
125
+ ```
126
+
127
+ ### `n8n_status(execution_id)`
128
+ Check the status of a workflow execution.
129
+
130
+ **Parameters:**
131
+ - `execution_id` (str): The execution ID returned from n8n_execute
132
+
133
+ **Returns:**
134
+ - Detailed execution status report
135
+ - Error message if execution not found
136
+
137
+ **Example:**
138
+ ```python
139
+ result = await agent.call_tool("n8n_status", execution_id="exec_789")
140
+ # 📊 Execution Status Report
141
+ # 🆔 Execution ID: exec_789
142
+ # 🔧 Workflow ID: 123
143
+ # ✅ Status: success
144
+ # 🕐 Started: 2024-01-15T10:30:00Z
145
+ # 🕑 Finished: 2024-01-15T10:32:15Z
146
+ ```
147
+
148
+ ## Setup Guide
149
+
150
+ ### Getting your n8n API Key
151
+
152
+ 1. Open your n8n instance
153
+ 2. Go to **Settings** > **n8n API**
154
+ 3. Click **Create an API key**
155
+ 4. Provide a label (e.g., "WebAgent Integration")
156
+ 5. Set expiration time (or leave blank for no expiration)
157
+ 6. Copy the generated API key
158
+
159
+ ### Environment Configuration
160
+
161
+ You can set a default n8n URL via environment variable:
162
+
163
+ ```bash
164
+ export N8N_BASE_URL=https://your-n8n-instance.com
165
+ ```
166
+
167
+ ## Usage Examples
168
+
169
+ ### Simple Workflow Execution
170
+ ```python
171
+ # Setup (one-time)
172
+ await agent.call_tool("n8n_setup", api_key="your_api_key")
173
+
174
+ # Execute a simple workflow
175
+ result = await agent.call_tool("n8n_execute", workflow_id="welcome_email")
176
+ print(result) # ✅ Workflow executed successfully!
177
+ ```
178
+
179
+ ### Workflow with Input Data
180
+ ```python
181
+ # Execute workflow with custom data
182
+ customer_data = {
183
+ "name": "John Doe",
184
+ "email": "john@example.com",
185
+ "subscription": "premium"
186
+ }
187
+
188
+ result = await agent.call_tool("n8n_execute",
189
+ workflow_id="customer_welcome",
190
+ data=customer_data
191
+ )
192
+
193
+ # Monitor execution
194
+ execution_id = "exec_123" # Extract from result
195
+ status = await agent.call_tool("n8n_status", execution_id=execution_id)
196
+ print(status)
197
+ ```
198
+
199
+ ### Discover and Execute Workflows
200
+ ```python
201
+ # First, see what workflows are available
202
+ workflows = await agent.call_tool("n8n_list_workflows")
203
+ print(workflows)
204
+
205
+ # Execute a specific workflow
206
+ result = await agent.call_tool("n8n_execute",
207
+ workflow_id="data_processing",
208
+ data={"source": "api", "format": "json"}
209
+ )
210
+ ```
211
+
212
+ ## Error Handling
213
+
214
+ The skill provides clear error messages for common scenarios:
215
+
216
+ - **❌ Authentication required** - User not authenticated
217
+ - **❌ API key is required** - Empty or missing API key
218
+ - **❌ Invalid API key** - API key rejected by n8n
219
+ - **❌ n8n instance not found** - Base URL unreachable
220
+ - **❌ Workflow not found** - Invalid workflow ID
221
+ - **❌ Execution not found** - Invalid execution ID
222
+
223
+ ## Security
224
+
225
+ - **API keys** are stored securely using the KV skill with per-user namespacing
226
+ - **User isolation** ensures each user can only access their own credentials
227
+ - **Authentication required** for all operations
228
+ - **Automatic validation** of API keys during setup
229
+ - **Memory fallback** available if KV skill unavailable
230
+
231
+ ## Architecture
232
+
233
+ ```
234
+ WebAgent
235
+ ├── Auth Skill (user context)
236
+ ├── KV Skill (secure storage)
237
+ └── n8n Skill
238
+ ├── Credential Management
239
+ ├── API Communication
240
+ └── Workflow Operations
241
+ ```
242
+
243
+ ## Dependencies
244
+
245
+ - `auth` skill - For user authentication and context
246
+ - `kv` skill - For secure credential storage
247
+ - `httpx` - For HTTP API communication
248
+
249
+ ## Troubleshooting
250
+
251
+ ### "Authentication required"
252
+ Ensure your agent has proper authentication configured.
253
+
254
+ ### "Invalid API key"
255
+ - Verify the API key is correct
256
+ - Check if the key has expired
257
+ - Ensure the key has necessary permissions
258
+
259
+ ### "n8n instance not found"
260
+ - Verify the base URL is correct
261
+ - Check network connectivity
262
+ - Ensure n8n instance is running
263
+
264
+ ### "Workflow not found"
265
+ - Use `n8n_list_workflows()` to see available workflows
266
+ - Check if the workflow ID is correct
267
+ - Verify the workflow is saved in n8n
268
+
269
+ ## Limitations
270
+
271
+ - Supports n8n API v1 endpoints
272
+ - Requires n8n instance with API access enabled
273
+ - Limited to workflow execution and status monitoring
274
+ - Does not support workflow creation or modification
275
+
276
+ ## Contributing
277
+
278
+ To extend this skill:
279
+
280
+ 1. Add new tools following the existing pattern
281
+ 2. Update tests in `tests/test_n8n_skill.py`
282
+ 3. Update this documentation
283
+ 4. Ensure proper error handling and user feedback
284
+
285
+ ## License
286
+
287
+ Part of the WebAgents framework. See main project license.
@@ -0,0 +1,3 @@
1
+ from .skill import N8nSkill
2
+
3
+ __all__ = ['N8nSkill']
@@ -0,0 +1,341 @@
1
+ """
2
+ Minimalistic n8n Skill for WebAgents
3
+
4
+ This skill allows users to:
5
+ - Set up n8n API key securely (via auth/kv skills)
6
+ - Execute n8n workflows
7
+ - List available workflows
8
+ - Get workflow execution status
9
+
10
+ Uses auth skill for user context and kv skill for secure API key storage.
11
+ """
12
+
13
+ import os
14
+ import httpx
15
+ import json
16
+ from typing import Dict, Any, Optional, List
17
+ from datetime import datetime
18
+
19
+ from webagents.agents.skills.base import Skill
20
+ from webagents.agents.tools.decorators import tool, prompt
21
+ from webagents.server.context.context_vars import get_context
22
+
23
+
24
+ class N8nSkill(Skill):
25
+ """Minimalistic n8n skill for workflow automation"""
26
+
27
+ def __init__(self):
28
+ super().__init__()
29
+ self.default_n8n_url = os.getenv('N8N_BASE_URL', 'http://localhost:5678')
30
+
31
+ def get_dependencies(self) -> List[str]:
32
+ """Skill dependencies"""
33
+ return ['auth', 'kv']
34
+
35
+ @prompt(priority=40, scope=["owner", "all"])
36
+ def n8n_prompt(self) -> str:
37
+ """Prompt describing n8n capabilities"""
38
+ return """
39
+ Minimalistic n8n integration for workflow automation. Available tools:
40
+
41
+ • n8n_setup(api_key, base_url) - Set up n8n API credentials securely
42
+ • n8n_execute(workflow_id, data) - Execute a specific workflow with optional input data
43
+ • n8n_list_workflows() - List all available workflows in your n8n instance
44
+ • n8n_status(execution_id) - Check the status of a workflow execution
45
+
46
+ Features:
47
+ - Secure API key storage via KV skill
48
+ - Per-user credential isolation via Auth skill
49
+ - Execute workflows with custom input data
50
+ - Monitor workflow execution status
51
+ - List and discover available workflows
52
+
53
+ Setup: First run n8n_setup() with your n8n API key and instance URL.
54
+ """
55
+
56
+ # Helper methods for auth and kv skills
57
+ async def _get_auth_skill(self):
58
+ """Get auth skill for user context"""
59
+ return self.agent.skills.get('auth')
60
+
61
+ async def _get_kv_skill(self):
62
+ """Get KV skill for secure storage"""
63
+ return self.agent.skills.get('kv')
64
+
65
+ async def _get_authenticated_user_id(self) -> Optional[str]:
66
+ """Get authenticated user ID from context"""
67
+ try:
68
+ context = get_context()
69
+ if context and context.auth and context.auth.authenticated:
70
+ return context.auth.user_id
71
+ return None
72
+ except Exception as e:
73
+ self.logger.error(f"Failed to get user context: {e}")
74
+ return None
75
+
76
+ async def _save_n8n_credentials(self, user_id: str, api_key: str, base_url: str) -> bool:
77
+ """Save n8n credentials securely using KV skill"""
78
+ try:
79
+ kv_skill = await self._get_kv_skill()
80
+ if kv_skill:
81
+ credentials = {
82
+ 'api_key': api_key,
83
+ 'base_url': base_url,
84
+ 'created_at': datetime.now().isoformat()
85
+ }
86
+ await kv_skill.kv_set(
87
+ key='credentials',
88
+ value=json.dumps(credentials),
89
+ namespace=f'n8n:{user_id}'
90
+ )
91
+ return True
92
+ else:
93
+ # Fallback to in-memory storage
94
+ if not hasattr(self.agent, '_n8n_credentials'):
95
+ self.agent._n8n_credentials = {}
96
+ self.agent._n8n_credentials[user_id] = {
97
+ 'api_key': api_key,
98
+ 'base_url': base_url
99
+ }
100
+ return True
101
+ except Exception as e:
102
+ self.logger.error(f"Failed to save n8n credentials: {e}")
103
+ return False
104
+
105
+ async def _load_n8n_credentials(self, user_id: str) -> Optional[Dict[str, str]]:
106
+ """Load n8n credentials from KV skill"""
107
+ try:
108
+ kv_skill = await self._get_kv_skill()
109
+ if kv_skill:
110
+ credentials_json = await kv_skill.kv_get(
111
+ key='credentials',
112
+ namespace=f'n8n:{user_id}'
113
+ )
114
+ if credentials_json:
115
+ return json.loads(credentials_json)
116
+ else:
117
+ # Fallback to in-memory storage
118
+ if hasattr(self.agent, '_n8n_credentials'):
119
+ return self.agent._n8n_credentials.get(user_id)
120
+ return None
121
+ except Exception as e:
122
+ self.logger.error(f"Failed to load n8n credentials: {e}")
123
+ return None
124
+
125
+ async def _make_n8n_request(self, method: str, endpoint: str, data: Optional[Dict] = None, user_id: str = None) -> Dict[str, Any]:
126
+ """Make authenticated request to n8n API"""
127
+ if not user_id:
128
+ user_id = await self._get_authenticated_user_id()
129
+ if not user_id:
130
+ raise Exception("Authentication required")
131
+
132
+ credentials = await self._load_n8n_credentials(user_id)
133
+ if not credentials:
134
+ raise Exception("n8n credentials not found. Please run n8n_setup() first.")
135
+
136
+ api_key = credentials['api_key']
137
+ base_url = credentials['base_url'].rstrip('/')
138
+
139
+ headers = {
140
+ 'X-N8N-API-KEY': api_key,
141
+ 'Content-Type': 'application/json'
142
+ }
143
+
144
+ url = f"{base_url}/api/v1{endpoint}"
145
+
146
+ async with httpx.AsyncClient() as client:
147
+ if method.upper() == 'GET':
148
+ response = await client.get(url, headers=headers)
149
+ elif method.upper() == 'POST':
150
+ response = await client.post(url, headers=headers, json=data)
151
+ elif method.upper() == 'PUT':
152
+ response = await client.put(url, headers=headers, json=data)
153
+ elif method.upper() == 'DELETE':
154
+ response = await client.delete(url, headers=headers)
155
+ else:
156
+ raise Exception(f"Unsupported HTTP method: {method}")
157
+
158
+ response.raise_for_status()
159
+ return response.json() if response.content else {}
160
+
161
+ # Public tools
162
+ @tool(description="Set up n8n API credentials securely. Get your API key from n8n Settings > n8n API.", scope="owner")
163
+ async def n8n_setup(self, api_key: str, base_url: str = None) -> str:
164
+ """Set up n8n API credentials for secure access"""
165
+ user_id = await self._get_authenticated_user_id()
166
+ if not user_id:
167
+ return "❌ Authentication required"
168
+
169
+ if not api_key or not api_key.strip():
170
+ return "❌ API key is required. Generate one from n8n Settings > n8n API."
171
+
172
+ # Use provided base_url or default
173
+ n8n_url = base_url or self.default_n8n_url
174
+ if not n8n_url.startswith(('http://', 'https://')):
175
+ n8n_url = f"https://{n8n_url}"
176
+
177
+ try:
178
+ # Test the API key by making a simple request
179
+ await self._make_n8n_request('GET', '/workflows', user_id=user_id)
180
+
181
+ # If test succeeds, save credentials
182
+ success = await self._save_n8n_credentials(user_id, api_key.strip(), n8n_url)
183
+
184
+ if success:
185
+ return f"✅ n8n credentials saved successfully!\n🌐 Base URL: {n8n_url}\n🔑 API key configured"
186
+ else:
187
+ return "❌ Failed to save credentials"
188
+
189
+ except httpx.HTTPStatusError as e:
190
+ if e.response.status_code == 401:
191
+ return "❌ Invalid API key. Please check your n8n API key."
192
+ elif e.response.status_code == 404:
193
+ return f"❌ n8n instance not found at {n8n_url}. Please check the URL."
194
+ else:
195
+ return f"❌ API test failed: HTTP {e.response.status_code}"
196
+ except Exception as e:
197
+ return f"❌ Setup failed: {str(e)}"
198
+
199
+ @tool(description="Execute an n8n workflow with optional input data")
200
+ async def n8n_execute(self, workflow_id: str, data: Dict[str, Any] = None) -> str:
201
+ """Execute an n8n workflow with optional input data"""
202
+ user_id = await self._get_authenticated_user_id()
203
+ if not user_id:
204
+ return "❌ Authentication required"
205
+
206
+ if not workflow_id or not workflow_id.strip():
207
+ return "❌ Workflow ID is required"
208
+
209
+ try:
210
+ # Prepare execution data
211
+ execution_data = {}
212
+ if data:
213
+ execution_data = data
214
+
215
+ # Execute the workflow
216
+ response = await self._make_n8n_request(
217
+ 'POST',
218
+ f'/workflows/{workflow_id.strip()}/execute',
219
+ execution_data,
220
+ user_id
221
+ )
222
+
223
+ execution_id = response.get('id', 'unknown')
224
+ status = response.get('status', 'unknown')
225
+
226
+ return f"✅ Workflow executed successfully!\n📋 Execution ID: {execution_id}\n📊 Status: {status}"
227
+
228
+ except httpx.HTTPStatusError as e:
229
+ if e.response.status_code == 401:
230
+ return "❌ Authentication failed. Please run n8n_setup() again."
231
+ elif e.response.status_code == 404:
232
+ return f"❌ Workflow '{workflow_id}' not found"
233
+ else:
234
+ return f"❌ Execution failed: HTTP {e.response.status_code}"
235
+ except Exception as e:
236
+ return f"❌ Error executing workflow: {str(e)}"
237
+
238
+ @tool(description="List all available workflows in your n8n instance")
239
+ async def n8n_list_workflows(self) -> str:
240
+ """List all available workflows"""
241
+ user_id = await self._get_authenticated_user_id()
242
+ if not user_id:
243
+ return "❌ Authentication required"
244
+
245
+ try:
246
+ response = await self._make_n8n_request('GET', '/workflows', user_id=user_id)
247
+
248
+ workflows = response.get('data', [])
249
+
250
+ if not workflows:
251
+ return "📭 No workflows found in your n8n instance"
252
+
253
+ result = ["📋 Available n8n Workflows:\n"]
254
+
255
+ for workflow in workflows:
256
+ workflow_id = workflow.get('id', 'unknown')
257
+ name = workflow.get('name', 'Unnamed')
258
+ active = workflow.get('active', False)
259
+ status_icon = "🟢" if active else "🔴"
260
+
261
+ result.append(f"{status_icon} **{name}** (ID: {workflow_id})")
262
+
263
+ # Add tags if available
264
+ tags = workflow.get('tags', [])
265
+ if tags:
266
+ tag_names = [tag.get('name', 'Unknown') for tag in tags]
267
+ result.append(f" 🏷️ Tags: {', '.join(tag_names)}")
268
+
269
+ result.append("") # Empty line for spacing
270
+
271
+ result.append("💡 Use n8n_execute(workflow_id, data) to run a workflow")
272
+
273
+ return "\n".join(result)
274
+
275
+ except httpx.HTTPStatusError as e:
276
+ if e.response.status_code == 401:
277
+ return "❌ Authentication failed. Please run n8n_setup() again."
278
+ else:
279
+ return f"❌ Failed to list workflows: HTTP {e.response.status_code}"
280
+ except Exception as e:
281
+ return f"❌ Error listing workflows: {str(e)}"
282
+
283
+ @tool(description="Check the status of a workflow execution")
284
+ async def n8n_status(self, execution_id: str) -> str:
285
+ """Check the status of a workflow execution"""
286
+ user_id = await self._get_authenticated_user_id()
287
+ if not user_id:
288
+ return "❌ Authentication required"
289
+
290
+ if not execution_id or not execution_id.strip():
291
+ return "❌ Execution ID is required"
292
+
293
+ try:
294
+ response = await self._make_n8n_request(
295
+ 'GET',
296
+ f'/executions/{execution_id.strip()}',
297
+ user_id=user_id
298
+ )
299
+
300
+ status = response.get('status', 'unknown')
301
+ workflow_id = response.get('workflowId', 'unknown')
302
+ start_time = response.get('startedAt', 'unknown')
303
+ end_time = response.get('stoppedAt', 'running')
304
+
305
+ # Status icons
306
+ status_icons = {
307
+ 'success': '✅',
308
+ 'error': '❌',
309
+ 'running': '🔄',
310
+ 'waiting': '⏳',
311
+ 'canceled': '🚫'
312
+ }
313
+
314
+ status_icon = status_icons.get(status.lower(), '❓')
315
+
316
+ result = [
317
+ f"📊 Execution Status Report",
318
+ f"🆔 Execution ID: {execution_id}",
319
+ f"🔧 Workflow ID: {workflow_id}",
320
+ f"{status_icon} Status: {status}",
321
+ f"🕐 Started: {start_time}",
322
+ f"🕑 Finished: {end_time}"
323
+ ]
324
+
325
+ # Add error details if execution failed
326
+ if status.lower() == 'error' and 'data' in response:
327
+ error_data = response.get('data', {})
328
+ if 'resultData' in error_data:
329
+ result.append(f"❌ Error details available in execution data")
330
+
331
+ return "\n".join(result)
332
+
333
+ except httpx.HTTPStatusError as e:
334
+ if e.response.status_code == 401:
335
+ return "❌ Authentication failed. Please run n8n_setup() again."
336
+ elif e.response.status_code == 404:
337
+ return f"❌ Execution '{execution_id}' not found"
338
+ else:
339
+ return f"❌ Status check failed: HTTP {e.response.status_code}"
340
+ except Exception as e:
341
+ return f"❌ Error checking status: {str(e)}"