{
  "name": "CMO Start - Campaign Planner",
  "description": null,
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "path": "cmo-start",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -608,
        304
      ],
      "id": "cmo-start-webhook",
      "name": "Webhook",
      "webhookId": "<<REPLACE_ME>>"
    },
    {
      "parameters": {
        "jsCode": "const apiKey = \"<<REPLACE_ME>>\";\nreturn { apiKey };"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -400,
        304
      ],
      "id": "api-key",
      "name": "API Key"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://aicivz8c3l.execute-api.ap-south-1.amazonaws.com/dev/mcp/call",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "X-API-Key",
              "value": "={{ $('API Key').item.json.apiKey }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\n  \"params\":{\"name\":\"graphql_query\",\"arguments\":{\n    \"query\":\"query G($id: ID!) { goalItemById(id: $id) { id body date period subTasks { id body } } }\",\n    \"variables\":{ \"id\":\"{{ $('Webhook').item.json.query.goal_id }}\" }\n  }},\"id\":1\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -208,
        304
      ],
      "id": "get-goal",
      "name": "Get Goal"
    },
    {
      "parameters": {
        "jsCode": "\nconst text = $input.first().json.result.content[0].text;\nconst data = JSON.parse(text);\nconst g = (data.data ? data.data.goalItemById : data.goalItemById) || {};\nconst subs = g.subTasks || [];\nconst now = new Date(); const pad = x => String(x).padStart(2,'0');\nconst today = pad(now.getDate())+'-'+pad(now.getMonth()+1)+'-'+now.getFullYear();\nreturn [{ json: {\n  idea: g.body || '',\n  goalId: $('Webhook').first().json.query.goal_id,\n  date: g.date || today,\n  period: g.period || 'day',\n  existingTitles: subs.map(s => String(s.body || '').trim())\n}}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        0,
        304
      ],
      "id": "parse-goal",
      "name": "Parse Goal"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { ok: true, goal_id: $('Parse Goal').first().json.goalId, status: 'campaign planned', blogTitle: (($('Parse Blog').first().json.blog || {}).title) || '' } }}",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        4400,
        304
      ],
      "id": "respond",
      "name": "Respond to Webhook"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.perplexity.ai/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "perplexityApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model:'sonar-pro', messages:[ {role:'system',content:'You are a research assistant. Return concrete, recent, real-world statistics, survey results and named case studies relevant to the topic, each WITH its source name and date. Prefer hard numbers (%, $, counts) from reputable reports. Be concise and strictly factual.'}, {role:'user',content:'Topic: ' + $('Parse Goal').first().json.idea + '\\n\\nGive 4-6 named case studies or industry/survey metrics (with numbers and sources) directly relevant to this topic.'} ] }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        400,
        304
      ],
      "id": "research-metrics",
      "name": "Research Metrics",
      "alwaysOutputData": true,
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 3000,
      "credentials": {
        "perplexityApi": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: Perplexity account)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "\nlet researchText='', citations=[];\ntry {\n  const r = $('Research Metrics').first().json;\n  researchText = (r && r.choices && r.choices[0] && r.choices[0].message && r.choices[0].message.content) || '';\n  citations = r.citations || r.search_results || [];\n} catch (e) {}\nif (!researchText) researchText = '(no external research returned; do NOT invent precise statistics - if you cite a number, label it as an approximate, widely-known figure)';\nreturn [{ json: { researchText: String(researchText).slice(0,6000), citations } }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        608,
        304
      ],
      "id": "prep-research",
      "name": "Prep Research"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "HTTP-Referer",
              "value": "https://workflows.grvpanchal.me"
            },
            {
              "name": "X-Title",
              "value": "CMO Campaign Engine"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ models:['~google/gemini-flash-latest'], reasoning:{enabled:false}, response_format:{type:'json_object'}, messages:[ {role:'system',content:\"You are a senior content strategist writing a FLAGSHIP blog post that must deliver genuine, specific value. The bar is Sandeep Swadia (theMITmonk) and James Jani: dense, concrete, framework-driven, NEVER vague or generic. Return ONLY a raw JSON object (no markdown fences) with EXACTLY these keys: title, hook, sections, architecture, key_takeaways, hero_image_prompt, meta_description, hashtags, sources. STRUCTURE - YOU architect the narrative: sections = an ARRAY of 5-8 objects {heading, body} forming an engaging arc with open loops, payoffs and momentum (each section should make the reader need the next one). Every heading is a sharp, curiosity-driven line unique to THIS topic - a claim, a number, a tension, a question (e.g. 'The $40k Mistake Everyone Ships', 'Your Bottleneck Is Not the Model'). NEVER use generic labels like 'What', 'Why', 'How', 'Introduction', 'Overview', 'Case Studies' or 'Conclusion' as headings. MANDATORY COVERAGE - woven naturally through the narrative, never labeled: by the end the reader must know (a) WHAT the thing is, (b) WHY it matters and the root cause it fixes, (c) HOW to implement it step by step, and (d) the recommended APPROACH and the reasoning behind choosing it. MANDATORY QUALITY RULES: 1. 'hook' opens with a contrarian gap + asymmetric promise (the mistake most people make -> the reader's opening). 2. Deliver at least ONE named, reusable framework (an acronym or a numbered system) the reader can apply. 3. Quantify everything. For ANY real-world statistic or case-study figure use ONLY numbers found in the RESEARCH the user provides, and cite the source inline like (Source: ...). Do NOT invent statistics. If the research is thin, write 'limited public data' rather than fabricate. 4. Every abstract claim gets a concrete worked example. 5. Include exactly one mechanism-revealing analogy. 6. Diagnose the root cause / reframe the real problem (it's not X, it's Y). 7. Ground it in a concrete case or story that includes a failure, and put named cases with their research-sourced metrics + inline (Source: ...) in whichever section they best serve. 8. The FINAL section ends with elevation - tie the tactics to a larger purpose. Tone: direct second-person, beginner-safe, confident, no fluff, no emoji filler. Do NOT use level-1 or level-2 markdown headings (# or ##) inside any field; use bold only. FIELD SPEC: title = sharp, curiosity + benefit. Each section body = 80-180 words (markdown ok). architecture = an OBJECT {section_heading, mermaid}: section_heading = the EXACT heading of the ONE section where you present the approach/framework (its diagram renders there); mermaid = a COMPACT valid Mermaid diagram string starting 'flowchart TB' or 'flowchart LR', max 10 nodes, SHORT alphanumeric labels, NO parentheses/quotes/colons/special characters inside labels (they break rendering). key_takeaways = array of 4-6 punchy strings. hero_image_prompt = a vivid prompt for a clean flat-illustration hero image, NO text in the image. meta_description = <=160 chars. hashtags = array of 5-8 strings WITHOUT #. sources = array of the source URLs/names you used.\"}, {role:'user',content:'CAMPAIGN TOPIC:\\n' + $('Parse Goal').first().json.idea + '\\n\\nRESEARCH (use ONLY these figures for any real-world metric, and cite the source inline):\\n' + $('Prep Research').first().json.researchText + '\\n\\nReturn the blog JSON now.'} ] }) }}",
        "options": {
          "timeout": 150000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        800,
        304
      ],
      "id": "generate-blog",
      "name": "Generate Blog",
      "alwaysOutputData": true,
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 3000,
      "credentials": {
        "openRouterApi": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: OpenRouter account)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "\nconst extractJson = (s) => {\n  s = String(s || '').trim().replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/, '');\n  try { return JSON.parse(s); } catch (e) {}\n  const i = s.indexOf('{');\n  if (i < 0) return null;\n  let depth = 0, inStr = false, esc = false;\n  for (let k = i; k < s.length; k++) {\n    const ch = s[k];\n    if (esc) { esc = false; continue; }\n    if (inStr) { if (ch === '\\\\') esc = true; else if (ch === '\"') inStr = false; continue; }\n    if (ch === '\"') { inStr = true; continue; }\n    if (ch === '{') depth++;\n    else if (ch === '}') { depth--; if (!depth) { try { return JSON.parse(s.slice(i, k + 1)); } catch (e) { return null; } } }\n  }\n  return null;\n};\n\nlet blog = null;\ntry { const c = $('Generate Blog').first().json; blog = extractJson(c && c.choices ? c.choices[0].message.content : ''); } catch (e) {}\nconst blogOk = !!(blog && blog.title && Array.isArray(blog.sections) && blog.sections.length >= 4);\nreturn [{ json: { blogOk } }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        304
      ],
      "id": "check-blog",
      "name": "Check Blog"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 1
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.blogOk }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        1104,
        224
      ],
      "id": "blog-ok",
      "name": "Blog OK?"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 1
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ !$json.blogOk }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        1104,
        464
      ],
      "id": "blog-fallback",
      "name": "Blog Fallback?"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "HTTP-Referer",
              "value": "https://workflows.grvpanchal.me"
            },
            {
              "name": "X-Title",
              "value": "CMO Campaign Engine"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ models:['anthropic/claude-sonnet-5'], reasoning:{enabled:false}, response_format:{type:'json_object'}, messages:[ {role:'system',content:\"You are a senior content strategist writing a FLAGSHIP blog post that must deliver genuine, specific value. The bar is Sandeep Swadia (theMITmonk) and James Jani: dense, concrete, framework-driven, NEVER vague or generic. Return ONLY a raw JSON object (no markdown fences) with EXACTLY these keys: title, hook, sections, architecture, key_takeaways, hero_image_prompt, meta_description, hashtags, sources. STRUCTURE - YOU architect the narrative: sections = an ARRAY of 5-8 objects {heading, body} forming an engaging arc with open loops, payoffs and momentum (each section should make the reader need the next one). Every heading is a sharp, curiosity-driven line unique to THIS topic - a claim, a number, a tension, a question (e.g. 'The $40k Mistake Everyone Ships', 'Your Bottleneck Is Not the Model'). NEVER use generic labels like 'What', 'Why', 'How', 'Introduction', 'Overview', 'Case Studies' or 'Conclusion' as headings. MANDATORY COVERAGE - woven naturally through the narrative, never labeled: by the end the reader must know (a) WHAT the thing is, (b) WHY it matters and the root cause it fixes, (c) HOW to implement it step by step, and (d) the recommended APPROACH and the reasoning behind choosing it. MANDATORY QUALITY RULES: 1. 'hook' opens with a contrarian gap + asymmetric promise (the mistake most people make -> the reader's opening). 2. Deliver at least ONE named, reusable framework (an acronym or a numbered system) the reader can apply. 3. Quantify everything. For ANY real-world statistic or case-study figure use ONLY numbers found in the RESEARCH the user provides, and cite the source inline like (Source: ...). Do NOT invent statistics. If the research is thin, write 'limited public data' rather than fabricate. 4. Every abstract claim gets a concrete worked example. 5. Include exactly one mechanism-revealing analogy. 6. Diagnose the root cause / reframe the real problem (it's not X, it's Y). 7. Ground it in a concrete case or story that includes a failure, and put named cases with their research-sourced metrics + inline (Source: ...) in whichever section they best serve. 8. The FINAL section ends with elevation - tie the tactics to a larger purpose. Tone: direct second-person, beginner-safe, confident, no fluff, no emoji filler. Do NOT use level-1 or level-2 markdown headings (# or ##) inside any field; use bold only. FIELD SPEC: title = sharp, curiosity + benefit. Each section body = 80-180 words (markdown ok). architecture = an OBJECT {section_heading, mermaid}: section_heading = the EXACT heading of the ONE section where you present the approach/framework (its diagram renders there); mermaid = a COMPACT valid Mermaid diagram string starting 'flowchart TB' or 'flowchart LR', max 10 nodes, SHORT alphanumeric labels, NO parentheses/quotes/colons/special characters inside labels (they break rendering). key_takeaways = array of 4-6 punchy strings. hero_image_prompt = a vivid prompt for a clean flat-illustration hero image, NO text in the image. meta_description = <=160 chars. hashtags = array of 5-8 strings WITHOUT #. sources = array of the source URLs/names you used.\"}, {role:'user',content:'CAMPAIGN TOPIC:\\n' + $('Parse Goal').first().json.idea + '\\n\\nRESEARCH (use ONLY these figures for any real-world metric, and cite the source inline):\\n' + $('Prep Research').first().json.researchText + '\\n\\nReturn the blog JSON now.'} ] }) }}",
        "options": {
          "timeout": 150000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1264,
        464
      ],
      "id": "generate-blog-paid",
      "name": "Generate Blog Paid",
      "alwaysOutputData": true,
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 3000,
      "credentials": {
        "openRouterApi": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: OpenRouter account)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "\nconst extractJson = (s) => {\n  s = String(s || '').trim().replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/, '');\n  try { return JSON.parse(s); } catch (e) {}\n  const i = s.indexOf('{');\n  if (i < 0) return null;\n  let depth = 0, inStr = false, esc = false;\n  for (let k = i; k < s.length; k++) {\n    const ch = s[k];\n    if (esc) { esc = false; continue; }\n    if (inStr) { if (ch === '\\\\') esc = true; else if (ch === '\"') inStr = false; continue; }\n    if (ch === '\"') { inStr = true; continue; }\n    if (ch === '{') depth++;\n    else if (ch === '}') { depth--; if (!depth) { try { return JSON.parse(s.slice(i, k + 1)); } catch (e) { return null; } } }\n  }\n  return null;\n};\n\nlet blog = null;\ntry { const c = $('Generate Blog').first().json; blog = extractJson(c && c.choices ? c.choices[0].message.content : ''); } catch (e) {}\nif (!(blog && blog.title && Array.isArray(blog.sections) && blog.sections.length >= 4)) {\n  try { const c2 = $('Generate Blog Paid').first().json;\n    blog = extractJson(c2 && c2.choices ? c2.choices[0].message.content : '') || blog; } catch (e) {}\n}\nblog = blog || {};\nconst arch = blog.architecture || {};\nlet diagramUrl = '';\nconst mer = String(arch.mermaid || '').trim();\nif (mer) {\n  const b64 = Buffer.from(mer, 'utf8').toString('base64').replace(/\\+/g,'-').replace(/\\//g,'_').replace(/=+$/,'');\n  diagramUrl = 'https://mermaid.ink/img/' + b64;\n}\nconst kt = Array.isArray(blog.key_takeaways) ? ('- ' + blog.key_takeaways.join('\\n- ')) : '';\n// dynamic narrative sections (writer-chosen headings) - the channels read this as their source of truth\nconst secs = Array.isArray(blog.sections) ? blog.sections : [];\nconst blogSource = [\n  'TITLE: ' + (blog.title || ''),\n  'HOOK: ' + (blog.hook || ''),\n  ...secs.map(s => ((s && s.heading) ? String(s.heading) : 'SECTION') + ':\\n' + ((s && s.body) || '')),\n  'KEY TAKEAWAYS:\\n' + kt\n].join('\\n\\n').slice(0,7000);\nconst heroPrompt = blog.hero_image_prompt || ('Modern professional editorial flat illustration about: ' + ($('Parse Goal').first().json.idea || ''));\nreturn [{ json: { blog, diagramUrl, blogSource, heroPrompt } }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1008,
        304
      ],
      "id": "parse-blog",
      "name": "Parse Blog"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "HTTP-Referer",
              "value": "https://workflows.grvpanchal.me"
            },
            {
              "name": "X-Title",
              "value": "CMO Campaign Engine"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model:'google/gemini-2.5-flash-image', modalities:['image','text'], messages:[ {role:'user',content:'Clean modern flat-illustration hero image, 16:9 aspect ratio, bold color, professional, NO text or words anywhere in the image. ' + $('Parse Blog').first().json.heroPrompt} ] }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1200,
        304
      ],
      "id": "generate-hero-image",
      "name": "Generate Hero Image",
      "alwaysOutputData": true,
      "credentials": {
        "openRouterApi": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: OpenRouter account)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "\nconst resp = $input.first().json;\nlet b64='', mime='image/png', hasImage=false;\nconst msg = resp && resp.choices && resp.choices[0] && resp.choices[0].message;\nif (msg && Array.isArray(msg.images) && msg.images.length) {\n  const u = (msg.images[0].image_url && msg.images[0].image_url.url) || msg.images[0].url || '';\n  const mm = u.match(/^data:(image\\/[a-zA-Z+]+);base64,(.+)$/);\n  if (mm) { mime=mm[1]; b64=mm[2]; hasImage=true; }\n}\nconst o = { json: { hasImage, mime }, pairedItem: 0 };\nif (hasImage) { const buf = Buffer.from(b64,'base64'); o.binary = { data: await this.helpers.prepareBinaryData(buf,'cmo-hero.png',mime) }; }\nreturn [o];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1408,
        304
      ],
      "id": "prep-image",
      "name": "Prep Image"
    },
    {
      "parameters": {
        "name": "=cmo-hero.png",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "<<REPLACE_ME>>"
        },
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "root"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        1600,
        304
      ],
      "id": "upload-to-drive",
      "name": "Upload to Drive",
      "alwaysOutputData": true,
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: grvpanchalus Drive)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://www.googleapis.com/drive/v3/files/{{ $json.id }}/permissions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleDriveOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ role:'reader', type:'anyone' }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1808,
        304
      ],
      "id": "make-public",
      "name": "Make Public",
      "alwaysOutputData": true,
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: grvpanchalus Drive)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "HTTP-Referer",
              "value": "https://workflows.grvpanchal.me"
            },
            {
              "name": "X-Title",
              "value": "CMO Campaign Engine"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ models:['~google/gemini-flash-latest'], reasoning:{enabled:false}, response_format:{type:'json_object'}, messages:[ {role:'system',content:\"You are scripting a documentary-style explainer video voiceover in the style of James Jani: a calm, curious investigator; rhetorical questions are the engine; lay out evidence before any verdict; never a tidy bow. Use the SOURCE BLOG as your factual base and do not invent facts beyond it. Return ONLY a raw JSON object with EXACTLY: headline, script, broll_prompts, caption, hashtags, cta. script = the SPOKEN voiceover ONLY (natural sentences said aloud; NO timestamps, NO 'Visual:'/'VO:' labels, NO shot directions), 600-900 words, following this 10-beat arc: 1) COLD OPEN with NO greeting - drop in on one concrete artifact (a quoted line, a startling number, or a first-person confession). 2) STATE THE SCALE in a hard number and pose the central question. 3) REWIND to the innocent origin. 4) ESCALATE in layers (individual -> systemic -> cultural), each ending on a small payoff. 5) REVEAL THE MECHANISM ('follow the money'): surface claim -> the real driver -> who profits and who pays. 6) DROP THE TWIST that complicates the easy story. 7) SHOW THE HUMAN COST with a named example (the emotional peak). 8) BRIDGE sections with rhetorical questions / 'so here's your next warning' / 'the problem with X is'. 9) Hold the calm investigator voice throughout. 10) CLOSE with a direct second-person warning + rule, optionally an unresolved question. broll_prompts = an ORDERED array of SHORT stock-footage SEARCH QUERIES (2-4 words each) that will be looked up on a stock-video site (Pexels) - so each must be a concrete, literal, searchable subject, e.g. 'city skyline night', 'hands counting cash', 'server room', 'crowded subway', 'stock market chart', 'lonely person laptop'. Give ONE query per sentence/beat of the script, in the SAME order as the script, so the footage tracks the narration. Provide ENOUGH queries to cover the WHOLE script - roughly one every 5 spoken seconds (typically 18-30 for a 600-900 word script). Plain searchable nouns/scenes ONLY: NO camera jargon, NO full sentences, NO on-screen text, NO logos, NO talking heads. caption = a 2-3 sentence YouTube description. hashtags = array of 5-8 WITHOUT #. cta = a subscribe / 'be curious, seek truth' style closing line.\"}, {role:'user',content:'SOURCE BLOG (this is your single source of truth; do not invent facts beyond it):\\n' + $('Parse Blog').first().json.blogSource + '\\n\\nNow produce ONLY the raw JSON for this channel as specified.'} ] }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2112,
        -304
      ],
      "id": "explainer-video",
      "name": "Explainer Video",
      "alwaysOutputData": true,
      "credentials": {
        "openRouterApi": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: OpenRouter account)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "HTTP-Referer",
              "value": "https://workflows.grvpanchal.me"
            },
            {
              "name": "X-Title",
              "value": "CMO Campaign Engine"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ models:['~google/gemini-flash-latest'], reasoning:{enabled:false}, response_format:{type:'json_object'}, messages:[ {role:'system',content:\"You script a 15-30 second Instagram Reel from the SOURCE BLOG. The first 2 seconds MUST be a pattern-interrupt hook; deliver ONE sharp insight; end on a loop / re-hook. Return ONLY raw JSON with EXACTLY: headline, script, broll_prompts, caption, hashtags, cta. script = spoken words only (no labels/timestamps), tight and punchy. broll_prompts = an ORDERED array of 4-8 SHORT stock-footage SEARCH QUERIES (2-4 words, concrete literal searchable subjects like 'city skyline night' or 'hands typing keyboard'), in script order so the footage tracks the narration; plain searchable scenes ONLY, NO camera jargon, NO on-screen text, NO logos. caption = punchy caption. hashtags = 8-12 WITHOUT #. cta = 'Full breakdown on the blog - link in bio'.\"}, {role:'user',content:'SOURCE BLOG (this is your single source of truth; do not invent facts beyond it):\\n' + $('Parse Blog').first().json.blogSource + '\\n\\nNow produce ONLY the raw JSON for this channel as specified.'} ] }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2112,
        64
      ],
      "id": "instagram-reel",
      "name": "Instagram Reel",
      "alwaysOutputData": true,
      "credentials": {
        "openRouterApi": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: OpenRouter account)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "HTTP-Referer",
              "value": "https://workflows.grvpanchal.me"
            },
            {
              "name": "X-Title",
              "value": "CMO Campaign Engine"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ models:['~google/gemini-flash-latest'], reasoning:{enabled:false}, response_format:{type:'json_object'}, messages:[ {role:'system',content:\"You write a Facebook post from the SOURCE BLOG: story-led and conversational, community framing, a relatable opening, ONE concrete insight backed by a number, and a soft CTA plus a question to spark comments. Return ONLY raw JSON with EXACTLY: headline, body, caption, hashtags, cta. body = the full post text. caption = same as body. hashtags = array of 3-5 WITHOUT #. cta = a question + 'full breakdown on the blog'.\"}, {role:'user',content:'SOURCE BLOG (this is your single source of truth; do not invent facts beyond it):\\n' + $('Parse Blog').first().json.blogSource + '\\n\\nNow produce ONLY the raw JSON for this channel as specified.'} ] }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2112,
        432
      ],
      "id": "facebook-post",
      "name": "Facebook Post",
      "alwaysOutputData": true,
      "credentials": {
        "openRouterApi": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: OpenRouter account)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "HTTP-Referer",
              "value": "https://workflows.grvpanchal.me"
            },
            {
              "name": "X-Title",
              "value": "CMO Campaign Engine"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ models:['~google/gemini-flash-latest'], reasoning:{enabled:false}, response_format:{type:'json_object'}, messages:[ {role:'system',content:\"You write an X (Twitter) thread plus a seed engagement reply from the SOURCE BLOG. Return ONLY raw JSON with EXACTLY: headline, body, caption, hashtags, cta. body = a 4-6 tweet thread: tweet 1 is a contrarian or number-led hook, each tweet is ONE idea, tweets separated by a blank line, the final tweet is a CTA to the blog. caption = a sharp seed reply (a question or hot-take) to post under the thread to start discussion. hashtags = array of 2-4 WITHOUT #. cta = a 'read the full breakdown on the blog' line.\"}, {role:'user',content:'SOURCE BLOG (this is your single source of truth; do not invent facts beyond it):\\n' + $('Parse Blog').first().json.blogSource + '\\n\\nNow produce ONLY the raw JSON for this channel as specified.'} ] }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2112,
        608
      ],
      "id": "x-post",
      "name": "X Post",
      "alwaysOutputData": true,
      "credentials": {
        "openRouterApi": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: OpenRouter account)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "HTTP-Referer",
              "value": "https://workflows.grvpanchal.me"
            },
            {
              "name": "X-Title",
              "value": "CMO Campaign Engine"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ models:['~google/gemini-flash-latest'], reasoning:{enabled:false}, response_format:{type:'json_object'}, messages:[ {role:'system',content:\"You script a 20-30 second TikTok from the SOURCE BLOG: native, casual, fast value, a hook in the first 2 seconds, trend-aware phrasing. Return ONLY raw JSON with EXACTLY: headline, script, broll_prompts, caption, hashtags, cta. script = spoken words only (no labels/timestamps), conversational. broll_prompts = an ORDERED array of 4-8 SHORT stock-footage SEARCH QUERIES (2-4 words, concrete literal searchable subjects like 'city skyline night' or 'hands typing keyboard'), in script order so footage tracks the narration; plain searchable scenes ONLY, NO camera jargon, NO on-screen text, NO logos. caption = punchy. hashtags = 8-12 WITHOUT #. cta = 'full breakdown on the blog'.\"}, {role:'user',content:'SOURCE BLOG (this is your single source of truth; do not invent facts beyond it):\\n' + $('Parse Blog').first().json.blogSource + '\\n\\nNow produce ONLY the raw JSON for this channel as specified.'} ] }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2112,
        784
      ],
      "id": "tiktok",
      "name": "TikTok",
      "alwaysOutputData": true,
      "credentials": {
        "openRouterApi": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: OpenRouter account)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "HTTP-Referer",
              "value": "https://workflows.grvpanchal.me"
            },
            {
              "name": "X-Title",
              "value": "CMO Campaign Engine"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ models:['~google/gemini-flash-latest'], reasoning:{enabled:false}, response_format:{type:'json_object'}, messages:[ {role:'system',content:\"You write a LinkedIn post (single image + caption) from the SOURCE BLOG, aimed at professionals. Framework: a strong first-line hook -> a short insight or story with one concrete number -> 3 crisp takeaways on separate lines -> a CTA that reads 'Full breakdown in the video below' followed by the LITERAL placeholder __YOUTUBE_URL__ on its own line (it is replaced with the real video link at publish time). Return ONLY raw JSON with EXACTLY: headline, body, caption, hashtags, cta. body = the full post text INCLUDING the 3 takeaways and the CTA line with the literal __YOUTUBE_URL__ placeholder. caption = same as body. hashtags = array of 3-5 WITHOUT #. cta = 'Watch the full explainer: __YOUTUBE_URL__'.\"}, {role:'user',content:'SOURCE BLOG (this is your single source of truth; do not invent facts beyond it):\\n' + $('Parse Blog').first().json.blogSource + '\\n\\nNow produce ONLY the raw JSON for this channel as specified.'} ] }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2112,
        960
      ],
      "id": "linkedin-post",
      "name": "LinkedIn Post",
      "alwaysOutputData": true,
      "credentials": {
        "openRouterApi": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: OpenRouter account)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "numberInputs": 7
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2512,
        304
      ],
      "id": "merge-channels",
      "name": "Merge Channels"
    },
    {
      "parameters": {
        "jsCode": "\nconst idea = $('Parse Goal').first().json.idea;\nconst pb = $('Parse Blog').first().json || {};\nconst blog = pb.blog || {};\nconst diagramUrl = pb.diagramUrl || '';\nlet heroUrl = '';\ntry { const id = ($('Upload to Drive').first().json || {}).id; if (id) heroUrl = 'https://lh3.googleusercontent.com/d/' + id + '=w1600'; } catch (e) {}\n\nconst tagify = a => Array.isArray(a) ? a.map(h => (String(h)[0]==='#'?h:'#'+h)).join(' ') : '';\nconst parseNode = nm => { try { const r = $(nm).first().json; return JSON.parse(r && r.choices ? r.choices[0].message.content : '{}'); } catch (e) { return {}; } };\n\n// ---- Blog section (End-compatible markers: ## Blog, **title**, Caption:, CTA:) ----\n// dynamic narrative: writer-chosen section headings; the architecture diagram renders inside the\n// section the writer nominated via architecture.section_heading (fallback: after the middle section)\nconst arch = blog.architecture || {};\nconst bl = ['## Blog'];\nif (blog.title) bl.push('**' + blog.title + '**');\nif (heroUrl) bl.push('![hero](' + heroUrl + ')');\nif (blog.hook) bl.push(blog.hook);\nconst secs = Array.isArray(blog.sections) ? blog.sections : [];\nconst archHead = String(arch.section_heading || '').trim().toLowerCase();\nlet diagramPlaced = false;\nsecs.forEach((s, i) => {\n  if (!s || (!s.heading && !s.body)) return;\n  if (s.heading) bl.push('### ' + s.heading);\n  if (s.body) bl.push(s.body);\n  const isArchSec = archHead && String(s.heading || '').trim().toLowerCase() === archHead;\n  const fallbackSlot = !archHead && i === Math.min(secs.length - 1, Math.floor(secs.length / 2));\n  if (diagramUrl && !diagramPlaced && (isArchSec || fallbackSlot)) {\n    bl.push('![architecture](' + diagramUrl + ')'); diagramPlaced = true;\n  }\n});\nif (diagramUrl && !diagramPlaced) bl.push('![architecture](' + diagramUrl + ')');\nif (Array.isArray(blog.sources) && blog.sources.length) bl.push('Sources: ' + blog.sources.join(' | '));\nif (blog.meta_description) bl.push('Caption: ' + blog.meta_description);\nconst btags = tagify(blog.hashtags); if (btags) bl.push(btags);\nbl.push('CTA: Read the full post on the blog.');\n\nconst CH = [\n  { node:'Explainer Video', label:'Explainer Video',      video:true  },\n  { node:'Instagram Reel',  label:'Instagram Reel',       video:true  },\n  { node:'YouTube Reel',    label:'YouTube Reel',         video:true  },\n  { node:'Facebook Post',   label:'Facebook Post',        video:false },\n  { node:'X Post',          label:'X Post + Engagement',  video:false },\n  { node:'TikTok',          label:'TikTok Post',          video:true  },\n  { node:'LinkedIn Post',   label:'LinkedIn Post',        video:false },\n];\n\nconst titles = ['Blog'];\nconst sections = [bl.join('\\n\\n')];\nfor (const ch of CH) {\n  titles.push(ch.label);\n  const c = parseNode(ch.node);\n  const L = ['## ' + ch.label];\n  if (c.headline) L.push('**' + c.headline + '**');\n  if (ch.video) {\n    if (c.script) L.push('Script:\\n' + c.script);\n    const bp = Array.isArray(c.broll_prompts) ? c.broll_prompts : (c.broll_prompts ? [c.broll_prompts] : []);\n    if (bp.length) L.push('B-roll:\\n' + bp.map((p,i) => (i+1) + '. ' + p).join('\\n'));\n  } else {\n    if (c.body) L.push(c.body);\n  }\n  if (c.caption) L.push('Caption: ' + c.caption);\n  const t = tagify(c.hashtags); if (t) L.push(t);\n  if (c.cta) L.push('CTA: ' + c.cta);\n  sections.push(L.join('\\n\\n'));\n}\n\nconst contributionMd = '# Campaign plan\\n\\nIdea: ' + idea\n  + '\\n\\nThe blog is the source of truth; every channel below is derived from it. Review the scripts, then tick the '\n  + 'channels you want - assets are generated only for ticked channels.\\n\\n'\n  + sections.join('\\n\\n---\\n\\n');\n\nreturn [{ json: {\n  goalId: $('Parse Goal').first().json.goalId,\n  date: $('Parse Goal').first().json.date,\n  period: $('Parse Goal').first().json.period,\n  idea, blogTitle: blog.title || '', diagramUrl, heroUrl, titles, contributionMd\n}}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3600,
        304
      ],
      "id": "build-plan",
      "name": "Build Plan"
    },
    {
      "parameters": {
        "jsCode": "\nconst j = $('Build Plan').first().json;\nconst apiKey = $('API Key').first().json.apiKey;\nconst existing = new Set(($('Parse Goal').first().json.existingTitles) || []);\nconst MCP = 'https://aicivz8c3l.execute-api.ap-south-1.amazonaws.com/dev/mcp/call';\nconst Q = 'mutation AddSub($taskId: ID!, $date: String!, $period: String!, $body: String) ' +\n          '{ addSubTaskItem(taskId: $taskId, date: $date, period: $period, body: $body, isComplete: false) { id } }';\nconst results = [];\nfor (const title of (j.titles || [])) {\n  if (existing.has(title)) { results.push('skip:' + title); continue; }\n  try {\n    await this.helpers.httpRequest({ method:'POST', url:MCP,\n      headers:{ 'Content-Type':'application/json', 'X-API-Key':apiKey },\n      body:{ jsonrpc:'2.0', method:'tools/call', id:1, params:{ name:'graphql_query',\n        arguments:{ query:Q, variables:{ taskId:j.goalId, date:j.date, period:j.period, body:title } } } }, json:true });\n    results.push('ok:' + title);\n  } catch (e) { results.push('err:' + title); }\n  await new Promise(r => setTimeout(r, 400));\n}\nreturn [{ json: { created: results.filter(r => r.startsWith('ok')).length, results,\n  goalId: j.goalId, contributionMd: j.contributionMd } }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3808,
        304
      ],
      "id": "create-subtasks",
      "name": "Create Subtasks"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://aicivz8c3l.execute-api.ap-south-1.amazonaws.com/dev/mcp/call",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "X-API-Key",
              "value": "={{ $('API Key').item.json.apiKey }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\n  \"params\":{\"name\":\"graphql_query\",\"arguments\":{\n    \"query\":\"mutation SetC($id: ID!, $contribution: String) { updateGoalItemContribution(id: $id, contribution: $contribution) { id } }\",\n    \"variables\":{ \"id\":\"{{ $json.goalId }}\", \"contribution\": {{ JSON.stringify($json.contributionMd) }} }\n  }},\"id\":1\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        4000,
        304
      ],
      "id": "set-contribution",
      "name": "Set Contribution",
      "alwaysOutputData": true,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://sheets.googleapis.com/v4/spreadsheets/<<REPLACE_ME>>/values/Campaigns:append?valueInputOption=USER_ENTERED",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSheetsOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ values:[[ $('Parse Goal').first().json.date, $('Build Plan').first().json.goalId, $('Build Plan').first().json.blogTitle, ($('Build Plan').first().json.idea || '').slice(0,200), 9, 'planned', $('Build Plan').first().json.diagramUrl, $('Build Plan').first().json.heroUrl ]] }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        4208,
        304
      ],
      "id": "log-to-sheet",
      "name": "Log to Sheet",
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: grvpanchal Google Sheets)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "HTTP-Referer",
              "value": "https://workflows.grvpanchal.me"
            },
            {
              "name": "X-Title",
              "value": "CMO Campaign Engine"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ models:['~google/gemini-flash-latest'], reasoning:{enabled:false}, response_format:{type:'json_object'}, messages:[ {role:'system',content:\"You script a 20-45 second YouTube Short from the SOURCE BLOG. The first 2 seconds MUST be a pattern-interrupt hook; deliver ONE sharp insight anchored on a concrete number or named framework; end on a re-hook that invites a rewatch or comment. Return ONLY raw JSON with EXACTLY: headline, script, broll_prompts, caption, hashtags, cta. script = spoken words only (no labels/timestamps), tight and punchy. broll_prompts = an ORDERED array of 4-8 SHORT stock-footage SEARCH QUERIES (2-4 words, concrete literal searchable subjects like 'city skyline night' or 'hands typing keyboard'), in script order so the footage tracks the narration; plain searchable scenes ONLY, NO camera jargon, NO on-screen text, NO logos. caption = punchy caption. hashtags = 8-12 WITHOUT #. cta = 'Full breakdown on the blog - link in description'.\"}, {role:'user',content:'SOURCE BLOG (this is your single source of truth; do not invent facts beyond it):\\n' + $('Parse Blog').first().json.blogSource + '\\n\\nNow produce ONLY the raw JSON for this channel as specified.'} ] }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2112,
        240
      ],
      "id": "ytReelContent01",
      "name": "YouTube Reel",
      "alwaysOutputData": true,
      "credentials": {
        "openRouterApi": {
          "id": "<<REPLACE_ME>>",
          "name": "<<REPLACE_ME>> (was: OpenRouter account)"
        }
      },
      "onError": "continueRegularOutput"
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "API Key",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API Key": {
      "main": [
        [
          {
            "node": "Get Goal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Goal": {
      "main": [
        [
          {
            "node": "Parse Goal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Goal": {
      "main": [
        [
          {
            "node": "Research Metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Research Metrics": {
      "main": [
        [
          {
            "node": "Prep Research",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prep Research": {
      "main": [
        [
          {
            "node": "Generate Blog",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Blog": {
      "main": [
        [
          {
            "node": "Check Blog",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Blog": {
      "main": [
        [
          {
            "node": "Blog OK?",
            "type": "main",
            "index": 0
          },
          {
            "node": "Blog Fallback?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Blog OK?": {
      "main": [
        [
          {
            "node": "Parse Blog",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Blog Fallback?": {
      "main": [
        [
          {
            "node": "Generate Blog Paid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Blog Paid": {
      "main": [
        [
          {
            "node": "Parse Blog",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Blog": {
      "main": [
        [
          {
            "node": "Generate Hero Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Hero Image": {
      "main": [
        [
          {
            "node": "Prep Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prep Image": {
      "main": [
        [
          {
            "node": "Upload to Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload to Drive": {
      "main": [
        [
          {
            "node": "Make Public",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Make Public": {
      "main": [
        [
          {
            "node": "Explainer Video",
            "type": "main",
            "index": 0
          },
          {
            "node": "Instagram Reel",
            "type": "main",
            "index": 0
          },
          {
            "node": "YouTube Reel",
            "type": "main",
            "index": 0
          },
          {
            "node": "Facebook Post",
            "type": "main",
            "index": 0
          },
          {
            "node": "X Post",
            "type": "main",
            "index": 0
          },
          {
            "node": "TikTok",
            "type": "main",
            "index": 0
          },
          {
            "node": "LinkedIn Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Explainer Video": {
      "main": [
        [
          {
            "node": "Merge Channels",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Instagram Reel": {
      "main": [
        [
          {
            "node": "Merge Channels",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Facebook Post": {
      "main": [
        [
          {
            "node": "Merge Channels",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "X Post": {
      "main": [
        [
          {
            "node": "Merge Channels",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "TikTok": {
      "main": [
        [
          {
            "node": "Merge Channels",
            "type": "main",
            "index": 5
          }
        ]
      ]
    },
    "LinkedIn Post": {
      "main": [
        [
          {
            "node": "Merge Channels",
            "type": "main",
            "index": 6
          }
        ]
      ]
    },
    "Merge Channels": {
      "main": [
        [
          {
            "node": "Build Plan",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Plan": {
      "main": [
        [
          {
            "node": "Create Subtasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Subtasks": {
      "main": [
        [
          {
            "node": "Set Contribution",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Contribution": {
      "main": [
        [
          {
            "node": "Log to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Sheet": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "YouTube Reel": {
      "main": [
        [
          {
            "node": "Merge Channels",
            "type": "main",
            "index": 2
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "executionTimeout": 600,
    "binaryMode": "separate"
  },
  "staticData": null,
  "pinData": {},
  "tags": [
    {
      "updatedAt": "2026-05-31T13:28:53.307Z",
      "createdAt": "2026-05-31T13:28:53.307Z",
      "id": "78AORzdGMsn6jsp8",
      "name": "phase:start"
    },
    {
      "updatedAt": "2026-05-31T13:28:55.437Z",
      "createdAt": "2026-05-31T13:28:55.437Z",
      "id": "RHxuUFIbX5MpXjYc",
      "name": "pillar:cmo"
    },
    {
      "updatedAt": "2026-05-31T13:28:52.809Z",
      "createdAt": "2026-05-31T13:28:52.809Z",
      "id": "kLQMuII6E7BC8apU",
      "name": "evolution"
    }
  ]
}