1+ """
2+ n8n CLI Commands
3+
4+ CLI commands for n8n workflow integration.
5+ """
6+
7+ import typer
8+ import logging
9+ from pathlib import Path
10+ from typing import Optional
11+
12+ logger = logging .getLogger (__name__ )
13+
14+ app = typer .Typer (
15+ name = "n8n" ,
16+ help = "n8n visual workflow editor integration commands" ,
17+ no_args_is_help = True ,
18+ rich_markup_mode = "rich"
19+ )
20+
21+ @app .command ()
22+ def export (
23+ yaml_path : Path = typer .Argument (..., help = "Path to YAML workflow file" ),
24+ output : Optional [Path ] = typer .Option (None , "--output" , "-o" , help = "Output JSON file path" ),
25+ format : str = typer .Option ("n8n" , "--format" , help = "Export format (currently only n8n supported)" )
26+ ):
27+ """Export PraisonAI YAML workflow to n8n JSON format.
28+
29+ Example:
30+ praisonai n8n export my-workflow.yaml --output workflow.json
31+ """
32+ if format != 'n8n' :
33+ typer .echo (f"Error: Unsupported format '{ format } '. Only 'n8n' is supported." , err = True )
34+ raise typer .Exit (1 )
35+
36+ try :
37+ from praisonai .n8n import YAMLToN8nConverter
38+ import yaml as yaml_lib
39+ import json
40+
41+ # Load YAML workflow
42+ with open (yaml_path , 'r' ) as f :
43+ yaml_workflow = yaml_lib .safe_load (f )
44+
45+ # Convert to n8n format
46+ converter = YAMLToN8nConverter ()
47+ n8n_json = converter .convert (yaml_workflow )
48+
49+ # Determine output path
50+ if output is None :
51+ output = yaml_path .with_suffix ('.json' )
52+
53+ # Write JSON file
54+ with open (output , 'w' ) as f :
55+ json .dump (n8n_json , f , indent = 2 )
56+
57+ typer .echo (f"✅ Exported workflow to: { output } " )
58+ typer .echo (f"💡 Import this file into n8n or use 'praisonai n8n preview { yaml_path } ' to open directly" )
59+
60+ except ImportError :
61+ typer .echo ("Error: n8n dependencies not installed. Run: pip install 'praisonai[n8n]'" , err = True )
62+ raise typer .Exit (1 )
63+ except Exception as e :
64+ typer .echo (f"Error: { e } " , err = True )
65+ raise typer .Exit (1 )
66+
67+ @app .command (name = "import" )
68+ def import_workflow (
69+ json_path : Path = typer .Argument (..., help = "Path to n8n JSON workflow file" ),
70+ output : Optional [Path ] = typer .Option (None , "--output" , "-o" , help = "Output YAML file path" ),
71+ format : str = typer .Option ("n8n" , "--format" , help = "Import format (currently only n8n supported)" )
72+ ):
73+ """Import n8n JSON workflow to PraisonAI YAML format.
74+
75+ Example:
76+ praisonai n8n import workflow.json --output my-workflow.yaml
77+ """
78+ if format != 'n8n' :
79+ typer .echo (f"Error: Unsupported format '{ format } '. Only 'n8n' is supported." , err = True )
80+ raise typer .Exit (1 )
81+
82+ try :
83+ from praisonai .n8n import N8nToYAMLConverter
84+ import yaml as yaml_lib
85+ import json
86+
87+ # Load n8n JSON workflow
88+ with open (json_path , 'r' ) as f :
89+ n8n_workflow = json .load (f )
90+
91+ # Convert to YAML format
92+ converter = N8nToYAMLConverter ()
93+ yaml_workflow = converter .convert (n8n_workflow )
94+
95+ # Determine output path
96+ if output is None :
97+ output = json_path .with_suffix ('.yaml' )
98+
99+ # Write YAML file
100+ with open (output , 'w' ) as f :
101+ yaml_lib .dump (yaml_workflow , f , default_flow_style = False , sort_keys = False )
102+
103+ typer .echo (f"✅ Imported workflow to: { output } " )
104+ typer .echo (f"💡 Run 'praisonai workflow run { output } ' to execute the workflow" )
105+
106+ except ImportError :
107+ typer .echo ("Error: n8n dependencies not installed. Run: pip install 'praisonai[n8n]'" , err = True )
108+ raise typer .Exit (1 )
109+ except Exception as e :
110+ typer .echo (f"Error: { e } " , err = True )
111+ raise typer .Exit (1 )
112+
113+ @app .command ()
114+ def preview (
115+ yaml_path : Path = typer .Argument (..., help = "Path to YAML workflow file" ),
116+ n8n_url : str = typer .Option ("http://localhost:5678" , "--n8n-url" , help = "n8n instance URL" ),
117+ api_key : Optional [str ] = typer .Option (None , "--api-key" , help = "n8n API key (or set N8N_API_KEY env var)" ),
118+ no_open : bool = typer .Option (False , "--no-open" , help = "Do not automatically open browser" )
119+ ):
120+ """Preview PraisonAI workflow in n8n visual editor.
121+
122+ This command converts your YAML workflow to n8n format and opens it
123+ in the n8n visual editor for preview and editing.
124+
125+ Example:
126+ praisonai n8n preview my-workflow.yaml
127+ praisonai n8n preview my-workflow.yaml --n8n-url http://n8n.example.com
128+ """
129+ try :
130+ from praisonai .n8n import preview_workflow
131+
132+ # Preview workflow in n8n
133+ editor_url = preview_workflow (
134+ yaml_path = str (yaml_path ),
135+ n8n_url = n8n_url ,
136+ api_key = api_key ,
137+ auto_open = not no_open
138+ )
139+
140+ typer .echo (f"✅ Workflow created in n8n" )
141+ typer .echo (f"🌐 Editor URL: { editor_url } " )
142+
143+ if no_open :
144+ typer .echo (f"💡 Open the URL above to view/edit your workflow visually" )
145+ else :
146+ typer .echo (f"💡 Browser should open automatically. If not, visit the URL above" )
147+
148+ except ImportError :
149+ typer .echo ("Error: n8n dependencies not installed. Run: pip install 'praisonai[n8n]'" , err = True )
150+ raise typer .Exit (1 )
151+ except FileNotFoundError as e :
152+ typer .echo (f"Error: { e } " , err = True )
153+ raise typer .Exit (1 )
154+ except ConnectionError as e :
155+ typer .echo (f"Connection Error: { e } " , err = True )
156+ typer .echo ("💡 Make sure n8n is running. Start with: npx n8n start" )
157+ raise typer .Exit (1 )
158+ except Exception as e :
159+ typer .echo (f"Error: { e } " , err = True )
160+ raise typer .Exit (1 )
161+
162+ @app .command ()
163+ def pull (
164+ workflow_id : str = typer .Argument (..., help = "n8n workflow ID" ),
165+ output_path : Path = typer .Argument (..., help = "Path where to save YAML file" ),
166+ n8n_url : str = typer .Option ("http://localhost:5678" , "--n8n-url" , help = "n8n instance URL" ),
167+ api_key : Optional [str ] = typer .Option (None , "--api-key" , help = "n8n API key (or set N8N_API_KEY env var)" )
168+ ):
169+ """Pull workflow from n8n and convert to YAML.
170+
171+ Example:
172+ praisonai n8n pull abc123 my-workflow.yaml
173+ """
174+ try :
175+ from praisonai .n8n import export_from_n8n
176+
177+ # Export from n8n to YAML
178+ export_from_n8n (
179+ workflow_id = workflow_id ,
180+ output_path = str (output_path ),
181+ n8n_url = n8n_url ,
182+ api_key = api_key
183+ )
184+
185+ typer .echo (f"✅ Pulled workflow { workflow_id } to: { output_path } " )
186+ typer .echo (f"💡 Run 'praisonai workflow run { output_path } ' to execute the workflow" )
187+
188+ except ImportError :
189+ typer .echo ("Error: n8n dependencies not installed. Run: pip install 'praisonai[n8n]'" , err = True )
190+ raise typer .Exit (1 )
191+ except ConnectionError as e :
192+ typer .echo (f"Connection Error: { e } " , err = True )
193+ typer .echo ("💡 Make sure n8n is running and accessible" )
194+ raise typer .Exit (1 )
195+ except Exception as e :
196+ typer .echo (f"Error: { e } " , err = True )
197+ raise typer .Exit (1 )
198+
199+ @app .command ()
200+ def push (
201+ yaml_path : Path = typer .Argument (..., help = "Path to YAML workflow file" ),
202+ workflow_id : str = typer .Argument (..., help = "Existing n8n workflow ID to update" ),
203+ n8n_url : str = typer .Option ("http://localhost:5678" , "--n8n-url" , help = "n8n instance URL" ),
204+ api_key : Optional [str ] = typer .Option (None , "--api-key" , help = "n8n API key (or set N8N_API_KEY env var)" )
205+ ):
206+ """Push YAML workflow updates to existing n8n workflow.
207+
208+ Example:
209+ praisonai n8n push my-workflow.yaml abc123
210+ """
211+ try :
212+ from praisonai .n8n import sync_workflow
213+
214+ # Sync workflow with n8n
215+ editor_url = sync_workflow (
216+ yaml_path = str (yaml_path ),
217+ workflow_id = workflow_id ,
218+ n8n_url = n8n_url ,
219+ api_key = api_key
220+ )
221+
222+ typer .echo (f"✅ Synced workflow { workflow_id } " )
223+ typer .echo (f"🌐 Editor URL: { editor_url } " )
224+
225+ except ImportError :
226+ typer .echo ("Error: n8n dependencies not installed. Run: pip install 'praisonai[n8n]'" , err = True )
227+ raise typer .Exit (1 )
228+ except FileNotFoundError as e :
229+ typer .echo (f"Error: { e } " , err = True )
230+ raise typer .Exit (1 )
231+ except ConnectionError as e :
232+ typer .echo (f"Connection Error: { e } " , err = True )
233+ typer .echo ("💡 Make sure n8n is running and accessible" )
234+ raise typer .Exit (1 )
235+ except Exception as e :
236+ typer .echo (f"Error: { e } " , err = True )
237+ raise typer .Exit (1 )
238+
239+ @app .command (name = "test" )
240+ def test_connection (
241+ n8n_url : str = typer .Option ("http://localhost:5678" , "--n8n-url" , help = "n8n instance URL" ),
242+ api_key : Optional [str ] = typer .Option (None , "--api-key" , help = "n8n API key (or set N8N_API_KEY env var)" )
243+ ):
244+ """Test connection to n8n instance.
245+
246+ Example:
247+ praisonai n8n test
248+ praisonai n8n test --n8n-url http://n8n.example.com
249+ """
250+ try :
251+ from praisonai .n8n import N8nClient
252+
253+ client = N8nClient (base_url = n8n_url , api_key = api_key )
254+
255+ if client .test_connection ():
256+ typer .echo (f"✅ Connected to n8n at { n8n_url } " )
257+
258+ # Try to list workflows to test API access
259+ try :
260+ workflows = client .list_workflows ()
261+ typer .echo (f"📊 Found { len (workflows )} workflows" )
262+ except Exception as e :
263+ typer .echo (f"⚠️ Connection successful but API access failed: { e } " )
264+ typer .echo ("💡 Check your API key or n8n permissions" )
265+ else :
266+ typer .echo (f"❌ Cannot connect to n8n at { n8n_url } " )
267+ typer .echo ("💡 Make sure n8n is running. Start with: npx n8n start" )
268+ raise typer .Exit (1 )
269+
270+ client .close ()
271+
272+ except ImportError :
273+ typer .echo ("Error: n8n dependencies not installed. Run: pip install 'praisonai[n8n]'" , err = True )
274+ raise typer .Exit (1 )
275+ except Exception as e :
276+ typer .echo (f"Error: { e } " , err = True )
277+ raise typer .Exit (1 )
278+
279+ @app .command (name = "list" )
280+ def list_workflows (
281+ n8n_url : str = typer .Option ("http://localhost:5678" , "--n8n-url" , help = "n8n instance URL" ),
282+ api_key : Optional [str ] = typer .Option (None , "--api-key" , help = "n8n API key (or set N8N_API_KEY env var)" )
283+ ):
284+ """List workflows in n8n instance.
285+
286+ Example:
287+ praisonai n8n list
288+ """
289+ try :
290+ from praisonai .n8n import N8nClient
291+
292+ client = N8nClient (base_url = n8n_url , api_key = api_key )
293+
294+ workflows = client .list_workflows ()
295+
296+ if not workflows :
297+ typer .echo ("No workflows found in n8n" )
298+ else :
299+ typer .echo (f"Found { len (workflows )} workflows:" )
300+ typer .echo ()
301+
302+ for workflow in workflows :
303+ workflow_id = workflow .get ('id' , 'Unknown' )
304+ name = workflow .get ('name' , 'Untitled' )
305+ active = workflow .get ('active' , False )
306+ status = "✅ Active" if active else "⏸️ Inactive"
307+
308+ typer .echo (f" { workflow_id } : { name } ({ status } )" )
309+
310+ client .close ()
311+
312+ except ImportError :
313+ typer .echo ("Error: n8n dependencies not installed. Run: pip install 'praisonai[n8n]'" , err = True )
314+ raise typer .Exit (1 )
315+ except ConnectionError as e :
316+ typer .echo (f"Connection Error: { e } " , err = True )
317+ typer .echo ("💡 Make sure n8n is running and accessible" )
318+ raise typer .Exit (1 )
319+ except Exception as e :
320+ typer .echo (f"Error: { e } " , err = True )
321+ raise typer .Exit (1 )
0 commit comments