894 lines
29 KiB
JSON
894 lines
29 KiB
JSON
{
|
|
"updatedAt": "2026-05-14T00:21:08.433Z",
|
|
"createdAt": "2026-05-13T21:41:17.798Z",
|
|
"id": "g3IdGZCK1EtTsv9T",
|
|
"name": "Morning Brief",
|
|
"description": null,
|
|
"active": true,
|
|
"isArchived": false,
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{
|
|
"field": "cronExpression",
|
|
"expression": "30 6 * * *"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.3,
|
|
"position": [
|
|
0,
|
|
0
|
|
],
|
|
"id": "16110cb5-e50a-4d99-a613-448057221422",
|
|
"name": "Daily 06:30 PT"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "http://wttr.in/Seattle?format=j1",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
300,
|
|
-400
|
|
],
|
|
"id": "a119dfe9-46db-43ca-98b2-f0690bc0f6f5",
|
|
"name": "Weather",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "http://172.19.0.1:18809/health",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
300,
|
|
-250
|
|
],
|
|
"id": "05f60eba-ab11-4fe0-b761-d1ca9ae557d4",
|
|
"name": "Swarm Health",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "http://127.0.0.1:5678/healthz",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
300,
|
|
-100
|
|
],
|
|
"id": "4b5c3f4c-7f11-4e0c-9c56-3b8596a1d25d",
|
|
"name": "n8n Health",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "http://172.19.0.1:18804/health/liveliness",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
300,
|
|
50
|
|
],
|
|
"id": "a8e4e45c-60a1-4f90-8ecc-49782d7be900",
|
|
"name": "LiteLLM Health",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "http://127.0.0.1:5678/api/v1/executions",
|
|
"sendQuery": true,
|
|
"queryParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "workflowId",
|
|
"value": "9sFwRyUDz51csAp7"
|
|
},
|
|
{
|
|
"name": "limit",
|
|
"value": "5"
|
|
},
|
|
{
|
|
"name": "status",
|
|
"value": "success"
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"timeout": 15000
|
|
},
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpHeaderAuth"
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
300,
|
|
200
|
|
],
|
|
"id": "c688abdf-9b63-43b4-81da-7c81388b73f8",
|
|
"name": "Email Highlights",
|
|
"continueOnFail": true,
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "UPAHgUJVRqZQceL4",
|
|
"name": "n8n Public API (Failure Digest)"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "=https://www.googleapis.com/calendar/v3/calendars/primary/events?timeMin={{ $now.format('yyyy-MM-dd') }}T00:00:00-07:00&timeMax={{ $now.plus({days:1}).format('yyyy-MM-dd') }}T23:59:59-07:00&singleEvents=true&orderBy=startTime",
|
|
"authentication": "oAuth2",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
300,
|
|
350
|
|
],
|
|
"id": "d3c5a4ce-9f81-4da8-8dc8-7256bd96285b",
|
|
"name": "Calendar",
|
|
"credentials": {
|
|
"oAuth2Api": {
|
|
"id": "458fY4bs1z49OTeZ",
|
|
"name": "Google OAuth"
|
|
}
|
|
},
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "runOnceForAllItems",
|
|
"jsCode": "\nfunction getSafe(nodeName) {\n try {\n const items = $(nodeName).all();\n if (items && items.length > 0 && items[0].json) {\n return items[0].json;\n }\n } catch (e) {}\n return { error: 'Node failed or returned no data' };\n}\n\nconst weather = getSafe('Weather');\nconst swarmHealth = getSafe('Swarm Health');\nconst n8nHealth = getSafe('n8n Health');\nconst litellmHealth = getSafe('LiteLLM Health');\nconst emailData = getSafe('Email Highlights');\nconst calendar = getSafe('Calendar');\n\n// Extract weather summary\nlet weatherSummary = {};\nif (weather.current_condition && weather.current_condition[0]) {\n const c = weather.current_condition[0];\n weatherSummary = {\n temp_F: c.FeelsLikeF || c.temp_F,\n description: c.weatherDesc ? c.weatherDesc[0].value : 'unknown',\n humidity: c.humidity,\n wind_mph: c.windspeedMiles\n };\n} else {\n weatherSummary = { error: 'Weather data unavailable' };\n}\n\n// Count healthy/unhealthy containers\nlet infraSummary = { healthy: 0, unhealthy: 0, details: [] };\nif (Array.isArray(swarmHealth)) {\n for (const c of swarmHealth) {\n if (c.health === 'healthy' || c.status === 'running') {\n infraSummary.healthy++;\n } else {\n infraSummary.unhealthy++;\n }\n infraSummary.details.push({ name: c.name || c.Names, status: c.status, health: c.health });\n }\n} else if (swarmHealth.containers && Array.isArray(swarmHealth.containers)) {\n for (const c of swarmHealth.containers) {\n if (c.health === 'healthy' || c.status === 'running') {\n infraSummary.healthy++;\n } else {\n infraSummary.unhealthy++;\n }\n infraSummary.details.push({ name: c.name, status: c.status, health: c.health });\n }\n} else if (swarmHealth.error) {\n infraSummary = { error: 'Swarm health endpoint unavailable' };\n}\n\nconst n8nOk = (n8nHealth && !n8nHealth.error);\nconst litellmOk = (litellmHealth && !litellmHealth.error);\n\n// Extract email info from execution data\nlet emailHighlights = [];\nif (emailData && emailData.data && Array.isArray(emailData.data)) {\n for (const exec of emailData.data.slice(0, 5)) {\n emailHighlights.push({\n id: exec.id,\n finished: exec.stoppedAt || 'unknown'\n });\n }\n}\n\n// Calendar events\nlet calendarEvents = [];\nif (calendar && calendar.items && Array.isArray(calendar.items)) {\n for (const ev of calendar.items.slice(0, 10)) {\n calendarEvents.push({\n summary: ev.summary || '(no title)',\n start: (ev.start && (ev.start.dateTime || ev.start.date)) || 'unknown',\n end: (ev.end && (ev.end.dateTime || ev.end.date)) || 'unknown'\n });\n }\n}\n\nconst dataForLLM = {\n date: new Date().toISOString().split('T')[0],\n weather: weatherSummary,\n infrastructure: {\n swarm: infraSummary,\n n8n: n8nOk ? 'healthy' : 'unhealthy',\n litellm: litellmOk ? 'healthy' : 'unhealthy'\n },\n email: emailHighlights.length > 0 ? emailHighlights : [{ info: 'No recent email triage data' }],\n calendar: calendarEvents.length > 0 ? calendarEvents : [{ info: 'Calendar unavailable or no events today' }]\n};\n\nreturn [{ json: { dataJson: JSON.stringify(dataForLLM, null, 2) } }];\n"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
650,
|
|
0
|
|
],
|
|
"id": "1d2b39db-3649-4316-8ce9-b5c83c981017",
|
|
"name": "Merge Data"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "http://172.19.0.1:18806/v1/chat/completions",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={\"model\": \"gemma-4-26B-A4B-it-UD-IQ2_M.gguf\", \"messages\": [{\"role\": \"system\", \"content\": \"You are a personal morning brief assistant. Given raw data about weather, infrastructure, email, and calendar, produce a concise morning brief under 400 words. Use emojis for section headers. Format for Telegram using HTML (use <b> for bold, <code> for code). Keep it scannable and actionable. If any section data is missing or shows errors, note it briefly and move on.\"}, {\"role\": \"user\", \"content\": \"Here is today's data:\\n{{ $json.dataJson }}\"}], \"temperature\": 0.3, \"max_tokens\": 800}",
|
|
"options": {
|
|
"timeout": 60000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
950,
|
|
0
|
|
],
|
|
"id": "f2eb23d3-bf07-46d8-8556-2ba6a0185f5a",
|
|
"name": "Synthesize with LLM",
|
|
"continueOnFail": false
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "runOnceForAllItems",
|
|
"jsCode": "\nconst response = $input.first().json;\nlet brief = '';\n\nif (response.choices && response.choices[0] && response.choices[0].message) {\n brief = response.choices[0].message.content;\n} else if (typeof response === 'string') {\n brief = response;\n} else {\n brief = 'Error: LLM synthesis failed. Raw data: ' + JSON.stringify(response).substring(0, 500);\n}\n\nconst today = new Date().toISOString().split('T')[0];\nconst yamlFrontmatter = '---\\ncreated: ' + today + '\\ntype: morning-brief\\ntags: [daily, brief]\\n---\\n\\n';\n\nreturn [{\n json: {\n brief: brief,\n briefWithFrontmatter: yamlFrontmatter + '# Morning Brief - ' + today + '\\n\\n' + brief,\n date: today\n }\n}];\n"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1250,
|
|
0
|
|
],
|
|
"id": "0adac542-7d95-4002-a3e2-080442cfd9e3",
|
|
"name": "Extract Brief"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"chatId": "8367012007",
|
|
"text": "={{ $json.brief }}",
|
|
"additionalFields": {
|
|
"parse_mode": "HTML"
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.telegram",
|
|
"typeVersion": 1.2,
|
|
"position": [
|
|
1550,
|
|
-150
|
|
],
|
|
"id": "8242ada9-20c8-4689-b00c-3cd2787b2eb5",
|
|
"name": "Send Telegram",
|
|
"credentials": {
|
|
"telegramApi": {
|
|
"id": "aox4dyIWVSRdcH5z",
|
|
"name": "Telegram Bot (OpenClaw)"
|
|
}
|
|
},
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "PUT",
|
|
"url": "=http://172.19.0.1:27123/vault/Notes/{{ $json.date }} Morning Brief.md",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "text/markdown"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"contentType": "raw",
|
|
"rawContentType": "text/markdown",
|
|
"body": "={{ $json.briefWithFrontmatter }}",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
1550,
|
|
150
|
|
],
|
|
"id": "0f1fd6a2-86c0-4d3f-a948-32ce701d9f9f",
|
|
"name": "Save to Obsidian",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "465Swz2b71O2KRAK",
|
|
"name": "Obsidian Local REST API"
|
|
}
|
|
},
|
|
"continueOnFail": true
|
|
}
|
|
],
|
|
"connections": {
|
|
"Daily 06:30 PT": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Weather",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Swarm Health",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "n8n Health",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "LiteLLM Health",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Email Highlights",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Calendar",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Weather": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Swarm Health": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"n8n Health": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"LiteLLM Health": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Email Highlights": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Calendar": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Merge Data": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Synthesize with LLM",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Synthesize with LLM": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Extract Brief",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Extract Brief": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Send Telegram",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Save to Obsidian",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"settings": {
|
|
"executionOrder": "v1",
|
|
"timezone": "America/Los_Angeles",
|
|
"callerPolicy": "workflowsFromSameOwner",
|
|
"availableInMCP": false
|
|
},
|
|
"staticData": {
|
|
"node:Daily 06:30 PT": {
|
|
"recurrenceRules": []
|
|
}
|
|
},
|
|
"meta": null,
|
|
"versionId": "c23946c7-29cf-42dc-985c-c1b818b8d010",
|
|
"activeVersionId": "c23946c7-29cf-42dc-985c-c1b818b8d010",
|
|
"versionCounter": 10,
|
|
"triggerCount": 1,
|
|
"shared": [
|
|
{
|
|
"updatedAt": "2026-05-13T21:41:17.800Z",
|
|
"createdAt": "2026-05-13T21:41:17.800Z",
|
|
"role": "workflow:owner",
|
|
"workflowId": "g3IdGZCK1EtTsv9T",
|
|
"projectId": "WGdp8QunI1tHpjXa",
|
|
"project": {
|
|
"updatedAt": "2026-03-11T21:08:10.005Z",
|
|
"createdAt": "2026-03-11T21:05:11.541Z",
|
|
"id": "WGdp8QunI1tHpjXa",
|
|
"name": "will will <will@wills-portal.com>",
|
|
"type": "personal",
|
|
"icon": null,
|
|
"description": null,
|
|
"creatorId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
|
}
|
|
}
|
|
],
|
|
"tags": [],
|
|
"activeVersion": {
|
|
"updatedAt": "2026-05-14T00:21:08.434Z",
|
|
"createdAt": "2026-05-14T00:21:08.434Z",
|
|
"versionId": "c23946c7-29cf-42dc-985c-c1b818b8d010",
|
|
"workflowId": "g3IdGZCK1EtTsv9T",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{
|
|
"field": "cronExpression",
|
|
"expression": "30 6 * * *"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.3,
|
|
"position": [
|
|
0,
|
|
0
|
|
],
|
|
"id": "16110cb5-e50a-4d99-a613-448057221422",
|
|
"name": "Daily 06:30 PT"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "http://wttr.in/Seattle?format=j1",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
300,
|
|
-400
|
|
],
|
|
"id": "a119dfe9-46db-43ca-98b2-f0690bc0f6f5",
|
|
"name": "Weather",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "http://172.19.0.1:18809/health",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
300,
|
|
-250
|
|
],
|
|
"id": "05f60eba-ab11-4fe0-b761-d1ca9ae557d4",
|
|
"name": "Swarm Health",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "http://127.0.0.1:5678/healthz",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
300,
|
|
-100
|
|
],
|
|
"id": "4b5c3f4c-7f11-4e0c-9c56-3b8596a1d25d",
|
|
"name": "n8n Health",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "http://172.19.0.1:18804/health/liveliness",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
300,
|
|
50
|
|
],
|
|
"id": "a8e4e45c-60a1-4f90-8ecc-49782d7be900",
|
|
"name": "LiteLLM Health",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "http://127.0.0.1:5678/api/v1/executions",
|
|
"sendQuery": true,
|
|
"queryParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "workflowId",
|
|
"value": "9sFwRyUDz51csAp7"
|
|
},
|
|
{
|
|
"name": "limit",
|
|
"value": "5"
|
|
},
|
|
{
|
|
"name": "status",
|
|
"value": "success"
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"timeout": 15000
|
|
},
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpHeaderAuth"
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
300,
|
|
200
|
|
],
|
|
"id": "c688abdf-9b63-43b4-81da-7c81388b73f8",
|
|
"name": "Email Highlights",
|
|
"continueOnFail": true,
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "UPAHgUJVRqZQceL4",
|
|
"name": "n8n Public API (Failure Digest)"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "=https://www.googleapis.com/calendar/v3/calendars/primary/events?timeMin={{ $now.format('yyyy-MM-dd') }}T00:00:00-07:00&timeMax={{ $now.plus({days:1}).format('yyyy-MM-dd') }}T23:59:59-07:00&singleEvents=true&orderBy=startTime",
|
|
"authentication": "oAuth2",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
300,
|
|
350
|
|
],
|
|
"id": "d3c5a4ce-9f81-4da8-8dc8-7256bd96285b",
|
|
"name": "Calendar",
|
|
"credentials": {
|
|
"oAuth2Api": {
|
|
"id": "458fY4bs1z49OTeZ",
|
|
"name": "Google OAuth"
|
|
}
|
|
},
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "runOnceForAllItems",
|
|
"jsCode": "\nfunction getSafe(nodeName) {\n try {\n const items = $(nodeName).all();\n if (items && items.length > 0 && items[0].json) {\n return items[0].json;\n }\n } catch (e) {}\n return { error: 'Node failed or returned no data' };\n}\n\nconst weather = getSafe('Weather');\nconst swarmHealth = getSafe('Swarm Health');\nconst n8nHealth = getSafe('n8n Health');\nconst litellmHealth = getSafe('LiteLLM Health');\nconst emailData = getSafe('Email Highlights');\nconst calendar = getSafe('Calendar');\n\n// Extract weather summary\nlet weatherSummary = {};\nif (weather.current_condition && weather.current_condition[0]) {\n const c = weather.current_condition[0];\n weatherSummary = {\n temp_F: c.FeelsLikeF || c.temp_F,\n description: c.weatherDesc ? c.weatherDesc[0].value : 'unknown',\n humidity: c.humidity,\n wind_mph: c.windspeedMiles\n };\n} else {\n weatherSummary = { error: 'Weather data unavailable' };\n}\n\n// Count healthy/unhealthy containers\nlet infraSummary = { healthy: 0, unhealthy: 0, details: [] };\nif (Array.isArray(swarmHealth)) {\n for (const c of swarmHealth) {\n if (c.health === 'healthy' || c.status === 'running') {\n infraSummary.healthy++;\n } else {\n infraSummary.unhealthy++;\n }\n infraSummary.details.push({ name: c.name || c.Names, status: c.status, health: c.health });\n }\n} else if (swarmHealth.containers && Array.isArray(swarmHealth.containers)) {\n for (const c of swarmHealth.containers) {\n if (c.health === 'healthy' || c.status === 'running') {\n infraSummary.healthy++;\n } else {\n infraSummary.unhealthy++;\n }\n infraSummary.details.push({ name: c.name, status: c.status, health: c.health });\n }\n} else if (swarmHealth.error) {\n infraSummary = { error: 'Swarm health endpoint unavailable' };\n}\n\nconst n8nOk = (n8nHealth && !n8nHealth.error);\nconst litellmOk = (litellmHealth && !litellmHealth.error);\n\n// Extract email info from execution data\nlet emailHighlights = [];\nif (emailData && emailData.data && Array.isArray(emailData.data)) {\n for (const exec of emailData.data.slice(0, 5)) {\n emailHighlights.push({\n id: exec.id,\n finished: exec.stoppedAt || 'unknown'\n });\n }\n}\n\n// Calendar events\nlet calendarEvents = [];\nif (calendar && calendar.items && Array.isArray(calendar.items)) {\n for (const ev of calendar.items.slice(0, 10)) {\n calendarEvents.push({\n summary: ev.summary || '(no title)',\n start: (ev.start && (ev.start.dateTime || ev.start.date)) || 'unknown',\n end: (ev.end && (ev.end.dateTime || ev.end.date)) || 'unknown'\n });\n }\n}\n\nconst dataForLLM = {\n date: new Date().toISOString().split('T')[0],\n weather: weatherSummary,\n infrastructure: {\n swarm: infraSummary,\n n8n: n8nOk ? 'healthy' : 'unhealthy',\n litellm: litellmOk ? 'healthy' : 'unhealthy'\n },\n email: emailHighlights.length > 0 ? emailHighlights : [{ info: 'No recent email triage data' }],\n calendar: calendarEvents.length > 0 ? calendarEvents : [{ info: 'Calendar unavailable or no events today' }]\n};\n\nreturn [{ json: { dataJson: JSON.stringify(dataForLLM, null, 2) } }];\n"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
650,
|
|
0
|
|
],
|
|
"id": "1d2b39db-3649-4316-8ce9-b5c83c981017",
|
|
"name": "Merge Data"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "http://172.19.0.1:18806/v1/chat/completions",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={\"model\": \"gemma-4-26B-A4B-it-UD-IQ2_M.gguf\", \"messages\": [{\"role\": \"system\", \"content\": \"You are a personal morning brief assistant. Given raw data about weather, infrastructure, email, and calendar, produce a concise morning brief under 400 words. Use emojis for section headers. Format for Telegram using HTML (use <b> for bold, <code> for code). Keep it scannable and actionable. If any section data is missing or shows errors, note it briefly and move on.\"}, {\"role\": \"user\", \"content\": \"Here is today's data:\\n{{ $json.dataJson }}\"}], \"temperature\": 0.3, \"max_tokens\": 800}",
|
|
"options": {
|
|
"timeout": 60000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
950,
|
|
0
|
|
],
|
|
"id": "f2eb23d3-bf07-46d8-8556-2ba6a0185f5a",
|
|
"name": "Synthesize with LLM",
|
|
"continueOnFail": false
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "runOnceForAllItems",
|
|
"jsCode": "\nconst response = $input.first().json;\nlet brief = '';\n\nif (response.choices && response.choices[0] && response.choices[0].message) {\n brief = response.choices[0].message.content;\n} else if (typeof response === 'string') {\n brief = response;\n} else {\n brief = 'Error: LLM synthesis failed. Raw data: ' + JSON.stringify(response).substring(0, 500);\n}\n\nconst today = new Date().toISOString().split('T')[0];\nconst yamlFrontmatter = '---\\ncreated: ' + today + '\\ntype: morning-brief\\ntags: [daily, brief]\\n---\\n\\n';\n\nreturn [{\n json: {\n brief: brief,\n briefWithFrontmatter: yamlFrontmatter + '# Morning Brief - ' + today + '\\n\\n' + brief,\n date: today\n }\n}];\n"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1250,
|
|
0
|
|
],
|
|
"id": "0adac542-7d95-4002-a3e2-080442cfd9e3",
|
|
"name": "Extract Brief"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"chatId": "8367012007",
|
|
"text": "={{ $json.brief }}",
|
|
"additionalFields": {
|
|
"parse_mode": "HTML"
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.telegram",
|
|
"typeVersion": 1.2,
|
|
"position": [
|
|
1550,
|
|
-150
|
|
],
|
|
"id": "8242ada9-20c8-4689-b00c-3cd2787b2eb5",
|
|
"name": "Send Telegram",
|
|
"credentials": {
|
|
"telegramApi": {
|
|
"id": "aox4dyIWVSRdcH5z",
|
|
"name": "Telegram Bot (OpenClaw)"
|
|
}
|
|
},
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "PUT",
|
|
"url": "=http://172.19.0.1:27123/vault/Notes/{{ $json.date }} Morning Brief.md",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "text/markdown"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"contentType": "raw",
|
|
"rawContentType": "text/markdown",
|
|
"body": "={{ $json.briefWithFrontmatter }}",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
1550,
|
|
150
|
|
],
|
|
"id": "0f1fd6a2-86c0-4d3f-a948-32ce701d9f9f",
|
|
"name": "Save to Obsidian",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "465Swz2b71O2KRAK",
|
|
"name": "Obsidian Local REST API"
|
|
}
|
|
},
|
|
"continueOnFail": true
|
|
}
|
|
],
|
|
"connections": {
|
|
"Daily 06:30 PT": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Weather",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Swarm Health",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "n8n Health",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "LiteLLM Health",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Email Highlights",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Calendar",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Weather": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Swarm Health": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"n8n Health": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"LiteLLM Health": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Email Highlights": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Calendar": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Merge Data": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Synthesize with LLM",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Synthesize with LLM": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Extract Brief",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Extract Brief": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Send Telegram",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Save to Obsidian",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"authors": "will will",
|
|
"name": null,
|
|
"description": null,
|
|
"autosaved": false,
|
|
"workflowPublishHistory": [
|
|
{
|
|
"createdAt": "2026-05-14T00:21:08.466Z",
|
|
"id": 1493,
|
|
"workflowId": "g3IdGZCK1EtTsv9T",
|
|
"versionId": "c23946c7-29cf-42dc-985c-c1b818b8d010",
|
|
"event": "activated",
|
|
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
|
}
|
|
]
|
|
}
|
|
}
|