const fs = require('fs'); const path = require('path'); require('dotenv').config(); async function bootstrap() { console.log("=== STARTING N8N WORKFLOW BOOTSTRAP / KICKSTART ==="); // 1. Fetch n8n API configuration from .agents/mcp_config.json const mcpConfigPath = path.join(__dirname, '../.agents/mcp_config.json'); if (!fs.existsSync(mcpConfigPath)) { console.error("Error: .agents/mcp_config.json not found."); process.exit(1); } const mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8')); const n8nEnv = mcpConfig.mcpServers.n8n.env; const n8nUrl = n8nEnv.N8N_API_URL || "https://n8n.gaboggamer.online"; const n8nApiKey = n8nEnv.N8N_API_KEY; if (!n8nApiKey) { console.error("Error: N8N_API_KEY is missing in mcp_config.json."); process.exit(1); } // 2. Load the official local workflow JSON const workflowPath = path.join(__dirname, '../n8n/sales_import_workflow.json'); if (!fs.existsSync(workflowPath)) { console.error("Error: n8n/sales_import_workflow.json not found."); process.exit(1); } const workflowJson = JSON.parse(fs.readFileSync(workflowPath, 'utf8')); const targetName = workflowJson.name || "Sales Data Import & Validation"; // 2.5. Fetch credentials dynamically to resolve AI accounts console.log("Fetching credentials from n8n to resolve AI accounts..."); let deepSeekCred = null; let geminiCred = null; try { const credsRes = await fetch(`${n8nUrl}/api/v1/credentials`, { method: 'GET', headers: { 'X-N8N-API-KEY': n8nApiKey } }); if (credsRes.ok) { const credsData = await credsRes.json(); const credentials = Array.isArray(credsData.data) ? credsData.data : (credsData.data.credentials || []); const deepSeekCreds = credentials.filter(c => c.type === 'deepSeekApi'); if (deepSeekCreds.length > 0) { deepSeekCred = deepSeekCreds.find(c => c.name.toLowerCase().includes('deepseek')) || deepSeekCreds[0]; } const geminiCreds = credentials.filter(c => c.type === 'googlePalmApi'); if (geminiCreds.length > 0) { geminiCred = geminiCreds.find(c => c.name.toLowerCase().includes('gemini') || c.name.toLowerCase().includes('google')) || geminiCreds[0]; } if (deepSeekCred) { console.log(`Resolved DeepSeek credential: "${deepSeekCred.name}" (ID: ${deepSeekCred.id})`); } if (geminiCred) { console.log(`Resolved Gemini credential: "${geminiCred.name}" (ID: ${geminiCred.id})`); } } else { console.warn(`Warning: Failed to fetch credentials. Status: ${credsRes.status}`); } } catch (err) { console.warn("Warning: Could not fetch credentials dynamically:", err.message); } // Inject resolved credentials dynamically into the workflow JSON for (const node of workflowJson.nodes) { if (node.type === '@n8n/n8n-nodes-langchain.lmChatDeepSeek' && deepSeekCred) { node.credentials = { deepSeekApi: { id: deepSeekCred.id, name: deepSeekCred.name } }; console.log(`Injected DeepSeek credential into node "${node.name}"`); } if (node.type === '@n8n/n8n-nodes-langchain.lmChatGoogleGemini' && geminiCred) { node.credentials = { googlePalmApi: { id: geminiCred.id, name: geminiCred.name } }; console.log(`Injected Gemini credential into node "${node.name}"`); } } // 3. Check if workflow already exists in n8n console.log(`Searching for existing workflow named "${targetName}"...`); const listRes = await fetch(`${n8nUrl}/api/v1/workflows`, { method: 'GET', headers: { 'X-N8N-API-KEY': n8nApiKey } }); if (!listRes.ok) { console.error(`Failed to list workflows from n8n. Status: ${listRes.status}`); process.exit(1); } const listData = await listRes.json(); const existingWorkflow = listData.data.find(w => w.name === targetName); // Retrieve project ID from existing workflows if available let projectId; const firstWf = listData.data.find(w => w.shared && w.shared.length > 0); if (firstWf) { projectId = firstWf.shared[0].projectId; console.log(`Resolved project ID dynamically: ${projectId}`); } let workflowId; if (existingWorkflow) { workflowId = existingWorkflow.id; console.log(`Found existing workflow. ID: ${workflowId}. Updating...`); const updateRes = await fetch(`${n8nUrl}/api/v1/workflows/${workflowId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-N8N-API-KEY': n8nApiKey }, body: JSON.stringify({ name: targetName, nodes: workflowJson.nodes, connections: workflowJson.connections, settings: workflowJson.settings || {} }) }); if (!updateRes.ok) { console.error(`Failed to update workflow. Status: ${updateRes.status}`); process.exit(1); } console.log(`Workflow updated successfully!`); } else { console.log("No existing workflow found. Creating new workflow..."); const createRes = await fetch(`${n8nUrl}/api/v1/workflows`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-N8N-API-KEY': n8nApiKey }, body: JSON.stringify({ name: targetName, nodes: workflowJson.nodes, connections: workflowJson.connections, settings: workflowJson.settings || {}, projectId }) }); if (!createRes.ok) { console.error(`Failed to create workflow. Status: ${createRes.status}`); process.exit(1); } const createData = await createRes.json(); workflowId = createData.id; console.log(`Workflow created successfully! ID: ${workflowId}`); } // 4. Activate the workflow console.log(`Activating workflow ${workflowId}...`); const activateRes = await fetch(`${n8nUrl}/api/v1/workflows/${workflowId}/activate`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-N8N-API-KEY': n8nApiKey }, body: JSON.stringify({}) }); if (!activateRes.ok) { console.error(`Warning: Failed to activate workflow. Status: ${activateRes.status}`); } else { console.log(`Workflow activated and ready!`); } console.log("=== N8N WORKFLOW BOOTSTRAP COMPLETED SUCCESSFULLY ==="); } bootstrap().catch(err => { console.error("Fatal bootstrap error:", err); process.exit(1); });