{
  "id": "7oFFGgWK8j6WPZQS",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Tasks Briefing",
  "tags": [],
  "nodes": [
    {
      "id": "3ffa1295-e39b-4610-9f65-3512e48e04a4",
      "name": "Code (Build LLM Prompt)",
      "type": "n8n-nodes-base.code",
      "position": [
        720,
        432
      ],
      "parameters": {
        "jsCode": "/* Build a single prompt for the LLM to produce a Slack-ready brief.\n * No tool calls\u2014just return concise Markdown text.\n */\nconst tasks = $input.all().map(i => i.json);\n\nconst compact = tasks.map(t => ({\n  title: t.title || '(untitled)',\n  notes: t.notes || '',\n  due: t.due || t.updated || '',\n  webViewLink: t.webViewLink || '',\n  links: (t.links || []).map(l => `${l.type || 'link'}: ${l.link || ''}`),\n  parent: t.parent || null,\n}));\n\nconst instructions = `You are an executive assistant. Create a concise Slack-ready morning brief for Google Tasks due TODAY.\nRequirements:\n- Start with a one-sentence overview (calm, actionable).\n- Then bullets: **Title** \u2014 1-sentence goal; 1\u20132 next steps; default owner \"Me\"; ETA (morning/afternoon/evening).\n- If notes contain a URL or webViewLink exists, show one key link inline.\n- Group obvious subtasks under a parent with indentation.\n- Keep it under ~120 words. No fluff.\n- Output plain Markdown suitable for Slack.`;\n\nconst context = `DATA (JSON):\\n${JSON.stringify(compact, null, 2)}`;\n\nreturn [{ json: { input: `${instructions}\\n\\n${context}` } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "482aa3f0-0adc-41ad-8109-35e5c44be974",
      "name": "Get many tasks",
      "type": "n8n-nodes-base.googleTasks",
      "position": [
        -176,
        672
      ],
      "parameters": {
        "task": "MTQzNzI1NzAyOTQxNTk3NzM5NDc6MDow",
        "operation": "getAll",
        "returnAll": true,
        "additionalFields": {}
      },
      "credentials": {
        "googleTasksOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "5f38a733-4ea4-48a5-9d51-a310b20a2399",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        256,
        672
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "f0f18c51-df5b-432f-ab71-b8fa036b4b97",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.__empty }}",
              "rightValue": "empty"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "0b43a198-7f5b-4832-961c-b141f098382d",
      "name": "Basic LLM Chain1",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        928,
        432
      ],
      "parameters": {
        "text": "={{ $json.input }}",
        "batching": {},
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "9e33de10-085c-4658-9bc2-cff7aa5d0a5d",
      "name": "Ollama Model1",
      "type": "@n8n/n8n-nodes-langchain.lmOllama",
      "position": [
        736,
        672
      ],
      "parameters": {
        "model": "qwen3:4b",
        "options": {}
      },
      "credentials": {
        "ollamaApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c8f712cd-3810-4bf1-98cb-8deba4dee8b7",
      "name": "Send a message1",
      "type": "n8n-nodes-base.slack",
      "position": [
        1344,
        656
      ],
      "parameters": {
        "text": "={{ $json.cleanText }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "REPLACE_WITH_SLACK_CHANNEL_NAME"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "82d65a9d-f437-4739-aa71-df8c25f0a930",
      "name": "Code",
      "type": "n8n-nodes-base.code",
      "position": [
        992,
        656
      ],
      "parameters": {
        "jsCode": "// Remove <think>...</think> blocks from the LLM output\nlet text = $json.text || \"\";\n\ntext = text.replace(/<think>[\\s\\S]*?<\\/think>/gi, \"\").trim();\n\nreturn [{ json: { cleanText: text } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "fb99dd91-1b64-4ef5-9c1c-1ec840a732e1",
      "name": "Code (Filter Due Today)",
      "type": "n8n-nodes-base.code",
      "position": [
        64,
        672
      ],
      "parameters": {
        "jsCode": "/* Filter Google Tasks that are due TODAY in Asia/Dhaka.\n * Emits N items (one per task) when there are tasks due today.\n * Emits a single item { __empty: true } when none.\n */\nconst TZ = 'Asia/Dhaka';\nconst toTZ = (d) => new Date(d.toLocaleString('en-US', { timeZone: TZ }));\nconst now = new Date();\nconst start = toTZ(new Date(now.getFullYear(), now.getMonth(), now.getDate()));\nconst end = toTZ(new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1));\nfunction isDueToday(dueRaw) {\n  if (!dueRaw) return false;\n  const iso = dueRaw.length === 10 ? `${dueRaw}T00:00:00Z` : dueRaw;\n  const d = toTZ(new Date(iso));\n  return d >= start && d < end;\n}\nconst dueToday = [];\nfor (const item of $input.all()) {\n  const t = item.json || {};\n  const status = (t.status || '').toLowerCase();\n  if (status === 'completed' || status === 'hidden' || status === 'deleted') continue;\n  if (isDueToday(t.due || '')) {\n    dueToday.push({ json: t });\n  }\n}\nif (dueToday.length === 0) {\n  return [{ json: { __empty: \"empty\" } }];\n}\nreturn dueToday;"
      },
      "typeVersion": 2
    },
    {
      "id": "aaccef83-3d04-4d61-ab2d-3177003e85cf",
      "name": "Slack1",
      "type": "n8n-nodes-base.slack",
      "position": [
        464,
        688
      ],
      "parameters": {
        "text": "\ud83c\udf24\ufe0f No Google Tasks due today. I\u2019ll check again tomorrow ",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "REPLACE_WITH_SLACK_CHANNEL_NAME"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "567f37f5-2267-4576-ba02-1385fe9ad4a4",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -320,
        336
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 512,
        "content": "## Scheduled Trigger\nEvery Morning at 7 AM Fetch all the tasks"
      },
      "typeVersion": 1
    },
    {
      "id": "a7560047-6b24-46ae-a7cf-4315b32d19fe",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        560
      ],
      "parameters": {
        "color": 7,
        "width": 576,
        "height": 288,
        "content": "## Filter Tasks\nSend tasks due today to LLM or notify user that there are no tasks scheduled today"
      },
      "typeVersion": 1
    },
    {
      "id": "f10cdca4-b703-4606-812c-8e2a823779dc",
      "name": "Trigger at Morning",
      "type": "n8n-nodes-base.cron",
      "position": [
        -224,
        448
      ],
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "hour": 7
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ad6b3cde-4e0b-44d4-aca5-03b51356a3da",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        624,
        336
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 512,
        "content": "## Process Tasks Briefing\nProduce a solid briefing for the user with LLM and clean the output removing the Think tags"
      },
      "typeVersion": 1
    },
    {
      "id": "6d980e56-2085-4ddb-bce6-f2d1601c0446",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1264,
        560
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 288,
        "content": "## Notify in Slack\nSend the task briefing as a slack message"
      },
      "typeVersion": 1
    },
    {
      "id": "ddedc07a-1434-45f9-acf8-b1c8d41ef3e1",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -896,
        -384
      ],
      "parameters": {
        "width": 512,
        "height": 2016,
        "content": "## Tasks Briefing\n\nThis template posts a clean, Slack-ready **morning summary of your Google Tasks due today**. It fetches tasks, filters only those due \u201ctoday\u201d in your timezone, asks a local **LLM (via LangChain + Ollama)** to produce a short summary (no steps, just a concise brief), strips any hidden `<think>` blocks, and delivers the message to your chosen Slack channel.\n\n## How it works\n\n1. **Trigger at Morning (Cron)** \u2013 runs at **7:00 AM** (you can change the hour) to kick things off daily.\n2. **Get many tasks (Google Tasks node)** \u2013 pulls tasks from your selected Google Tasklist.\n3. **Code (Filter Due Today)** \u2013 normalizes dates to your timezone, keeps only tasks due today, and emits a fallback flag if none exist.\n4. **If** \u2013 routes:\n\n   * **True (has tasks)** \u2192 continues to the LLM summary path.\n   * **False (no tasks)** \u2192 sends a \u201cNo tasks due today\u201d message to Slack.\n5. **Code (Build LLM Prompt)** \u2013 builds a compact, Markdown-only prompt for the model (no tool calls).\n6. **Basic LLM Chain (LangChain)** + **Ollama Model** \u2013 generates a short summary for Slack.\n7. **Code (Cleanup)** \u2013 removes any `<think>\u2026</think>` content if the model includes it.\n8. **Send a message (Slack)** \u2013 posts the final brief to your Slack channel.\n\n## Required credentials\n\n* **Google Tasks OAuth2 API** \u2013 to read tasks from your Google Tasklist.\n* **Slack API** \u2013 to post the summary into a channel.\n* **Ollama** \u2013 local model endpoint (e.g., `qwen3:4b`); used by the LangChain LLM nodes.\n\n## Setup Instructions\n\n1. **Google Tasks credential**\n\n   * In Google Cloud Console: enable **Google Tasks API**, create an **OAuth Client (Web)**, and set the redirect URI shown by n8n.\n   * In n8n **Credentials**, add **Google Tasks OAuth2 API** with scope:\n\n     * `https://www.googleapis.com/auth/tasks` (read/write) or\n     * `https://www.googleapis.com/auth/tasks.readonly` (read-only).\n   * In the **Get many tasks** node, select your credential and your **Tasklist**.\n\n2. **Slack credential & channel**\n\n   * In n8n **Credentials**, add **Slack API** (bot/user token with `chat:write`).\n   * In **Send a message** nodes, select your Slack credential and set the **Channel** (e.g., `#new-leads`).\n\n3. **Ollama model (LangChain)**\n\n   * Ensure **Ollama** is running on your host (default `http://localhost:11434`).\n   * Pull a model (e.g., `ollama pull qwen3:4b`) or use another supported model (`llama3:8b`, etc.).\n   * In **Ollama Model** node, select your **Ollama** credential and set the **model** name to match what you pulled.\n\n4. **Timezone & schedule**\n\n   * The **Cron** node is set to **7:00 AM**. Adjust as needed.\n   * The **Code (Filter Due Today)** node is configured for **Asia/Dhaka**; change the `TZ` constant if you prefer a different timezone.\n\n5. **(Optional) Cleanup safety**\n\n   * The template includes a **Code (Cleanup)** node that strips `<think>\u2026</think>` blocks from model output. Keep this connected before the Slack node.\n\n6. **Test the flow**\n\n   * Run the workflow once manually:\n\n     * If you have tasks due today, you should see a concise summary posted to your Slack channel.\n     * If none are due, you\u2019ll receive a friendly \u201cNo tasks due today\u201d message.\n\n7. **Activate**\n\n   * When everything looks good, toggle the workflow **Active** to receive the daily summary automatically.\n\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "86f277b0-90ef-4463-b819-7205a795c460",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Code (Build LLM Prompt)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "Send a message1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ollama Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Basic LLM Chain1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Get many tasks": {
      "main": [
        [
          {
            "node": "Code (Filter Due Today)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Basic LLM Chain1": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger at Morning": {
      "main": [
        [
          {
            "node": "Get many tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code (Build LLM Prompt)": {
      "main": [
        [
          {
            "node": "Basic LLM Chain1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code (Filter Due Today)": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}