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. 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); } // 3. Fetch project ID dynamically console.log("Fetching workflows list to resolve project ID..."); 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(); 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}`); } const workflowsToBootstrap = [ 'sales_import_workflow.json', 'settlement_calculation_workflow.json' ]; for (const filename of workflowsToBootstrap) { console.log(`\n--- Bootstrapping workflow: ${filename} ---`); const workflowPath = path.join(__dirname, '../n8n', filename); if (!fs.existsSync(workflowPath)) { console.error(`Error: ${workflowPath} not found.`); continue; } const workflowJson = JSON.parse(fs.readFileSync(workflowPath, 'utf8')); const targetName = workflowJson.name; // Inject resolved credentials dynamically into the workflow JSON for (const node of workflowJson.nodes) { if (node.type === '@n8n/n8n-nodes-langchain.chainLlm') { // Find deepseek node and gemini node to map credentials } 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}" in ${filename}`); } 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}" in ${filename}`); } } const existingWorkflow = listData.data.find(w => w.name === targetName); let workflowId; if (existingWorkflow) { workflowId = existingWorkflow.id; console.log(`Found existing workflow "${targetName}". 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 for "${targetName}". 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}`); } // Activate the workflow console.log(`Activating workflow "${targetName}" (${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 "${targetName}" activated and ready!`); } } console.log("\n=== N8N WORKFLOW BOOTSTRAP COMPLETED SUCCESSFULLY ==="); } bootstrap().catch(err => { console.error("Fatal bootstrap error:", err); process.exit(1); });