{
  "name": "TMH \u2014 Auto Blog Publisher",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        208,
        96
      ],
      "id": "4a675d1e-c494-49c5-a7c2-52bef6e3c910",
      "name": "Every 6 Hours"
    },
    {
      "parameters": {
        "url": "=https://api.github.com/repos/NJacobs415/manageher-makeover/contents/public/blog/{{ $json.slug }}.json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        880,
        0
      ],
      "id": "09f1ab8e-50be-49a6-a5b1-a54a10ab7f98",
      "name": "Check If Blog Exists",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        },
        "githubApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "check-404",
              "leftValue": "={{ $json.message }}",
              "rightValue": "Not Found",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1104,
        0
      ],
      "id": "1361d2bd-6085-4134-8cf4-1a93f93023f7",
      "name": "Is New Episode?"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://www.youtube-transcript.io/api/transcripts",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={\"ids\":[\"{{ $(\"Code in JavaScript\").item.json.id }}\"]}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        1328,
        0
      ],
      "id": "88e5aca6-484c-417b-ac29-4806578b4106",
      "name": "Fetch Transcript",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "// Combine transcript segments into clean text\nconst episode = $('Code in JavaScript').item.json;\nlet transcript = '';\n\ntry {\n  const transcriptData = $input.first().json;\n  \n  // Handle different transcript response formats\n  if (transcriptData.transcripts) {\n    // youtube-transcript.io format\n    transcript = transcriptData.transcripts\n      .map(t => t.text || t.snippet || '')\n      .join(' ')\n      .replace(/\\s+/g, ' ')\n      .trim();\n  } else if (transcriptData.transcript) {\n    // Alternative format\n    if (Array.isArray(transcriptData.transcript)) {\n      transcript = transcriptData.transcript\n        .map(t => t.text || t.snippet || '')\n        .join(' ')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    } else {\n      transcript = transcriptData.transcript;\n    }\n  } else if (Array.isArray(transcriptData)) {\n    transcript = transcriptData\n      .map(t => t.text || t.snippet || '')\n      .join(' ')\n      .replace(/\\s+/g, ' ')\n      .trim();\n  }\n} catch(e) {\n  // If transcript fetch failed, use the video description as fallback\n  transcript = episode.description || 'Transcript not available.';\n}\n\n// Truncate if too long (Claude has context limits)\nif (transcript.length > 80000) {\n  transcript = transcript.substring(0, 80000) + '... [transcript truncated]';\n}\n\nreturn [{ json: { ...episode, transcript } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1552,
        0
      ],
      "id": "cb44f4db-ffea-4670-9a4d-9c65f6cd94d5",
      "name": "Clean Transcript"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ $json.claudeRequestBody }}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2000,
        0
      ],
      "id": "d287c296-2d66-427f-b164-2a56f5fc6d17",
      "name": "Claude: Generate Blog Post",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Parse Claude's tool_use response. Anthropic guarantees the input\n// object matches the tool's input_schema, so JSON parse failures from\n// unescaped quotes in free-text JSON (the original bug) are impossible.\nconst episode = $('Clean Transcript').item.json;\nconst claudeResponse = $input.first().json;\n\nif (!Array.isArray(claudeResponse.content)) {\n  throw new Error('Claude response missing content array: ' + JSON.stringify(claudeResponse).slice(0, 400));\n}\nconst toolUse = claudeResponse.content.find(b => b.type === 'tool_use' && b.name === 'generate_blog_post');\nif (!toolUse) {\n  throw new Error('Claude did not return a generate_blog_post tool_use block. stop_reason=' +\n    (claudeResponse.stop_reason || 'unknown') + ' content_types=' +\n    JSON.stringify(claudeResponse.content.map(b => b.type)));\n}\nconst blogData = toolUse.input;\nif (!blogData || typeof blogData !== 'object') {\n  throw new Error('tool_use.input is not an object: ' + JSON.stringify(toolUse).slice(0, 400));\n}\n\n// Build the complete blog post object (same shape as before).\nconst post = {\n  slug: episode.slug,\n  title: blogData.title || episode.title,\n  episodeNumber: blogData.episodeNumber || 0,\n  guestName: blogData.guestName || '',\n  guestBio: blogData.guestBio || '',\n  publishedAt: episode.publishedAt,\n  duration: episode.durationFormatted,\n  thumbnail: episode.thumbnail,\n  youtubeUrl: episode.youtubeUrl,\n  spotifyUrl: 'https://open.spotify.com/show/03FuFRyzkaWhZkk5yxFePJ',\n  appleUrl: 'https://podcasts.apple.com/us/podcast/the-manage-her/id1809208475',\n  excerpt: blogData.excerpt || '',\n  metaDescription: blogData.metaDescription || '',\n  topics: blogData.topics || [],\n  keyTakeaways: blogData.keyTakeaways || [],\n  guestLinks: blogData.guestLinks || [],\n  pullQuotes: blogData.pullQuotes || [],\n  timestamps: blogData.timestamps || [],\n  quiz: blogData.quiz || null,\n  transcript: $('Clean Transcript').first().json.transcript || '',\n  content: blogData.content || ''\n};\n\nconst indexEntry = {\n  slug: post.slug,\n  title: post.title,\n  episodeNumber: post.episodeNumber,\n  guestName: post.guestName,\n  publishedAt: post.publishedAt,\n  duration: post.duration,\n  thumbnail: post.thumbnail,\n  excerpt: post.excerpt,\n  topics: post.topics,\n  youtubeUrl: post.youtubeUrl\n};\n\nreturn [{ json: { post, indexEntry, slug: episode.slug } }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2224,
        0
      ],
      "id": "e1aeff75-6570-4a4f-98c1-c0fcaf81deeb",
      "name": "Build Blog JSON"
    },
    {
      "parameters": {
        "content": "## TMH Auto Blog Publisher\n\n### FLOW:\n1. Fetches 5 most recent medium-length videos\n2. Filters to full episodes (30+ min)\n3. Checks if blog already exists in GitHub\n4. If new: fetches transcript\n5. Sends to Claude API for blog generation\n6. Commits blog post JSON to GitHub\n7. Updates posts.json index\n8. Cloudflare Pages auto-deploys\n\n### SETUP:\n1. Add GitHub PAT credential (Header Auth)\n2. Add Anthropic API credential\n3. Update credential IDs in nodes\n4. Test with manual trigger first\n\n### TRANSCRIPT:\nUses free transcript API.\nFalls back to video description if unavailable."
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "id": "d9daf4b9-8edd-46f1-83e6-b26edfc0222a",
      "name": "Instructions"
    },
    {
      "parameters": {
        "url": "https://n8n.srv1075406.hstgr.cloud/webhook/tmh-youtube-episodes",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        432,
        0
      ],
      "id": "5c57dcdf-99d3-4dac-8fee-b3755efc5c25",
      "name": "HTTP Request"
    },
    {
      "parameters": {
        "jsCode": "// Process episodes from the webhook response\nconst episodes = $input.first().json.episodes || [];\n\nfunction makeSlug(title) {\n  return title\n    .toLowerCase()\n    .replace(/^ep\\.?\\s*\\d+\\s*:?\\s*/i, '')\n    .replace(/\\|.*$/g, '')\n    .replace(/the manage her:?\\s*/gi, '')\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-+|-+$/g, '')\n    .substring(0, 120);\n}\n\nfunction formatDuration(iso) {\n  const match = (iso || '').match(/PT(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?/);\n  if (!match) return '30 min';\n  const h = parseInt(match[1]||0);\n  const m = parseInt(match[2]||0);\n  if (h > 0) return `${h}h ${m}m`;\n  return `${m} min`;\n}\n\n// Filter out YouTube Shorts (under 120 seconds)\nconst filtered = episodes.filter(ep => {\n  const match = (ep.duration || '').match(/PT(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?/);\n  if (!match) return true;\n  const totalSeconds = (parseInt(match[1]||0) * 3600) + (parseInt(match[2]||0) * 60) + parseInt(match[3]||0);\n  return totalSeconds >= 120;\n});\n\n// Return each episode as a separate item\nreturn filtered.slice(0, 5).map(ep => ({\n  json: {\n    id: ep.id,\n    title: ep.title,\n    slug: makeSlug(ep.title),\n    description: ep.description,\n    thumbnail: ep.thumbnailMax || ep.thumbnail,\n    publishedAt: ep.publishedAt,\n    duration: ep.duration,\n    durationFormatted: formatDuration(ep.duration),\n    youtubeUrl: `https://www.youtube.com/watch?v=${ep.id}`\n  }\n}));\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        656,
        0
      ],
      "id": "0688b309-1c14-4e9d-87ae-4c4b8a2a24a9",
      "name": "Code in JavaScript"
    },
    {
      "parameters": {
        "jsCode": "const episode = $input.first().json;\n\nconst prompt = `You are a blog content writer for The Manage Her\u00ae \u2014 a women's leadership podcast hosted by Aimee Rickabus, CEO of a nine-figure technology company, bestselling author, and mother of six. Generate an enhanced show notes blog post from this podcast episode by calling the generate_blog_post tool.\n\nEPISODE DETAILS:\nTitle: ${episode.title}\nDuration: ${episode.durationFormatted}\nPublished: ${episode.publishedAt}\nYouTube URL: ${episode.youtubeUrl}\n\nEPISODE DESCRIPTION:\n${(episode.description || '').substring(0, 8000)}\n\nTRANSCRIPT:\n${(episode.transcript || '').substring(0, 50000)}\n\nWRITING GUIDANCE:\n- title: SEO-friendly, under 80 characters, different from the episode title\n- episodeNumber: extract from the title or use 0 if absent\n- guestName: full name or empty string for solo episodes\n- guestBio: 2-3 sentence bio based on what's discussed\n- excerpt: 2-3 sentence summary that makes someone want to listen\n- metaDescription: SEO meta description under 160 characters\n- topics: 3-5 from the enum (Leadership, Motherhood, Financial Literacy, Wellness, Entrepreneurship, Boundaries, Identity, Marriage, Community, Spirituality)\n- keyTakeaways: 5-7 complete-sentence takeaways\n- guestLinks: extract Instagram/LinkedIn/TikTok/YouTube/website URLs from the description\n- pullQuotes: 3-5 memorable quotes; timestamp empty if unknown\n- timestamps: chapter markers from the transcript/description\n- content: 800-1200 words of HTML using p/strong/em/h3 tags. Brand voice: bold, warm, direct, permission-giving. Address reader as \"you\". No bullet lists.\n- quiz: a SELF-DISCOVERY quiz (not knowledge test) with empowering archetype names tied to the episode. types.A/B/C/D each have a name (2-4 word archetype) and a 3-4 sentence personalized description in second person ending with why they should listen. questions: EXACTLY 6 questions, each with EXACTLY 4 options. Each option's type is A/B/C/D. All options should feel valid; this is self-discovery, not right/wrong.`;\n\nconst tool = {\n  name: \"generate_blog_post\",\n  description: \"Generate the structured blog post for this episode.\",\n  input_schema: {\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"title\",\"episodeNumber\",\"guestName\",\"excerpt\",\"metaDescription\",\"topics\",\"keyTakeaways\",\"content\",\"quiz\"],\"properties\":{\"title\":{\"type\":\"string\",\"maxLength\":100},\"episodeNumber\":{\"type\":\"integer\",\"minimum\":0},\"guestName\":{\"type\":\"string\"},\"guestBio\":{\"type\":\"string\"},\"excerpt\":{\"type\":\"string\",\"maxLength\":400},\"metaDescription\":{\"type\":\"string\",\"maxLength\":200},\"topics\":{\"type\":\"array\",\"minItems\":1,\"maxItems\":5,\"items\":{\"type\":\"string\",\"enum\":[\"Leadership\",\"Motherhood\",\"Financial Literacy\",\"Wellness\",\"Entrepreneurship\",\"Boundaries\",\"Identity\",\"Marriage\",\"Community\",\"Spirituality\"]}},\"keyTakeaways\":{\"type\":\"array\",\"minItems\":3,\"maxItems\":8,\"items\":{\"type\":\"string\"}},\"guestLinks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"label\",\"url\"],\"properties\":{\"label\":{\"type\":\"string\"},\"url\":{\"type\":\"string\"}}}},\"pullQuotes\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"text\"],\"properties\":{\"text\":{\"type\":\"string\"},\"timestamp\":{\"type\":\"string\"}}}},\"timestamps\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"time\",\"label\"],\"properties\":{\"time\":{\"type\":\"string\"},\"label\":{\"type\":\"string\"}}}},\"content\":{\"type\":\"string\"},\"quiz\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"title\",\"description\",\"types\",\"questions\"],\"properties\":{\"title\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"},\"types\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"A\",\"B\",\"C\",\"D\"],\"properties\":{\"A\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"name\",\"description\"],\"properties\":{\"name\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"}}},\"B\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"name\",\"description\"],\"properties\":{\"name\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"}}},\"C\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"name\",\"description\"],\"properties\":{\"name\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"}}},\"D\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"name\",\"description\"],\"properties\":{\"name\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"}}}}},\"questions\":{\"type\":\"array\",\"minItems\":6,\"maxItems\":6,\"items\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"question\",\"options\"],\"properties\":{\"question\":{\"type\":\"string\"},\"options\":{\"type\":\"array\",\"minItems\":4,\"maxItems\":4,\"items\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"text\",\"type\"],\"properties\":{\"text\":{\"type\":\"string\"},\"type\":{\"type\":\"string\",\"enum\":[\"A\",\"B\",\"C\",\"D\"]}}}}}}}}}}}\n};\n\nconst requestBody = {\n  model: \"claude-sonnet-4-6\",\n  max_tokens: 10000,\n  tools: [tool],\n  tool_choice: { type: \"tool\", name: \"generate_blog_post\" },\n  messages: [{ role: \"user\", content: prompt }]\n};\n\nreturn [{ json: { ...episode, claudeRequestBody: JSON.stringify(requestBody) } }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1776,
        0
      ],
      "id": "7b164a58-19eb-4713-bbef-dd673f50095d",
      "name": "Build Claude Request"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.indexnow.org/indexnow",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"host\": \"themanageher.com\",\n  \"key\": \"a1b2c3d4e5f6g7h8\",\n  \"keyLocation\": \"https://themanageher.com/a1b2c3d4e5f6g7h8.txt\",\n  \"urlList\": [\"https://themanageher.com/blog/{{ $('Build Blog JSON').item.json.slug }}\"]\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        4464,
        0
      ],
      "id": "indexnow-ping-001",
      "name": "Ping IndexNow",
      "onError": "continueRegularOutput",
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "url": "=https://services.leadconnectorhq.com/contacts/?locationId=JzYUXEAehZEve2vuOdqM&query={{ encodeURIComponent($('Build Blog JSON').item.json.post.guestName.replace(/^(Dr\\.?|Chef|Coach|Pastor|Rev\\.?|Professor|Prof\\.?)\\s+/i, '')) }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        4688,
        0
      ],
      "id": "ghl-lookup-guest-001",
      "name": "Lookup Guest by Name",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "tags-include-sent",
              "leftValue": "={{ ($json.contacts && $json.contacts[0] && $json.contacts[0].tags) ? $json.contacts[0].tags : [] }}",
              "rightValue": "guest-outreach-sent",
              "operator": {
                "type": "array",
                "operation": "contains",
                "rightType": "any"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4912,
        0
      ],
      "id": "ghl-is-sent-001",
      "name": "Is Already Sent?"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "has-contact-id",
              "leftValue": "={{ ($('Lookup Guest by Name').item.json.contacts || []).length }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5136,
        0
      ],
      "id": "ghl-has-existing-001",
      "name": "Has Existing Contact?"
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Lookup Guest by Name').item.json.contacts[0].id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"customFields\": [\n    {\"id\": \"RyA70UVI5yUY83Wu5hYz\", \"field_value\": \"{{ $('Build Blog JSON').item.json.post.title }}\"},\n    {\"id\": \"zRU07yCeCsSBQrkNZwdb\", \"field_value\": \"https://themanageher.com/blog/{{ $('Build Blog JSON').item.json.slug }}\"},\n    {\"id\": \"lctcR3WExxz0pULDyokA\", \"field_value\": \"{{ $('Build Blog JSON').item.json.post.episodeNumber }}\"},\n    {\"id\": \"HsP54mVLCSvKjPv2sGyO\", \"field_value\": \"{{ $('Build Blog JSON').item.json.post.quiz ? $('Build Blog JSON').item.json.post.quiz.title : '' }}\"},\n    {\"id\": \"KX3Tvi2pvlrUCtlVhYDI\", \"field_value\": \"{{ new Date($('Build Blog JSON').item.json.post.publishedAt).toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' }) }}\"}\n  ]\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        5360,
        -96
      ],
      "id": "ghl-update-existing-001",
      "name": "Update Existing Contact",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Lookup Guest by Name').item.json.contacts[0].id }}/tags",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"tags\": [\"podcast-guest\", \"guest-outreach-pending\"]\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        5584,
        -96
      ],
      "id": "ghl-add-tags-001",
      "name": "Add Tags to Existing",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://services.leadconnectorhq.com/contacts/",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"locationId\": \"JzYUXEAehZEve2vuOdqM\",\n  \"name\": \"{{ $('Build Blog JSON').item.json.post.guestName.replace(/^(Dr\\.?|Chef|Coach|Pastor|Rev\\.?|Professor|Prof\\.?)\\s+/i, '') }}\",\n  \"firstName\": \"{{ $('Build Blog JSON').item.json.post.guestName.replace(/^(Dr\\.?|Chef|Coach|Pastor|Rev\\.?|Professor|Prof\\.?)\\s+/i, '').split(' ')[0] }}\",\n  \"lastName\": \"{{ $('Build Blog JSON').item.json.post.guestName.replace(/^(Dr\\.?|Chef|Coach|Pastor|Rev\\.?|Professor|Prof\\.?)\\s+/i, '').split(' ').slice(1).join(' ') }}\",\n  \"tags\": [\"podcast-guest\", \"guest-outreach-pending\"],\n  \"customFields\": [\n    {\"id\": \"RyA70UVI5yUY83Wu5hYz\", \"field_value\": \"{{ $('Build Blog JSON').item.json.post.title }}\"},\n    {\"id\": \"zRU07yCeCsSBQrkNZwdb\", \"field_value\": \"https://themanageher.com/blog/{{ $('Build Blog JSON').item.json.slug }}\"},\n    {\"id\": \"lctcR3WExxz0pULDyokA\", \"field_value\": \"{{ $('Build Blog JSON').item.json.post.episodeNumber }}\"},\n    {\"id\": \"HsP54mVLCSvKjPv2sGyO\", \"field_value\": \"{{ $('Build Blog JSON').item.json.post.quiz ? $('Build Blog JSON').item.json.post.quiz.title : '' }}\"},\n    {\"id\": \"KX3Tvi2pvlrUCtlVhYDI\", \"field_value\": \"{{ new Date($('Build Blog JSON').item.json.post.publishedAt).toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' }) }}\"}\n  ]\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        5360,
        96
      ],
      "id": "ghl-create-new-001",
      "name": "Create New Contact",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "https://api.github.com/repos/NJacobs415/manageher-makeover/git/refs/heads/main",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2448,
        0
      ],
      "id": "a000aaaa-0001-0001-0001-000000000001",
      "name": "GH: Get HEAD Ref",
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "=https://api.github.com/repos/NJacobs415/manageher-makeover/git/commits/{{ $json.object.sha }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2672,
        0
      ],
      "id": "a000aaaa-0002-0002-0002-000000000002",
      "name": "GH: Get HEAD Commit",
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "https://api.github.com/repos/NJacobs415/manageher-makeover/contents/public/blog/posts.json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2896,
        0
      ],
      "id": "a000aaaa-0003-0003-0003-000000000003",
      "name": "GH: Get posts.json (or 404)",
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Strict 200/404 status-code split per atomic-commit design (v2).\n// fullResponse:true on the previous node gives us {statusCode, body, headers}.\n// Any status other than 200 or 404 throws \u2014 we do NOT silently\n// reset posts.json to an empty index on transient/auth errors.\nconst buildOut = $node[\"Build Blog JSON\"].json;\nconst headRef  = $node[\"GH: Get HEAD Ref\"].json;\nconst headCommit = $node[\"GH: Get HEAD Commit\"].json;\nconst env = $input.first().json;\n\nif (!headRef?.object?.sha) throw new Error('GH: Get HEAD Ref returned no object.sha');\nif (!headCommit?.tree?.sha) throw new Error('GH: Get HEAD Commit returned no tree.sha');\n\nconst status = env?.statusCode;\nif (typeof status !== 'number') {\n  throw new Error('posts.json fetch: missing statusCode (fullResponse not enabled?). Got: ' + JSON.stringify(env).slice(0,200));\n}\n\nlet existingPosts = [];\nif (status === 200) {\n  const body = env.body;\n  if (!body || typeof body.content !== 'string') {\n    throw new Error('200 from posts.json fetch but body.content missing or not a string.');\n  }\n  try {\n    const decoded = Buffer.from(body.content, 'base64').toString('utf-8');\n    const parsed = JSON.parse(decoded);\n    existingPosts = Array.isArray(parsed.posts) ? parsed.posts : [];\n  } catch(e) {\n    throw new Error('Failed to decode/parse current posts.json: ' + e.message);\n  }\n} else if (status === 404) {\n  // Bootstrap empty index \u2014 only valid path for an empty repo.\n  existingPosts = [];\n} else {\n  throw new Error('Refusing to mutate posts.json \u2014 unexpected status ' + status + ' from contents API: ' + JSON.stringify(env.body || env).slice(0,300));\n}\n\nconst newEntry = buildOut.indexEntry;\nconst merged = [newEntry, ...existingPosts.filter(p => p.slug !== newEntry.slug)];\nconst indexJson = JSON.stringify({ posts: merged }, null, 2);\nconst postJson  = JSON.stringify(buildOut.post, null, 2);\n\nreturn [{\n  json: {\n    headSha: headRef.object.sha,\n    treeSha: headCommit.tree.sha,\n    slug: buildOut.slug,\n    title: buildOut.post.title,\n    postPath: 'public/blog/' + buildOut.slug + '.json',\n    indexPath: 'public/blog/posts.json',\n    postBase64: Buffer.from(postJson).toString('base64'),\n    indexBase64: Buffer.from(indexJson).toString('base64'),\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3120,
        0
      ],
      "id": "a000aaaa-0004-0004-0004-000000000004",
      "name": "Merge Index + Encode Blobs"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.github.com/repos/NJacobs415/manageher-makeover/git/blobs",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"content\": \"{{ $json.postBase64 }}\",\n  \"encoding\": \"base64\"\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        3344,
        0
      ],
      "id": "a000aaaa-0005-0005-0005-000000000005",
      "name": "GH: Create Post Blob",
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.github.com/repos/NJacobs415/manageher-makeover/git/blobs",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"content\": \"{{ $node[\"Merge Index + Encode Blobs\"].json.indexBase64 }}\",\n  \"encoding\": \"base64\"\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        3568,
        0
      ],
      "id": "a000aaaa-0006-0006-0006-000000000006",
      "name": "GH: Create Index Blob",
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.github.com/repos/NJacobs415/manageher-makeover/git/trees",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"base_tree\": \"{{ $node[\"Merge Index + Encode Blobs\"].json.treeSha }}\",\n  \"tree\": [\n    {\n      \"path\": \"{{ $node[\"Merge Index + Encode Blobs\"].json.postPath }}\",\n      \"mode\": \"100644\",\n      \"type\": \"blob\",\n      \"sha\": \"{{ $node[\"GH: Create Post Blob\"].json.sha }}\"\n    },\n    {\n      \"path\": \"{{ $node[\"Merge Index + Encode Blobs\"].json.indexPath }}\",\n      \"mode\": \"100644\",\n      \"type\": \"blob\",\n      \"sha\": \"{{ $node[\"GH: Create Index Blob\"].json.sha }}\"\n    }\n  ]\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        3792,
        0
      ],
      "id": "a000aaaa-0007-0007-0007-000000000007",
      "name": "GH: Create Tree",
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.github.com/repos/NJacobs415/manageher-makeover/git/commits",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"message\": \"Auto-publish blog: {{ $node[\"Merge Index + Encode Blobs\"].json.title }}\",\n  \"tree\": \"{{ $json.sha }}\",\n  \"parents\": [\"{{ $node[\"Merge Index + Encode Blobs\"].json.headSha }}\"]\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        4016,
        0
      ],
      "id": "a000aaaa-0008-0008-0008-000000000008",
      "name": "GH: Create Commit",
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "https://api.github.com/repos/NJacobs415/manageher-makeover/git/refs/heads/main",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"sha\": \"{{ $json.sha }}\"\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        4240,
        0
      ],
      "id": "a000aaaa-0009-0009-0009-000000000009",
      "name": "GH: Update Ref (atomic)",
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Every 6 Hours": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check If Blog Exists": {
      "main": [
        [
          {
            "node": "Is New Episode?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is New Episode?": {
      "main": [
        [
          {
            "node": "Fetch Transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean Transcript": {
      "main": [
        [
          {
            "node": "Build Claude Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude: Generate Blog Post": {
      "main": [
        [
          {
            "node": "Build Blog JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Blog JSON": {
      "main": [
        [
          {
            "node": "GH: Get HEAD Ref",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Check If Blog Exists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Claude Request": {
      "main": [
        [
          {
            "node": "Claude: Generate Blog Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ping IndexNow": {
      "main": [
        [
          {
            "node": "Lookup Guest by Name",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lookup Guest by Name": {
      "main": [
        [
          {
            "node": "Is Already Sent?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Already Sent?": {
      "main": [
        [],
        [
          {
            "node": "Has Existing Contact?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Existing Contact?": {
      "main": [
        [
          {
            "node": "Update Existing Contact",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create New Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Existing Contact": {
      "main": [
        [
          {
            "node": "Add Tags to Existing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Transcript": {
      "main": [
        [
          {
            "node": "Clean Transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GH: Get HEAD Ref": {
      "main": [
        [
          {
            "node": "GH: Get HEAD Commit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GH: Get HEAD Commit": {
      "main": [
        [
          {
            "node": "GH: Get posts.json (or 404)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GH: Get posts.json (or 404)": {
      "main": [
        [
          {
            "node": "Merge Index + Encode Blobs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Index + Encode Blobs": {
      "main": [
        [
          {
            "node": "GH: Create Post Blob",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GH: Create Post Blob": {
      "main": [
        [
          {
            "node": "GH: Create Index Blob",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GH: Create Index Blob": {
      "main": [
        [
          {
            "node": "GH: Create Tree",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GH: Create Tree": {
      "main": [
        [
          {
            "node": "GH: Create Commit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GH: Create Commit": {
      "main": [
        [
          {
            "node": "GH: Update Ref (atomic)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GH: Update Ref (atomic)": {
      "main": [
        [
          {
            "node": "Ping IndexNow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": true
  },
  "staticData": {
    "node:Every 6 Hours": {
      "recurrenceRules": [
        18
      ]
    }
  }
}