{
  "name": "API Documentation \u2014 Generator",
  "tags": [],
  "nodes": [
    {
      "id": "8655873a-3376-45e4-8311-68a646570e47",
      "name": "Extract from File2",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -208,
        544
      ],
      "parameters": {
        "options": {},
        "operation": "text"
      },
      "typeVersion": 1.1
    },
    {
      "id": "a0edb4ab-f3e6-4ff0-be75-a7ef3d6bfb39",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -1520,
        448
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "75a5f62e-1479-4845-84ce-283291ad65a5",
      "name": "Sticky Note \u2014 Step 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1520,
        256
      ],
      "parameters": {
        "color": 4,
        "width": 560,
        "height": 344,
        "content": "### \ud83d\udcc1 Step 1 \u2014 Load Files\nTrigger manually, then fetch all files from the configured Google Drive folder and filter down to `.h` files only.\n\n\u2699\ufe0f **To configure:** Open **Get all files from folder** and update the `queryString` with your own Google Drive folder ID."
      },
      "typeVersion": 1
    },
    {
      "id": "046dba14-4d05-491a-a311-2446963de7dd",
      "name": "Sticky Note \u2014 Step 8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -848,
        256
      ],
      "parameters": {
        "width": 252,
        "height": 424,
        "content": "### \ud83d\udd01 Step 2 \u2014 Loop\nProcess one `.h` file at a time using **Split in Batches**.\n\nThe loop returns here after each file is saved to Drive, until all files are processed. When done, it branches to the email notification."
      },
      "typeVersion": 1
    },
    {
      "id": "77d98362-d5c4-4be3-967d-da5fc59d573b",
      "name": "Sticky Note \u2014 Step 9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -464,
        416
      ],
      "parameters": {
        "width": 372,
        "height": 280,
        "content": "### \ud83d\udce5 Step 3 \u2014 Download & Read\nDownloads the binary file from Google Drive and extracts its raw text content, ready to be sent to GPT-4o."
      },
      "typeVersion": 1
    },
    {
      "id": "6493ac89-3f40-45eb-822f-988d82f4a570",
      "name": "Sticky Note \u2014 Step 10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        256
      ],
      "parameters": {
        "color": 2,
        "width": 248,
        "height": 392,
        "content": "### \ud83e\udd16 Step 4 \u2014 AI Extraction\nSends the raw `.h` file content to **GPT-4o** with a structured prompt.\n\nGPT returns a strict JSON object with:\n`overview` \u00b7 `functions` \u00b7 `enumerators` \u00b7 `data_types` \u00b7 `constants` \u00b7 `notes`\n\n\u26a0\ufe0f Requires an **OpenAI credential** with GPT-4o access."
      },
      "typeVersion": 1
    },
    {
      "id": "11cab0db-26db-4a64-ae73-c7601eefdf75",
      "name": "Sticky Note \u2014 Step 11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        320
      ],
      "parameters": {
        "width": 224,
        "height": 360,
        "content": "### \ud83d\udd27 Step 5 \u2014 Parse Response\nStrips any markdown fences from the GPT response and parses it as JSON.\n\n\ud83d\udca1 If parsing fails, the raw GPT text is placed in `overview` so you can debug what GPT returned."
      },
      "typeVersion": 1
    },
    {
      "id": "f248ab44-1130-49c1-90f2-ec65567cc480",
      "name": "Sticky Note \u2014 Step 12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        224
      ],
      "parameters": {
        "color": 5,
        "width": 244,
        "height": 472,
        "content": "### \ud83c\udfa8 Step 6 \u2014 Generate HTML\nBuilds a fully styled HTML documentation file with:\n- Sidebar navigation\n- Hero header with metadata\n- Function cards with signature, params table & examples\n- Data types, enums, constants sections\n\nOutputs as a **binary `.html` file** ready for Google Drive upload."
      },
      "typeVersion": 1
    },
    {
      "id": "ebb3f0fc-5d41-4022-a8d2-58b7b951c215",
      "name": "Sticky Note \u2014 Step 13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        848,
        224
      ],
      "parameters": {
        "color": 4,
        "width": 252,
        "height": 472,
        "content": "### \ud83d\udcbe Step 7 \u2014 Save to Drive\nUploads the generated `DOC_filename.html` file to the configured output folder in Google Drive.\n\n\u2699\ufe0f **To configure:** Update the `folderId` in this node with your output folder ID."
      },
      "typeVersion": 1
    },
    {
      "id": "0410927e-234a-46f5-aca6-a9a60eeb56d0",
      "name": "Sticky Note \u2014 Email1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -480,
        -32
      ],
      "parameters": {
        "color": 2,
        "width": 440,
        "height": 340,
        "content": "### \u2709\ufe0f Completion Email\nOnce all files are processed, the **Split in Batches** node exits the loop and triggers this branch.\n\nBuilds a summary email and sends it via **Gmail** to notify that all docs have been generated.\n\n\u2699\ufe0f Update the recipient email in **Send completion email**."
      },
      "typeVersion": 1
    },
    {
      "id": "240eb434-776f-4195-a456-15a83b8f5d5a",
      "name": "Sticky Note \u2014 Overview1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1968,
        80
      ],
      "parameters": {
        "color": 5,
        "width": 396,
        "height": 678,
        "content": "# \ud83d\udcc4 API Documentation Generator\n\nAutomatically generates **beautiful HTML documentation** from C header (`.h`) files stored in Google Drive.\n\n**How it works:**\n1. Reads all files from a specified Google Drive folder\n2. Filters to `.h` header files only\n3. Processes each file one at a time through GPT-4o\n4. GPT extracts functions, types, enums, constants & notes as structured JSON\n5. Generates a professional HTML doc and saves it back to Google Drive\n6. Sends a completion email when all files are processed\n\n**Requirements:**\n- Google Drive OAuth2 credentials\n- OpenAI API credentials\n- Gmail credentials\n- Update the Google Drive folder ID in the **Get all files** node"
      },
      "typeVersion": 1
    },
    {
      "id": "f63de4ea-6d88-4f10-a195-feb2f7b5f3ea",
      "name": "Get all files from folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -1328,
        448
      ],
      "parameters": {
        "filter": {},
        "options": {},
        "resource": "fileFolder",
        "returnAll": true,
        "queryString": "'YOUR_FOLDER_ID' in parents and trashed=false",
        "searchMethod": "query"
      },
      "typeVersion": 3
    },
    {
      "id": "14c89229-0249-4e47-a46c-1480e961b59c",
      "name": "Filter .h files only",
      "type": "n8n-nodes-base.code",
      "position": [
        -1104,
        448
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst results = [];\nfor (const item of items) {\n  const name = (item.json.name || '').toLowerCase();\n  if (name.endsWith('.h')) {\n    results.push({ json: { id: item.json.id, fileName: item.json.name } });\n  }\n}\nif (results.length === 0) throw new Error('No .h header files found.');\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "b2362399-2709-42d6-a294-5d834562a4b7",
      "name": "Process one file at a time",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -768,
        528
      ],
      "parameters": {
        "options": {}
      },
      "executeOnce": false,
      "typeVersion": 3
    },
    {
      "id": "e74de2b2-e2bf-4e9f-b191-e703a914cb15",
      "name": "Download file",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -432,
        544
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "",
          "value": "={{ $json.id }}"
        },
        "options": {},
        "operation": "download"
      },
      "typeVersion": 3
    },
    {
      "id": "477c9a71-e6b8-4125-8dca-9987320fda1a",
      "name": "Build email body",
      "type": "n8n-nodes-base.code",
      "position": [
        -400,
        160
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nfor (const item of items) {\n  const fileName = item.json.pdfName || item.json.name || 'DOC.pdf';\n  item.json.subject = 'API Documentation PDF Ready \u2014 XYZ Technologies';\n  item.json.message = `Hello,\n\nYour C header file API documentation PDF has been generated by XYZ Technologies.\n\nFile: ${fileName}\nUploaded to: Output folder in Google Drive\nGenerated: ${new Date().toUTCString()}\n\nThe PDF includes:\n  - Overview\n  - Functions (signatures, parameters, return values, examples)\n  - Enumerators\n  - Data Types & Structures\n  - Constants\n  - Notes\n\nBest regards,\nXYZ Technologies Automation`;\n}\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "e350754f-4db4-407b-a6d0-ce199df685bd",
      "name": "Send completion email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -192,
        160
      ],
      "parameters": {
        "sendTo": "your@email.com",
        "message": "={{ $json.message }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "typeVersion": 2.2
    },
    {
      "id": "8e43b277-9554-43e2-bc04-db84eadb6b51",
      "name": "Extract structured docs (GPT-4o)",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        16,
        544
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o",
          "cachedResultName": "GPT-4O"
        },
        "options": {},
        "responses": {
          "values": [
            {
              "role": "system",
              "content": "You are a senior technical writer for embedded systems at XYZ Technologies. Given a C header (.h) file, extract and document everything as strict JSON \u2014 no markdown, no explanation, raw JSON only.\n\nReturn exactly this structure:\n{\n  \"overview\": \"<2-3 sentence summary>\",\n  \"functions\": [\n    {\n      \"name\": \"<function name>\",\n      \"signature\": \"<full prototype>\",\n      \"description\": \"<what it does>\",\n      \"parameters\": [\n        { \"name\": \"<param>\", \"type\": \"<type>\", \"description\": \"<desc>\" }\n      ],\n      \"returns\": \"<return type and meaning>\",\n      \"example\": \"<minimal C usage>\"\n    }\n  ],\n  \"enumerators\": [\n    {\n      \"name\": \"<enum type name>\",\n      \"description\": \"<purpose>\",\n      \"values\": [\n        { \"name\": \"<VALUE>\", \"value\": \"<integer>\", \"description\": \"<meaning>\" }\n      ]\n    }\n  ],\n  \"data_types\": [\n    {\n      \"name\": \"<type name>\",\n      \"kind\": \"<struct|union|typedef|bitfield>\",\n      \"description\": \"<purpose>\",\n      \"members\": [\n        { \"name\": \"<field>\", \"type\": \"<type>\", \"description\": \"<meaning>\" }\n      ]\n    }\n  ],\n  \"constants\": [\n    { \"name\": \"<CONSTANT_NAME>\", \"value\": \"<value>\", \"description\": \"<meaning>\" }\n  ],\n  \"notes\": \"<usage notes, thread safety, prerequisites>\"\n}\n\nIf a section has no items return []. Return ONLY the JSON object."
            },
            {
              "content": "=File: {{ $('Process one file at a time').item.json.fileName }}\n\n{{ $('Extract from File2').item.json.data }}"
            }
          ]
        },
        "builtInTools": {}
      },
      "typeVersion": 2.1
    },
    {
      "id": "f463a4fa-5996-4e9d-8f10-bc2f9c54d4ad",
      "name": "Parse JSON response",
      "type": "n8n-nodes-base.code",
      "position": [
        352,
        544
      ],
      "parameters": {
        "jsCode": "// \u2500\u2500 Parse JSON response \u2014 handles all known OpenAI/n8n response shapes \u2500\u2500\u2500\u2500\u2500\u2500\nconst item = $input.first();\n\n// Log the full response so you can inspect it in n8n's output panel\n// (remove once working)\nconsole.log('RAW ITEM KEYS:', JSON.stringify(Object.keys(item.json || {})));\n\nconst raw =\n  // n8n OpenAI node (AI Agent / Chat Model)\n  item.json?.choices?.[0]?.message?.content ||\n  // OpenAI Responses API shape\n  item.json?.output?.[0]?.content?.[0]?.text ||\n  // Fallback: sometimes n8n wraps it under .text or .message\n  item.json?.message?.content ||\n  item.json?.text ||\n  // Last resort: if the whole json IS the parsed object already\n  (item.json?.overview ? JSON.stringify(item.json) : '{}');\n\nlet parsed;\ntry {\n  const cleaned = raw\n    .replace(/^```(?:json)?\\r?\\n?/, '')\n    .replace(/\\r?\\n?```$/, '')\n    .trim();\n  parsed = JSON.parse(cleaned);\n} catch (e) {\n  // If JSON parse fails, put the raw text in overview so you can see what GPT returned\n  parsed = {\n    overview: 'JSON parse failed. Raw response: ' + raw.slice(0, 500),\n    functions: [],\n    enumerators: [],\n    data_types: [],\n    constants: [],\n    notes: ''\n  };\n}\n\n// Try multiple places for fileName\nconst fileName =\n  $('Process one file at a time')?.item?.json?.fileName ||\n  $('Process one file at a time1')?.item?.json?.fileName ||\n  item.json?.fileName ||\n  'unknown.h';\n\nreturn [{ json: { ...parsed, fileName } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "f7a197b7-3470-4ad5-a2bb-fa913c461969",
      "name": "Generate HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        608,
        544
      ],
      "parameters": {
        "jsCode": "// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// GENERATE HTML FILE \u2014 outputs binary HTML file \u2192 save directly to Google Drive\n// Replace \"Generate PDF (zero dependencies)1\" with this node.\n// Then wire: this node \u2192 Save to Google Drive (upload file)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconst doc = $input.first().json;\nconst fileName = doc.fileName || 'unknown.h';\nconst baseName = fileName.replace(/\\.[^/.]+$/, '');\nconst htmlName = 'DOC_' + baseName + '.html';\nconst today    = new Date().toISOString().slice(0, 10);\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst esc = s => String(s || '')\n  .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\n\nfunction funcBlock(fn) {\n  const params = fn.parameters || [];\n  const paramRows = params.map(p => `\n    <tr>\n      <td><code>${esc(p.name)}</code></td>\n      <td><code>${esc(p.type)}</code></td>\n      <td>${esc(p.description)}</td>\n    </tr>`).join('');\n\n  return `\n  <div class=\"card fn-block\">\n    <div class=\"fn-name\">${esc(fn.name || '')}</div>\n    <p class=\"fn-desc\">${esc(fn.description || '')}</p>\n    ${fn.signature ? `<div class=\"label\">Signature</div><pre><code>${esc(fn.signature)}</code></pre>` : ''}\n    ${params.length ? `\n    <div class=\"label\">Parameters</div>\n    <div class=\"table-wrap\"><table>\n      <thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead>\n      <tbody>${paramRows}</tbody>\n    </table></div>` : ''}\n    ${fn.returns ? `<div class=\"label\">Returns</div><p class=\"returns\">${esc(fn.returns)}</p>` : ''}\n    ${fn.example ? `<div class=\"label\">Example</div><pre><code>${esc(fn.example)}</code></pre>` : ''}\n  </div>`;\n}\n\nfunction dtBlock(dt) {\n  const members = dt.members || [];\n  const rows = members.map(m => `\n    <tr>\n      <td><code>${esc(m.name)}</code></td>\n      <td><code>${esc(m.type)}</code></td>\n      <td>${esc(m.description)}</td>\n    </tr>`).join('');\n  return `\n  <div class=\"card\">\n    <div class=\"fn-name\">${esc(dt.name || '')} <span class=\"kind-tag\">${esc(dt.kind || '')}</span></div>\n    <p>${esc(dt.description || '')}</p>\n    ${members.length ? `\n    <div class=\"label\">Members</div>\n    <div class=\"table-wrap\"><table>\n      <thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>\n      <tbody>${rows}</tbody>\n    </table></div>` : ''}\n  </div>`;\n}\n\nfunction enumBlock(en) {\n  const vals = en.values || [];\n  const rows = vals.map(v => `\n    <tr>\n      <td><code>${esc(v.name)}</code></td>\n      <td>${esc(v.value)}</td>\n      <td>${esc(v.description)}</td>\n    </tr>`).join('');\n  return `\n  <div class=\"card\">\n    <div class=\"fn-name\">${esc(en.name || '')}</div>\n    <p>${esc(en.description || '')}</p>\n    ${vals.length ? `\n    <div class=\"table-wrap\"><table>\n      <thead><tr><th>Enumerator</th><th>Value</th><th>Description</th></tr></thead>\n      <tbody>${rows}</tbody>\n    </table></div>` : ''}\n  </div>`;\n}\n\nconst fns    = doc.functions   || [];\nconst enums  = doc.enumerators || [];\nconst dts    = doc.data_types  || [];\nconst consts = doc.constants   || [];\n\nconst empty = label => `<p class=\"empty-state\">No ${label} found in this header file.</p>`;\n\nconst fnHtml    = fns.length    ? fns.map(funcBlock).join('')    : empty('functions');\nconst enumHtml  = enums.length  ? enums.map(enumBlock).join('')  : empty('enumerators');\nconst dtHtml    = dts.length    ? dts.map(dtBlock).join('')      : empty('data types');\nconst constHtml = consts.length ? `\n  <div class=\"card\">\n  <div class=\"table-wrap\"><table>\n    <thead><tr><th>Constant Name</th><th>Value</th><th>Description</th></tr></thead>\n    <tbody>${consts.map(c=>`<tr><td><code>${esc(c.name)}</code></td><td>${esc(c.value)}</td><td>${esc(c.description)}</td></tr>`).join('')}</tbody>\n  </table></div></div>` : empty('constants');\n\nconst tocItems = [\n  ['1','Overview'],['2','Functions'],['3','Enumerators'],\n  ['4','Data Types &amp; Structures'],['5','Constants'],['6','Notes']\n];\n\nconst html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>${esc(baseName)} \u2014 API Documentation</title>\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link href=\"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,600;0,700;1,400&display=swap\" rel=\"stylesheet\">\n<style>\n  :root {\n    --navy:   #0D1C28;\n    --blue:   #1567BF;\n    --cyan:   #00B0FF;\n    --light:  #E8EFF9;\n    --muted:  #8C9BA5;\n    --text:   #1A2530;\n    --border: #CBD5E1;\n    --code-bg:#F0F7EC;\n    --code-fg:#1B5E20;\n    --white:  #FFFFFF;\n    --card-bg:#FAFCFF;\n    --sidebar-w: 240px;\n  }\n\n  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n  html { scroll-behavior: smooth; }\n\n  body {\n    font-family: 'IBM Plex Sans', sans-serif;\n    font-size: 14px;\n    line-height: 1.65;\n    color: var(--text);\n    background: #F4F7FB;\n    display: flex;\n    min-height: 100vh;\n  }\n\n  /* \u2500\u2500 Sidebar \u2500\u2500 */\n  .sidebar {\n    width: var(--sidebar-w);\n    min-height: 100vh;\n    background: var(--navy);\n    position: fixed;\n    top: 0; left: 0;\n    display: flex;\n    flex-direction: column;\n    z-index: 100;\n  }\n\n  .sidebar-logo {\n    padding: 24px 20px 16px;\n    border-bottom: 1px solid rgba(255,255,255,0.08);\n  }\n  .sidebar-logo .brand {\n    font-size: 17px;\n    font-weight: 700;\n    color: var(--white);\n    letter-spacing: 0.3px;\n  }\n  .sidebar-logo .brand span { color: var(--cyan); font-weight: 300; }\n  .sidebar-logo .file-name {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 10px;\n    color: var(--muted);\n    margin-top: 4px;\n  }\n\n  .sidebar-nav {\n    padding: 16px 0;\n    flex: 1;\n    overflow-y: auto;\n  }\n  .nav-label {\n    font-size: 9px;\n    font-weight: 700;\n    text-transform: uppercase;\n    letter-spacing: 1.2px;\n    color: var(--muted);\n    padding: 8px 20px 4px;\n  }\n  .nav-item {\n    display: flex;\n    align-items: center;\n    gap: 10px;\n    padding: 8px 20px;\n    color: rgba(255,255,255,0.65);\n    text-decoration: none;\n    font-size: 13px;\n    font-weight: 400;\n    transition: all 0.15s;\n    border-left: 3px solid transparent;\n  }\n  .nav-item:hover {\n    color: var(--white);\n    background: rgba(255,255,255,0.06);\n    border-left-color: var(--cyan);\n  }\n  .nav-item .num {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 10px;\n    color: var(--cyan);\n    min-width: 16px;\n  }\n\n  .sidebar-footer {\n    padding: 14px 20px;\n    border-top: 1px solid rgba(255,255,255,0.08);\n    font-size: 10px;\n    color: var(--muted);\n  }\n\n  /* \u2500\u2500 Main content \u2500\u2500 */\n  .main {\n    margin-left: var(--sidebar-w);\n    flex: 1;\n    display: flex;\n    flex-direction: column;\n  }\n\n  /* \u2500\u2500 Top bar \u2500\u2500 */\n  .topbar {\n    background: var(--white);\n    border-bottom: 1px solid var(--border);\n    padding: 14px 40px;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    position: sticky;\n    top: 0;\n    z-index: 50;\n  }\n  .topbar-title {\n    font-size: 15px;\n    font-weight: 600;\n    color: var(--navy);\n  }\n  .topbar-meta {\n    font-size: 11px;\n    color: var(--muted);\n    font-family: 'IBM Plex Mono', monospace;\n  }\n  .badge {\n    display: inline-flex;\n    align-items: center;\n    gap: 5px;\n    background: var(--light);\n    color: var(--blue);\n    font-size: 10px;\n    font-weight: 600;\n    padding: 3px 8px;\n    border-radius: 4px;\n    letter-spacing: 0.3px;\n  }\n\n  /* \u2500\u2500 Cover hero \u2500\u2500 */\n  .hero {\n    background: linear-gradient(135deg, var(--navy) 0%, #152E45 60%, #1B3A52 100%);\n    padding: 56px 40px 48px;\n    color: var(--white);\n    position: relative;\n    overflow: hidden;\n  }\n  .hero::before {\n    content: '';\n    position: absolute;\n    top: -40px; right: -40px;\n    width: 280px; height: 280px;\n    border-radius: 50%;\n    background: radial-gradient(circle, rgba(0,176,255,0.12) 0%, transparent 70%);\n    pointer-events: none;\n  }\n  .hero-eyebrow {\n    font-size: 10px;\n    font-weight: 700;\n    text-transform: uppercase;\n    letter-spacing: 2px;\n    color: var(--cyan);\n    margin-bottom: 12px;\n  }\n  .hero-title {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 32px;\n    font-weight: 600;\n    letter-spacing: -0.5px;\n    margin-bottom: 8px;\n  }\n  .hero-subtitle {\n    font-size: 15px;\n    color: rgba(255,255,255,0.55);\n    font-weight: 300;\n    margin-bottom: 28px;\n  }\n  .hero-meta {\n    display: flex;\n    gap: 24px;\n    flex-wrap: wrap;\n  }\n  .hero-meta-item {\n    display: flex;\n    flex-direction: column;\n    gap: 2px;\n  }\n  .hero-meta-item .meta-key {\n    font-size: 9px;\n    text-transform: uppercase;\n    letter-spacing: 1px;\n    color: var(--muted);\n  }\n  .hero-meta-item .meta-val {\n    font-size: 12px;\n    font-family: 'IBM Plex Mono', monospace;\n    color: rgba(255,255,255,0.8);\n  }\n  .hero-accent {\n    height: 2px;\n    background: linear-gradient(90deg, var(--cyan), transparent);\n    margin-bottom: 28px;\n    width: 80px;\n  }\n\n  /* \u2500\u2500 Content area \u2500\u2500 */\n  .content {\n    padding: 0 40px 60px;\n    max-width: 900px;\n  }\n\n  /* \u2500\u2500 Section \u2500\u2500 */\n  .section {\n    margin-top: 48px;\n  }\n  .section-header {\n    display: flex;\n    align-items: center;\n    gap: 12px;\n    margin-bottom: 20px;\n    padding-bottom: 12px;\n    border-bottom: 2px solid var(--border);\n  }\n  .section-num {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 11px;\n    font-weight: 600;\n    color: var(--cyan);\n    background: var(--navy);\n    padding: 3px 8px;\n    border-radius: 4px;\n  }\n  .section-title {\n    font-size: 18px;\n    font-weight: 700;\n    color: var(--navy);\n    letter-spacing: -0.3px;\n  }\n\n  /* \u2500\u2500 Card \u2500\u2500 */\n  .card {\n    background: var(--white);\n    border: 1px solid var(--border);\n    border-radius: 8px;\n    padding: 22px 24px;\n    margin-bottom: 14px;\n    box-shadow: 0 1px 3px rgba(0,0,0,0.05);\n  }\n\n  /* \u2500\u2500 Function name \u2500\u2500 */\n  .fn-name {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 15px;\n    font-weight: 600;\n    color: var(--blue);\n    margin-bottom: 6px;\n  }\n  .fn-desc {\n    color: #4A5568;\n    margin-bottom: 14px;\n    font-size: 13.5px;\n  }\n  .kind-tag {\n    font-size: 11px;\n    font-weight: 400;\n    font-family: 'IBM Plex Sans', sans-serif;\n    color: var(--muted);\n    background: var(--light);\n    padding: 1px 7px;\n    border-radius: 3px;\n    vertical-align: middle;\n    margin-left: 6px;\n  }\n\n  /* \u2500\u2500 Label \u2500\u2500 */\n  .label {\n    font-size: 9px;\n    font-weight: 700;\n    text-transform: uppercase;\n    letter-spacing: 1.2px;\n    color: var(--blue);\n    margin: 14px 0 6px;\n  }\n\n  /* \u2500\u2500 Code \u2500\u2500 */\n  pre {\n    background: var(--code-bg);\n    border-left: 3px solid var(--cyan);\n    border-radius: 0 6px 6px 0;\n    padding: 12px 16px;\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 12px;\n    color: var(--code-fg);\n    white-space: pre-wrap;\n    word-break: break-all;\n    margin-bottom: 6px;\n    overflow-x: auto;\n  }\n  code {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 12px;\n    background: var(--code-bg);\n    color: var(--code-fg);\n    padding: 1px 5px;\n    border-radius: 3px;\n  }\n  pre code { background: none; padding: 0; }\n\n  /* \u2500\u2500 Returns \u2500\u2500 */\n  .returns {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 12px;\n    color: #555;\n  }\n\n  /* \u2500\u2500 Tables \u2500\u2500 */\n  .table-wrap { overflow-x: auto; }\n  table {\n    width: 100%;\n    border-collapse: collapse;\n    font-size: 13px;\n  }\n  thead tr {\n    background: var(--navy);\n    color: var(--white);\n  }\n  thead th {\n    padding: 9px 12px;\n    text-align: left;\n    font-weight: 600;\n    font-size: 11px;\n    letter-spacing: 0.3px;\n    white-space: nowrap;\n  }\n  thead th:first-child { border-radius: 6px 0 0 0; }\n  thead th:last-child  { border-radius: 0 6px 0 0; }\n  tbody tr:nth-child(odd)  { background: var(--white); }\n  tbody tr:nth-child(even) { background: var(--light); }\n  tbody td {\n    padding: 8px 12px;\n    border-bottom: 1px solid var(--border);\n    vertical-align: top;\n    font-size: 12.5px;\n    color: var(--text);\n  }\n\n  /* \u2500\u2500 Empty state \u2500\u2500 */\n  .empty-state {\n    color: var(--muted);\n    font-style: italic;\n    font-size: 13px;\n    padding: 20px 0 4px;\n  }\n\n  /* \u2500\u2500 Notes footer \u2500\u2500 */\n  .notes-footer {\n    margin-top: 24px;\n    padding-top: 14px;\n    border-top: 1px solid var(--cyan);\n    font-size: 11px;\n    color: var(--muted);\n    font-style: italic;\n    text-align: center;\n  }\n\n  /* \u2500\u2500 Scrollbar \u2500\u2500 */\n  ::-webkit-scrollbar { width: 6px; height: 6px; }\n  ::-webkit-scrollbar-track { background: transparent; }\n  ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }\n</style>\n</head>\n<body>\n\n<!-- SIDEBAR -->\n<aside class=\"sidebar\">\n  <div class=\"sidebar-logo\">\n    <div class=\"brand\">XYZ <span>Technologies</span></div>\n    <div class=\"file-name\">${esc(fileName)}</div>\n  </div>\n  <nav class=\"sidebar-nav\">\n    <div class=\"nav-label\">Contents</div>\n    ${tocItems.map(([n,t]) => `\n    <a class=\"nav-item\" href=\"#sec${n}\">\n      <span class=\"num\">${n}</span>${t}\n    </a>`).join('')}\n  </nav>\n  <div class=\"sidebar-footer\">\n    v1.0 &nbsp;\u00b7&nbsp; Internal<br>Generated: ${today}\n  </div>\n</aside>\n\n<!-- MAIN -->\n<div class=\"main\">\n\n  <!-- TOP BAR -->\n  <div class=\"topbar\">\n    <div class=\"topbar-title\">API Documentation &mdash; <span style=\"font-family:'IBM Plex Mono',monospace;font-weight:400\">${esc(fileName)}</span></div>\n    <div style=\"display:flex;gap:8px;align-items:center\">\n      <span class=\"badge\">C Embedded</span>\n      <span class=\"topbar-meta\">${today}</span>\n    </div>\n  </div>\n\n  <!-- HERO -->\n  <div class=\"hero\">\n    <div class=\"hero-eyebrow\">XYZ Technologies &nbsp;\u00b7&nbsp; Header File Documentation</div>\n    <div class=\"hero-title\">${esc(baseName.toUpperCase())}</div>\n    <div class=\"hero-accent\"></div>\n    <div class=\"hero-subtitle\">C Embedded Systems Interface Reference</div>\n    <div class=\"hero-meta\">\n      <div class=\"hero-meta-item\"><span class=\"meta-key\">Source</span><span class=\"meta-val\">${esc(fileName)}</span></div>\n      <div class=\"hero-meta-item\"><span class=\"meta-key\">Prepared by</span><span class=\"meta-val\">XYZ Technologies</span></div>\n      <div class=\"hero-meta-item\"><span class=\"meta-key\">Date</span><span class=\"meta-val\">${today}</span></div>\n      <div class=\"hero-meta-item\"><span class=\"meta-key\">Version</span><span class=\"meta-val\">1.0</span></div>\n      <div class=\"hero-meta-item\"><span class=\"meta-key\">Classification</span><span class=\"meta-val\">Internal</span></div>\n    </div>\n  </div>\n\n  <!-- CONTENT -->\n  <div class=\"content\">\n\n    <!-- 1. OVERVIEW -->\n    <div class=\"section\" id=\"sec1\">\n      <div class=\"section-header\">\n        <span class=\"section-num\">01</span>\n        <span class=\"section-title\">Overview</span>\n      </div>\n      <div class=\"card\">\n        <p>${esc(doc.overview || 'No overview available.')}</p>\n      </div>\n    </div>\n\n    <!-- 2. FUNCTIONS -->\n    <div class=\"section\" id=\"sec2\">\n      <div class=\"section-header\">\n        <span class=\"section-num\">02</span>\n        <span class=\"section-title\">Functions</span>\n      </div>\n      ${fnHtml}\n    </div>\n\n    <!-- 3. ENUMERATORS -->\n    <div class=\"section\" id=\"sec3\">\n      <div class=\"section-header\">\n        <span class=\"section-num\">03</span>\n        <span class=\"section-title\">Enumerators</span>\n      </div>\n      ${enumHtml}\n    </div>\n\n    <!-- 4. DATA TYPES & STRUCTURES -->\n    <div class=\"section\" id=\"sec4\">\n      <div class=\"section-header\">\n        <span class=\"section-num\">04</span>\n        <span class=\"section-title\">Data Types &amp; Structures</span>\n      </div>\n      ${dtHtml}\n    </div>\n\n    <!-- 5. CONSTANTS -->\n    <div class=\"section\" id=\"sec5\">\n      <div class=\"section-header\">\n        <span class=\"section-num\">05</span>\n        <span class=\"section-title\">Constants</span>\n      </div>\n      ${constHtml}\n    </div>\n\n    <!-- 6. NOTES -->\n    <div class=\"section\" id=\"sec6\">\n      <div class=\"section-header\">\n        <span class=\"section-num\">06</span>\n        <span class=\"section-title\">Notes</span>\n      </div>\n      <div class=\"card\">\n        <p>${esc(doc.notes || 'No additional notes.')}</p>\n        <div class=\"notes-footer\">This document was automatically generated by XYZ Technologies.</div>\n      </div>\n    </div>\n\n  </div><!-- /content -->\n</div><!-- /main -->\n\n</body>\n</html>`;\n\n// Convert HTML string to base64 binary for Google Drive upload\nconst b64 = Buffer.from(html, 'utf8').toString('base64');\n\nreturn [{\n  json: {\n    htmlName,\n    fileName,\n    timestamp: today\n  },\n  binary: {\n    data: {\n      data: b64,\n      mimeType: 'text/html',\n      fileName: htmlName,\n      fileExtension: 'html'\n    }\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "0b63f327-e409-4fe9-8716-bf4d53697441",
      "name": "Save PDF to Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        928,
        544
      ],
      "parameters": {
        "name": "={{ $json.pdfName }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "MyDrive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_OUTPUT_FOLDER_ID"
        }
      },
      "typeVersion": 3
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "connections": {
    "Download file": {
      "main": [
        [
          {
            "node": "Extract from File2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate HTML": {
      "main": [
        [
          {
            "node": "Save PDF to Google Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build email body": {
      "main": [
        [
          {
            "node": "Send completion email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract from File2": {
      "main": [
        [
          {
            "node": "Extract structured docs (GPT-4o)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse JSON response": {
      "main": [
        [
          {
            "node": "Generate HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter .h files only": {
      "main": [
        [
          {
            "node": "Process one file at a time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save PDF to Google Drive": {
      "main": [
        [
          {
            "node": "Process one file at a time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get all files from folder": {
      "main": [
        [
          {
            "node": "Filter .h files only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process one file at a time": {
      "main": [
        [
          {
            "node": "Build email body",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Download file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract structured docs (GPT-4o)": {
      "main": [
        [
          {
            "node": "Parse JSON response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Get all files from folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}