Visual Workflow

    JSON Code

    {
      "meta": {
        "instanceId": "16c94fc796f6fcebddc18ce2c5341407b495e2eab83e587fdaffb79601db3c8c"
      },
      "nodes": [
        {
          "id": "f2bdbd53-f9d0-4242-98d0-1774d7c5caca",
          "name": "Schedule: Daily 09:00",
          "type": "n8n-nodes-base.scheduleTrigger",
          "position": [
            0,
            304
          ],
          "parameters": {
            "rule": {
              "interval": [
                {
                  "triggerAtHour": 9
                }
              ]
            }
          },
          "typeVersion": 1.2
        },
        {
          "id": "0d09bc85-243b-4893-b594-52865168973e",
          "name": "RSS Feed Source",
          "type": "n8n-nodes-base.rssFeedRead",
          "onError": "continueErrorOutput",
          "position": [
            304,
            304
          ],
          "parameters": {
            "url": "https://techcrunch.com/feed/",
            "options": {}
          },
          "retryOnFail": true,
          "typeVersion": 1
        },
        {
          "id": "074e3e54-29f2-439c-a460-72636a83d5cf",
          "name": "NewsAPI Source",
          "type": "n8n-nodes-base.httpRequest",
          "onError": "continueErrorOutput",
          "position": [
            304,
            512
          ],
          "parameters": {
            "url": "https://newsapi.org/v2/everything",
            "options": {},
            "sendQuery": true,
            "queryParameters": {
              "parameters": [
                {
                  "name": "apiKey",
                  "value": "YOUR_NEWSAPI_KEY_HERE"
                },
                {
                  "name": "q",
                  "value": "(Technology OR AI OR Webdev)"
                },
                {
                  "name": "language",
                  "value": "en"
                },
                {
                  "name": "sortBy",
                  "value": "publishedAt"
                },
                {
                  "name": "from",
                  "value": "={{ new Date(Date.now() - 172800000).toISOString().split('T')[0] }}"
                }
              ]
            }
          },
          "typeVersion": 4.2
        },
        {
          "id": "c355e27e-e881-4306-aca7-11d18248d0f4",
          "name": "Filter Recent (48h)",
          "type": "n8n-nodes-base.code",
          "position": [
            560,
            304
          ],
          "parameters": {
            "jsCode": "// ============= 48 HOUR FILTER =============\n// Filters out articles older than 48 hours to ensure freshness\n\nconst now = new Date();\nconst cutoffTime = new Date(now.getTime() - (48 * 60 * 60 * 1000));\n\nconst kept = [];\n\nfor (const item of $input.all()) {\n  const data = item.json;\n  let dateString = data.pubDate || data.isoDate || data.published || data.date || data.updated || data.publishedAt;\n  \n  if (!dateString) continue;\n  \n  try {\n    const articleDate = new Date(dateString);\n    if (articleDate >= cutoffTime) {\n      kept.push(item);\n    }\n  } catch (error) {}\n}\n\nreturn kept;"
          },
          "typeVersion": 2
        },
        {
          "id": "45b90042-b885-4ce6-8ee9-cd7461ded61a",
          "name": "Normalize NewsAPI",
          "type": "n8n-nodes-base.code",
          "position": [
            560,
            512
          ],
          "parameters": {
            "jsCode": "const now = new Date();\nconst scrapedAt = now.toISOString();\n\nconst newsData = $input.item.json;\n\nif (!newsData.articles || !Array.isArray(newsData.articles)) {\n  return [];\n}\n\nconst normalizedArticles = newsData.articles.map(article => {\n  return {\n    title: article.title || '',\n    url: article.url || '',\n    pubDate: article.publishedAt || '',\n    source: article.source?.name || '',\n    summary: article.description || '',\n    scrapedAt: scrapedAt\n  };\n});\n\nreturn normalizedArticles.map(article => ({ json: article }));"
          },
          "typeVersion": 2
        },
        {
          "id": "89886a77-e781-428f-8117-158b2037b41c",
          "name": "Merge Sources",
          "type": "n8n-nodes-base.merge",
          "position": [
            832,
            400
          ],
          "parameters": {},
          "typeVersion": 3.2
        },
        {
          "id": "fe23c2ed-2da2-4879-8b23-483e45410ae0",
          "name": "Remove Duplicates",
          "type": "n8n-nodes-base.code",
          "position": [
            1040,
            400
          ],
          "parameters": {
            "jsCode": "// ============= DUPLICATE REMOVER =============\n// Removes exact duplicates and similar titles using fuzzy matching\n\nfunction similarity(s1, s2) {\n  if (!s1 || !s2) return 0;\n  const a = s1.toLowerCase();\n  const b = s2.toLowerCase();\n  if (a === b) return 100;\n  if (a.includes(b) || b.includes(a)) return 90;\n  return 0;\n}\n\nconst articles = $input.all();\nconst uniqueArticles = [];\nconst seenUrls = new Set();\nconst seenTitles = []; \n\nfor (const item of articles) {\n  const article = item.json;\n  const url = (article.url || article.link || '').trim();\n  const title = (article.title || '').trim();\n\n  if (!title || !url) continue;\n\n  if (seenUrls.has(url)) continue;\n\n  let isDuplicate = false;\n  for (const seenTitle of seenTitles) {\n    if (similarity(title, seenTitle) > 80) {\n      isDuplicate = true;\n      break;\n    }\n  }\n\n  if (!isDuplicate) {\n    seenUrls.add(url);\n    seenTitles.push(title);\n    uniqueArticles.push(item);\n  }\n}\n\nreturn uniqueArticles;"
          },
          "typeVersion": 2
        },
        {
          "id": "9aa015f6-72f2-4cef-b982-12b1de974f17",
          "name": "Get Topic History",
          "type": "n8n-nodes-base.airtable",
          "position": [
            1280,
            624
          ],
          "parameters": {
            "base": {
              "__rl": true,
              "mode": "list",
              "value": "appXXXXXXXXXXXXXX",
              "cachedResultUrl": "https://airtable.com/",
              "cachedResultName": "YOUR_BASE_NAME"
            },
            "table": {
              "__rl": true,
              "mode": "list",
              "value": "tblXXXXXXXXXXXXXX",
              "cachedResultUrl": "https://airtable.com/",
              "cachedResultName": "YOUR_TABLE_NAME"
            },
            "options": {},
            "operation": "search"
          },
          "credentials": {
            "airtableTokenApi": {
              "id": "LsOadaN5biaN8NBc",
              "name": "Airtable Personal Access Token account"
            }
          },
          "typeVersion": 2
        },
        {
          "id": "6d901dd7-64f4-4b77-ac43-70f77f9898ce",
          "name": "Prepare Data for AI",
          "type": "n8n-nodes-base.code",
          "position": [
            1280,
            400
          ],
          "parameters": {
            "jsCode": "// ============= PREPARE CONTEXT FOR AI =============\n\nconst allItems = $input.all();\n\n// 1. Extract History\nconst historyItem = allItems.find(item => item.json.history_string !== undefined);\nconst blogHistory = historyItem ? historyItem.json.history_string : \"No previous articles.\";\n\n// 2. Clean and Format News\nconst newsArticles = allItems\n  .filter(item => {\n    const data = item.json;\n    if (data.history_string !== undefined) return false;\n    if (data.timestamp) return false;\n    if (!data.title) return false;\n    return true;\n  })\n  .map(item => {\n    const data = item.json;\n    \n    let summary = \n      data.contentSnippet || \n      data.summary || \n      data.description || \n      data['content:encodedSnippet'] || \n      \"\";\n\n    if (!summary && (data.content || data['content:encoded'])) {\n      const rawContent = data.content || data['content:encoded'];\n      summary = rawContent.replace(/<[^>]*>?/gm, '').substring(0, 400) + \"...\";\n    }\n\n    const url = data.link || data.url || data.guid;\n\n    return {\n      title: data.title,\n      url: url,\n      summary: summary ? summary.substring(0, 600) : \"No description available\",\n      source: extractSource(url)\n    };\n  });\n\nfunction extractSource(url) {\n  if (!url) return \"Unknown\";\n  try {\n    return new URL(url).hostname.replace('www.', '');\n  } catch (e) { return \"Unknown\"; }\n}\n\nreturn [{\n  json: {\n    news_articles: newsArticles,\n    blog_history: blogHistory,\n    article_count: newsArticles.length\n  }\n}];"
          },
          "typeVersion": 2
        },
        {
          "id": "8eb9971e-0a2f-4ff2-a87f-19f4cceeb26d",
          "name": "Merge News & History",
          "type": "n8n-nodes-base.merge",
          "position": [
            1520,
            480
          ],
          "parameters": {},
          "typeVersion": 3
        },
        {
          "id": "a1eb5361-227f-487b-aff7-00e6ea515141",
          "name": "Format Context",
          "type": "n8n-nodes-base.code",
          "position": [
            1744,
            480
          ],
          "parameters": {
            "jsCode": "// ============= FINAL CONTEXT MERGE =============\n\nconst allItems = $input.all();\n\nlet finalNewsList = [];\nlet historyTitles = [];\n\nfor (const item of allItems) {\n  const data = item.json;\n\n  if (data.news_articles && Array.isArray(data.news_articles)) {\n    finalNewsList = data.news_articles;\n    continue;\n  }\n\n  if (data.id && (data.title || data.Title || data.Titel)) {\n    historyTitles.push(data.title || data.Title || data.Titel);\n    continue;\n  }\n}\n\nconst historyString = historyTitles.length > 0 \n  ? historyTitles.join(\", \") \n  : \"No previous articles.\";\n\nreturn [{\n  json: {\n    news_articles: finalNewsList,\n    blog_history: historyString,\n    stats: `Analyzing ${finalNewsList.length} articles against ${historyTitles.length} history items`\n  }\n}];"
          },
          "typeVersion": 2
        },
        {
          "id": "b9dc1f9c-d9ad-4509-afdb-3f03f4332a19",
          "name": "Agent 1: Topic Scout",
          "type": "@n8n/n8n-nodes-langchain.agent",
          "maxTries": 3,
          "position": [
            2128,
            480
          ],
          "parameters": {
            "text": "=ROLE:\nYou are an expert SEO Strategist and Content Scout.\n\nGOAL:\nAnalyze the provided news articles and select the single best topic for a high-traffic blog post.\n\nINPUTS:\n1. Available News: {{ JSON.stringify($json.news_articles) }}\n2. ALREADY COVERED TOPICS (DO NOT REPEAT): {{ $json.blog_history }}\n\nRULES:\n- Do NOT select a topic that is semantically identical to the \"Already Covered\" list.\n- Focus on high-impact topics (AI, Performance, Tools, Major Updates).\n- Look for \"Pain Points\" or \"Solutions\" (e.g., instead of \"New Astro Version\", choose \"Why your site is slow\").\n\nOUTPUT JSON:\n- title: SEO-optimized working title.\n- description: Brief context of why this matters now.\n- keywords: Array of 3-5 high-volume keywords.\n- source_urls: Array of URLs used for research.",
            "options": {
              "systemMessage": "You are an SEO expert. Think in terms of 'Search Intent' and 'User Problems', not just news headlines."
            },
            "promptType": "define",
            "hasOutputParser": true
          },
          "retryOnFail": true,
          "typeVersion": 1.7
        },
        {
          "id": "9fa8a6c2-3051-4a09-994e-24f1d2a5cafc",
          "name": "Gemini Pro (Topic)",
          "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
          "position": [
            2064,
            704
          ],
          "parameters": {
            "model": "google/gemini-3-pro-preview",
            "options": {}
          },
          "credentials": {
            "openRouterApi": {
              "id": "CErK3KqUFAxPvsBg",
              "name": "AIAGENTKEY"
            }
          },
          "typeVersion": 1
        },
        {
          "id": "b93225e8-5ffe-477b-97bc-f8d3458dce9a",
          "name": "Topic Parser",
          "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
          "position": [
            2192,
            704
          ],
          "parameters": {
            "autoFix": true,
            "schemaType": "manual",
            "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"title\": { \"type\": \"string\" },\n    \"description\": { \"type\": \"string\" },\n    \"keywords\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } },\n    \"source_urls\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } }\n  }\n}"
          },
          "typeVersion": 1.2
        },
        {
          "id": "712a9f57-6d93-4ca9-b16a-b32719e95cfc",
          "name": "Agent 2: Ghost Writer",
          "type": "@n8n/n8n-nodes-langchain.agent",
          "maxTries": 3,
          "position": [
            2592,
            480
          ],
          "parameters": {
            "text": "=WRITE A BLOG POST ABOUT:\nTitle: {{ $json.output.title }}\nContext: {{ $json.output.description }}\nKeywords: {{ $json.output.keywords.join(', ') }}\n\nāš ļø PRIMARY RULE: YOU ARE NOT A CHATBOT. YOU ARE A CMS OPERATOR.\nYOUR GOAL IS TO EXECUTE THE `Create Post` TOOL. ONLY THEN IS THE JOB DONE.\n\nPROCESS (Step-by-Step):\n\n1. RESEARCH (Perplexity Tool) - MANDATORY:\nFind hard facts, stats, and studies.\n- Search for: \"Benchmarks 2025\", \"Stats\", \"Costs of...\"\n\n2. THE \"HOOK\" (Engaging & Witty):\nStart with a bang. Be professional but approachable.\n- Example: \"Still using [Old Tech]? That's like bringing a knife to a laser fight.\"\n\n3. BODY CONTENT (Structured):\n- **The Problem:** Empathize with the reader's pain points.\n- **The Solution:** Explain the new tech/solution simply.\n- **Evidence:** Use the facts found via Perplexity.\n\n4. CITATIONS (IMPORTANT):\nLink directly to sources in the text.\nFormat: `...as seen in <a href=\"SOURCE_URL\">this study</a>.`\n\n5. FORMATTING (STRICT):\n- NO Markdown blocks. Pure HTML tags (<h2>, <p>, <ul>).\n- Use standard hyphens (-), never em-dashes.\n\n6. PUBLISH (Ghost Tool) - FINALE:\nUse the `Create Post` tool immediately:\n- Title: Click-worthy title (Max 60 chars).\n- Content: Your HTML text.\n- Meta Info: Filled out for SEO.\n\nDO NOT RETURN TEXT TO ME. EXECUTE THE TOOL.",
            "options": {
              "systemMessage": "You are a Senior Tech Blogger. \nSTYLE:\n- Smart, witty, but professional.\n- Data-driven (you love stats).\n- Helpful and clear.\n\nRULES:\n1. Use HTML for formatting.\n2. Cite your sources with hyperlinks.\n3. USE THE GHOST TOOL TO SAVE."
            },
            "promptType": "define",
            "hasOutputParser": true
          },
          "retryOnFail": true,
          "typeVersion": 1.7
        },
        {
          "id": "b38d5de9-770d-46d9-bbf3-dd5d9e5b072c",
          "name": "Perplexity Research",
          "type": "n8n-nodes-base.perplexityTool",
          "position": [
            2672,
            720
          ],
          "parameters": {
            "options": {},
            "messages": {
              "message": [
                {
                  "content": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('message0_Text', ``, 'string') }}"
                }
              ]
            },
            "simplify": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Simplify_Output', ``, 'boolean') }}",
            "requestOptions": {}
          },
          "credentials": {
            "perplexityApi": {
              "id": "SzbdkunqZ8JO1GFv",
              "name": "Perplexity account"
            }
          },
          "typeVersion": 1
        },
        {
          "id": "07881b33-f0c0-4c4d-bf7f-3f35575da751",
          "name": "Create Post",
          "type": "n8n-nodes-base.ghostTool",
          "position": [
            2816,
            720
          ],
          "parameters": {
            "title": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Title', ``, 'string') }}",
            "source": "adminApi",
            "content": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Content', ``, 'string') }}",
            "operation": "create",
            "additionalFields": {
              "status": "draft",
              "meta_title": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Meta_Title', ``, 'string') }}",
              "meta_description": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Meta_Description', `Focus on very good SEO`, 'string') }}"
            }
          },
          "credentials": {
            "ghostAdminApi": {
              "id": "uQZxY3Tst7oKZFVx",
              "name": "Ghost Admin account"
            }
          },
          "typeVersion": 1
        },
        {
          "id": "1a545970-c854-4342-b630-31b6cd7e96ed",
          "name": "Post Output Parser",
          "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
          "position": [
            2752,
            624
          ],
          "parameters": {
            "autoFix": true,
            "schemaType": "manual",
            "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"title\": { \"type\": \"string\" },\n    \"id\": { \"type\": \"string\", \"description\": \"Ghost Post ID\" },\n    \"updated_at\": { \"type\": \"string\", \"description\": \"Ghost updated_at timestamp\" },\n    \"content_html\": { \"type\": \"string\" },\n    \"meta_description\": { \"type\": \"string\" }\n  }\n}"
          },
          "typeVersion": 1.2
        },
        {
          "id": "7bf516ad-af92-4d5a-84b2-80b95b0e0ca7",
          "name": "Save to History",
          "type": "n8n-nodes-base.airtable",
          "position": [
            3072,
            240
          ],
          "parameters": {
            "base": {
              "__rl": true,
              "mode": "list",
              "value": "appXXXXXXXXXXXXXX",
              "cachedResultUrl": "https://airtable.com/",
              "cachedResultName": "YOUR_BASE_NAME"
            },
            "table": {
              "__rl": true,
              "mode": "list",
              "value": "tblXXXXXXXXXXXXXX",
              "cachedResultUrl": "https://airtable.com/",
              "cachedResultName": "YOUR_TABLE_NAME"
            },
            "columns": {
              "value": {
                "title": "={{ $json.output.title }}",
                "summary": "={{ $json.output.meta_description }}"
              },
              "schema": [
                {
                  "id": "title",
                  "type": "string",
                  "display": true,
                  "removed": false,
                  "readOnly": false,
                  "required": false,
                  "displayName": "title",
                  "defaultMatch": false,
                  "canBeUsedToMatch": true
                },
                {
                  "id": "summary",
                  "type": "string",
                  "display": true,
                  "removed": false,
                  "readOnly": false,
                  "required": false,
                  "displayName": "summary",
                  "defaultMatch": false,
                  "canBeUsedToMatch": true
                }
              ],
              "mappingMode": "defineBelow",
              "matchingColumns": [],
              "attemptToConvertTypes": false,
              "convertFieldsToString": false
            },
            "options": {},
            "operation": "create"
          },
          "credentials": {
            "airtableTokenApi": {
              "id": "LsOadaN5biaN8NBc",
              "name": "Airtable Personal Access Token account"
            }
          },
          "typeVersion": 2
        },
        {
          "id": "78c0cea3-96c7-46e1-b605-d960d110d585",
          "name": "Agent 3: Image Prompter",
          "type": "@n8n/n8n-nodes-langchain.agent",
          "position": [
            3072,
            672
          ],
          "parameters": {
            "text": "=ROLE:\nYou are a Minimalist Art Director for a Tech Blog.\n\nTASK:\nCreate an image generation prompt based on the blog post title.\n\nINPUT:\nTITLE: {{ $json.output.title }}\n\nINSTRUCTIONS:\n1. **Visual Metaphor:** Abstract geometry, physics, glass, metal. No people.\n2. **Typography:** Incorporate a key word from the title on a physical object in the scene (e.g. embossed on metal).\n3. **Vibe:** Dark mode, premium, cinematic lighting, 8k resolution, unreal engine render.\n\nOUTPUT:\nWrite ONLY the English image prompt.",
            "options": {},
            "promptType": "define",
            "hasOutputParser": true
          },
          "typeVersion": 3
        },
        {
          "id": "6f950b47-4dac-466e-a2e9-9c997977694b",
          "name": "Gemini Flash (Fast)",
          "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
          "position": [
            3088,
            880
          ],
          "parameters": {
            "model": "google/gemini-2.5-flash",
            "options": {}
          },
          "credentials": {
            "openRouterApi": {
              "id": "CErK3KqUFAxPvsBg",
              "name": "AIAGENTKEY"
            }
          },
          "typeVersion": 1
        },
        {
          "id": "620f5b72-f03c-4b25-8697-d91ad277c98e",
          "name": "Submit to Flux/Fal.ai",
          "type": "n8n-nodes-base.httpRequest",
          "position": [
            3552,
            672
          ],
          "parameters": {
            "url": "https://queue.fal.run/fal-ai/nano-banana-pro",
            "method": "POST",
            "options": {},
            "sendBody": true,
            "sendHeaders": true,
            "bodyParameters": {
              "parameters": [
                {
                  "name": "prompt",
                  "value": "={{ $json.output }}"
                },
                {
                  "name": "num_images",
                  "value": "1"
                },
                {
                  "name": "aspect_ratio",
                  "value": "16:9"
                },
                {
                  "name": "resolution",
                  "value": "4K"
                },
                {
                  "name": "output_format",
                  "value": "png"
                }
              ]
            },
            "headerParameters": {
              "parameters": [
                {
                  "name": "Authorization",
                  "value": "Key YOUR_FAL_AI_KEY_HERE"
                }
              ]
            }
          },
          "typeVersion": 4.2
        },
        {
          "id": "d795880e-f535-4e21-a93b-cfdece91e015",
          "name": "Wait 5s",
          "type": "n8n-nodes-base.wait",
          "position": [
            3744,
            672
          ],
          "webhookId": "wait-initial-5s",
          "parameters": {},
          "typeVersion": 1.1
        },
        {
          "id": "7acb7ea8-c7f7-4f4b-aebb-29ba07bbd195",
          "name": "Check Status",
          "type": "n8n-nodes-base.httpRequest",
          "position": [
            3936,
            672
          ],
          "parameters": {
            "url": "=https://queue.fal.run/fal-ai/nano-banana-pro/requests/{{ $json.request_id }}/status",
            "options": {
              "response": {
                "response": {
                  "neverError": true
                }
              }
            },
            "sendHeaders": true,
            "headerParameters": {
              "parameters": [
                {
                  "name": "Authorization",
                  "value": "Key YOUR_FAL_AI_KEY_HERE"
                }
              ]
            }
          },
          "typeVersion": 4.2,
          "alwaysOutputData": true
        },
        {
          "id": "7255f42a-8f00-4cd1-b507-aabaf78c0cb4",
          "name": "Is Completed?",
          "type": "n8n-nodes-base.if",
          "position": [
            4128,
            672
          ],
          "parameters": {
            "options": {},
            "conditions": {
              "options": {
                "version": 2,
                "leftValue": "",
                "caseSensitive": true,
                "typeValidation": "strict"
              },
              "combinator": "and",
              "conditions": [
                {
                  "id": "87c816eb-48f6-4f37-9095-199b237a6fb8",
                  "operator": {
                    "type": "string",
                    "operation": "equals"
                  },
                  "leftValue": "={{ $json.status }}",
                  "rightValue": "COMPLETED"
                }
              ]
            }
          },
          "typeVersion": 2.2
        },
        {
          "id": "db8f7213-2700-4ab5-b8b4-4dc9d13b4606",
          "name": "Retry Wait",
          "type": "n8n-nodes-base.wait",
          "position": [
            4128,
            864
          ],
          "webhookId": "wait-retry-3s",
          "parameters": {
            "amount": 3
          },
          "typeVersion": 1.1
        },
        {
          "id": "7702d521-f248-45b8-b745-63ed7c9a1e41",
          "name": "Get Result",
          "type": "n8n-nodes-base.httpRequest",
          "position": [
            4352,
            672
          ],
          "parameters": {
            "url": "=https://queue.fal.run/fal-ai/nano-banana-pro/requests/{{ $json.request_id }}",
            "options": {},
            "sendHeaders": true,
            "headerParameters": {
              "parameters": [
                {
                  "name": "Authorization",
                  "value": "Key YOUR_FAL_AI_KEY_HERE"
                }
              ]
            }
          },
          "typeVersion": 4.2
        },
        {
          "id": "ce967214-5311-4886-9b31-82a0a2bbc176",
          "name": "Split Images",
          "type": "n8n-nodes-base.splitOut",
          "position": [
            4544,
            672
          ],
          "parameters": {
            "options": {},
            "fieldToSplitOut": "images"
          },
          "typeVersion": 1
        },
        {
          "id": "cd670982-2967-4e3f-9790-6855c463be8a",
          "name": "Download Image",
          "type": "n8n-nodes-base.httpRequest",
          "position": [
            4752,
            672
          ],
          "parameters": {
            "url": "={{ $json.url }}",
            "options": {
              "response": {
                "response": {
                  "responseFormat": "file"
                }
              }
            }
          },
          "typeVersion": 4.2
        },
        {
          "id": "1422a835-eccc-47a9-b8f2-eb4ec8959b51",
          "name": "Upload to Ghost",
          "type": "n8n-nodes-base.httpRequest",
          "position": [
            4944,
            672
          ],
          "parameters": {
            "url": "https://YOUR-GHOST-BLOG.com/ghost/api/admin/images/upload/",
            "method": "POST",
            "options": {
              "response": {
                "response": {
                  "responseFormat": "json"
                }
              }
            },
            "sendBody": true,
            "contentType": "multipart-form-data",
            "authentication": "predefinedCredentialType",
            "bodyParameters": {
              "parameters": [
                {
                  "name": "file",
                  "parameterType": "formBinaryData",
                  "inputDataFieldName": "data"
                },
                {
                  "name": "purpose",
                  "value": "image"
                },
                {
                  "name": "ref",
                  "value": "={{ $('Agent 2: Ghost Writer').item.json.output.id }}"
                }
              ]
            },
            "nodeCredentialType": "ghostAdminApi"
          },
          "credentials": {
            "ghostAdminApi": {
              "id": "uQZxY3Tst7oKZFVx",
              "name": "Ghost Admin account"
            }
          },
          "typeVersion": 4.2,
          "alwaysOutputData": true
        },
        {
          "id": "e922118c-84e8-435b-983e-dc7bee8ad868",
          "name": "Update Post with Image",
          "type": "n8n-nodes-base.httpRequest",
          "position": [
            5168,
            672
          ],
          "parameters": {
            "url": "={{ 'https://YOUR-GHOST-BLOG.com/ghost/api/admin/posts/' + $('Agent 2: Ghost Writer').item.json.output.id + '/' }}",
            "method": "PUT",
            "options": {
              "response": {
                "response": {
                  "responseFormat": "json"
                }
              }
            },
            "jsonBody": "={\n  \"posts\": [{\n    \"feature_image\": \"{{ $json.images[0].url }}\",\n    \"updated_at\": \"{{ $('Agent 2: Ghost Writer').item.json.output.updated_at }}\"\n  }]\n}",
            "sendBody": true,
            "specifyBody": "json",
            "authentication": "predefinedCredentialType",
            "nodeCredentialType": "ghostAdminApi"
          },
          "credentials": {
            "ghostAdminApi": {
              "id": "uQZxY3Tst7oKZFVx",
              "name": "Ghost Admin account"
            }
          },
          "typeVersion": 4.2,
          "alwaysOutputData": true
        },
        {
          "id": "07aa7c94-7ecf-4a07-ba61-5e8c01f949ad",
          "name": "Sticky Note: Intro",
          "type": "n8n-nodes-base.stickyNote",
          "position": [
            -608,
            112
          ],
          "parameters": {
            "color": 5,
            "width": 480,
            "height": 432,
            "content": "###  šŸš€  Start Here: Setup Instructions\n\n**1. News Sources:**\nConfigure the RSS Node with your favorite Feed URL. Configure the NewsAPI Node with your API Key.\n\n**2. AI Models:**\nEnsure you have an OpenRouter or OpenAI credential set up in the AI Agent nodes.\n\n**3. Ghost & Airtable:**\nAdd your Ghost Admin Credentials and Airtable Token. \n\n*Goal: This workflow automatically finds trending topics, checks your history to avoid duplicates, writes a full article, and generates a matching cover image.*"
          },
          "typeVersion": 1
        },
        {
          "id": "fa8f855a-5dbd-4bff-b76f-b4377b3a126f",
          "name": "Sticky Note: Sources",
          "type": "n8n-nodes-base.stickyNote",
          "position": [
            240,
            -16
          ],
          "parameters": {
            "color": 4,
            "width": 484,
            "height": 804,
            "content": "### šŸ“°   Step 1: News Sources\n\nGet data from RSS and NewsAPI.\n\n**Action Required:**\n- Enter your NewsAPI Key in the `NewsAPI Source` node.\n- Enter your Feed URL in the `RSS Feed Source` node."
          },
          "typeVersion": 1
        },
        {
          "id": "63abb65c-2933-4c7c-8e37-6d6fcffc024a",
          "name": "Sticky Note: Scout",
          "type": "n8n-nodes-base.stickyNote",
          "position": [
            2016,
            208
          ],
          "parameters": {
            "color": 3,
            "width": 444,
            "height": 704,
            "content": "### 🧠 Step 2: Topic Scout\n\nThis AI Agent compares new news against your Airtable history.\n\n**Model:** Gemini 3 Pro (Review).\n**Logic:** Finds the best SEO opportunity that you haven't written about yet."
          },
          "typeVersion": 1
        },
        {
          "id": "0a4dc56b-2cf5-4152-925e-05683f5919e5",
          "name": "Sticky Note: Writer",
          "type": "n8n-nodes-base.stickyNote",
          "position": [
            2496,
            208
          ],
          "parameters": {
            "color": 2,
            "width": 508,
            "height": 704,
            "content": "### āœļø Step 3: Ghost Writer\n\nDrafts the full article using Perplexity for facts.\n\n**Model:** Claude 3.5 Sonnet / GPT-5.\n**Tools:** Perplexity (Research), Ghost (Create Draft)."
          },
          "typeVersion": 1
        },
        {
          "id": "d47f31a4-ec61-4119-82fa-11d4cc9d7b31",
          "name": "Sticky Note: Art",
          "type": "n8n-nodes-base.stickyNote",
          "position": [
            3040,
            480
          ],
          "parameters": {
            "color": 6,
            "width": 676,
            "height": 528,
            "content": "### šŸŽØ Step 4: Art Director\n\nGenerates a prompt, creates the image via Fal.ai, and uploads it to Ghost.\n\n**Model:** Gemini Flash (Fast).\n**Action:** Add your Fal.ai Key in the HTTP nodes."
          },
          "typeVersion": 1
        },
        {
          "id": "45f439a1-bbae-4cc5-b8b6-9575e72193d8",
          "name": "Claude Sonnet 4.5 (Writer)",
          "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
          "position": [
            2544,
            720
          ],
          "parameters": {
            "model": "anthropic/claude-sonnet-4.5",
            "options": {
              "topP": 1,
              "maxTokens": -1,
              "temperature": 0.7,
              "presencePenalty": 0,
              "frequencyPenalty": 0
            }
          },
          "credentials": {
            "openRouterApi": {
              "id": "CErK3KqUFAxPvsBg",
              "name": "AIAGENTKEY"
            }
          },
          "typeVersion": 1
        },
        {
          "id": "75d0b341-3f7b-49a6-af13-186c1a3a5928",
          "name": "Sticky Note",
          "type": "n8n-nodes-base.stickyNote",
          "position": [
            4704,
            400
          ],
          "parameters": {
            "width": 704,
            "height": 464,
            "content": "### šŸ–¼ļø Image Pipeline & Ghost Upload\n\nSince Ghost does not automatically import images from external URLs, the upload process requires 3 specific steps:\n\n1. **Download:** Retrieves the generated image from Fal.ai as a binary file. \n2. **Upload:** Sends the binary data to the Ghost API (/images/upload). Ghost returns the internal file path. \n3. **Update:** Updates the previously drafted post via a PUT request, setting the feature_image to the new local path."
          },
          "typeVersion": 1
        }
      ],
      "pinData": {},
      "connections": {
        "Wait 5s": {
          "main": [
            [
              {
                "node": "Check Status",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Get Result": {
          "main": [
            [
              {
                "node": "Split Images",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Retry Wait": {
          "main": [
            [
              {
                "node": "Check Status",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Create Post": {
          "ai_tool": [
            [
              {
                "node": "Agent 2: Ghost Writer",
                "type": "ai_tool",
                "index": 0
              }
            ]
          ]
        },
        "Check Status": {
          "main": [
            [
              {
                "node": "Is Completed?",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Split Images": {
          "main": [
            [
              {
                "node": "Download Image",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Topic Parser": {
          "ai_outputParser": [
            [
              {
                "node": "Agent 1: Topic Scout",
                "type": "ai_outputParser",
                "index": 0
              }
            ]
          ]
        },
        "Is Completed?": {
          "main": [
            [
              {
                "node": "Get Result",
                "type": "main",
                "index": 0
              }
            ],
            [
              {
                "node": "Retry Wait",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Merge Sources": {
          "main": [
            [
              {
                "node": "Filter Recent (48h)",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Download Image": {
          "main": [
            [
              {
                "node": "Upload to Ghost",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Format Context": {
          "main": [
            [
              {
                "node": "Agent 1: Topic Scout",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "NewsAPI Source": {
          "main": [
            [
              {
                "node": "Normalize NewsAPI",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "RSS Feed Source": {
          "main": [
            [
              {
                "node": "Merge Sources",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Upload to Ghost": {
          "main": [
            [
              {
                "node": "Update Post with Image",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Get Topic History": {
          "main": [
            [
              {
                "node": "Merge News & History",
                "type": "main",
                "index": 1
              }
            ]
          ]
        },
        "Normalize NewsAPI": {
          "main": [
            [
              {
                "node": "Merge Sources",
                "type": "main",
                "index": 1
              }
            ]
          ]
        },
        "Remove Duplicates": {
          "main": [
            [
              {
                "node": "Prepare Data for AI",
                "type": "main",
                "index": 0
              },
              {
                "node": "Get Topic History",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Gemini Pro (Topic)": {
          "ai_languageModel": [
            [
              {
                "node": "Agent 1: Topic Scout",
                "type": "ai_languageModel",
                "index": 0
              },
              {
                "node": "Topic Parser",
                "type": "ai_languageModel",
                "index": 0
              }
            ]
          ]
        },
        "Post Output Parser": {
          "ai_outputParser": [
            [
              {
                "node": "Agent 2: Ghost Writer",
                "type": "ai_outputParser",
                "index": 0
              }
            ]
          ]
        },
        "Filter Recent (48h)": {
          "main": [
            [
              {
                "node": "Remove Duplicates",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Gemini Flash (Fast)": {
          "ai_languageModel": [
            [
              {
                "node": "Agent 3: Image Prompter",
                "type": "ai_languageModel",
                "index": 0
              }
            ]
          ]
        },
        "Perplexity Research": {
          "ai_tool": [
            [
              {
                "node": "Agent 2: Ghost Writer",
                "type": "ai_tool",
                "index": 0
              }
            ]
          ]
        },
        "Prepare Data for AI": {
          "main": [
            [
              {
                "node": "Merge News & History",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Agent 1: Topic Scout": {
          "main": [
            [
              {
                "node": "Agent 2: Ghost Writer",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Merge News & History": {
          "main": [
            [
              {
                "node": "Format Context",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Agent 2: Ghost Writer": {
          "main": [
            [
              {
                "node": "Save to History",
                "type": "main",
                "index": 0
              },
              {
                "node": "Agent 3: Image Prompter",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Schedule: Daily 09:00": {
          "main": [
            [
              {
                "node": "RSS Feed Source",
                "type": "main",
                "index": 0
              },
              {
                "node": "NewsAPI Source",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Submit to Flux/Fal.ai": {
          "main": [
            [
              {
                "node": "Wait 5s",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Agent 3: Image Prompter": {
          "main": [
            [
              {
                "node": "Submit to Flux/Fal.ai",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Claude Sonnet 4.5 (Writer)": {
          "ai_languageModel": [
            [
              {
                "node": "Agent 2: Ghost Writer",
                "type": "ai_languageModel",
                "index": 0
              },
              {
                "node": "Post Output Parser",
                "type": "ai_languageModel",
                "index": 0
              }
            ]
          ]
        }
      }
    }