diff --git a/swarm-common/n8n-workflows/75JCevkdgkyCr2qH.json b/swarm-common/n8n-workflows/75JCevkdgkyCr2qH.json new file mode 100644 index 0000000..af6047f --- /dev/null +++ b/swarm-common/n8n-workflows/75JCevkdgkyCr2qH.json @@ -0,0 +1,95 @@ +{ + "updatedAt": "2026-05-01T20:02:33.035Z", + "createdAt": "2026-03-27T23:10:47.862Z", + "id": "75JCevkdgkyCr2qH", + "name": "Nightly Obsidian Vault Sync", + "description": null, + "active": false, + "isArchived": false, + "nodes": [ + { + "id": "schedule-node", + "name": "Schedule Trigger", + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.2, + "position": [ + 240, + 300 + ], + "parameters": { + "rule": { + "interval": [ + { + "field": "cronExpression", + "expression": "45 23 * * *" + } + ] + } + } + }, + { + "id": "nightly-sync", + "name": "Generate Nightly Vault Sync", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 520, + 300 + ], + "parameters": { + "mode": "runOnceForAllItems", + "jsCode": "\nconst http = this.helpers.httpRequest;\nconst OBS_BASE = 'http://192.168.153.130:27123';\nconst OBS_KEY = '698cfc8b00b93c41480e7e1cb84d77b75176be87507256a5fae9a5b53b5a20cb';\nconst MODEL = 'gemma-4-26B-A4B-it-UD-IQ2_M.gguf';\nconst TZ = 'America/Los_Angeles';\n\nconst enc = (p) => encodeURIComponent(p).replace(/%2F/g, '/');\nconst getDate = () => new Intl.DateTimeFormat('en-CA', { timeZone: TZ, year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date()).replaceAll('/', '-');\n\nconst notePaths = [\n 'Infrastructure/Architecture.md',\n 'Infrastructure/Automation/n8n Workflows.md',\n 'Infrastructure/Automation/Cron Jobs.md',\n 'Infrastructure/Services/Docker Services.md'\n];\n\nconst obsHeaders = {\n 'Authorization': `Bearer ${OBS_KEY}`,\n 'User-Agent': 'n8n-nightly-vault-sync'\n};\n\nconst notes = {};\nfor (const p of notePaths) {\n notes[p] = await http({\n method: 'GET',\n url: `${OBS_BASE}/vault/${enc(p)}`,\n headers: obsHeaders,\n timeout: 15000,\n });\n}\n\nconst n8nHealth = await http({\n method: 'GET',\n url: 'http://192.168.153.130:18808/healthz',\n json: true,\n timeout: 10000,\n});\n\nconst modelInfo = await http({\n method: 'GET',\n url: 'http://192.168.153.130:18806/v1/models',\n json: true,\n timeout: 10000,\n});\n\nconst prompt = [\n 'Write a concise nightly operational sync note for an Obsidian shared vault.',\n 'Return markdown body only. No code fences.',\n 'Start with heading: # Nightly Vault Sync',\n 'Then sections: ## Summary, ## Current State, ## Follow-ups',\n 'Keep it factual, low-noise, and under 250 words.',\n 'Mention that this is an automated nightly note generated by n8n using the local LLM.',\n '',\n 'Live health:',\n `- n8n health: ${JSON.stringify(n8nHealth)}`,\n `- local model ids: ${JSON.stringify((modelInfo.data || []).map(m => m.id))}`,\n '',\n 'Source note contents:',\n ...notePaths.flatMap(p => [`\\n--- ${p} ---`, String(notes[p]).slice(0, 1800)])\n].join('\\n');\n\nconst llm = await http({\n method: 'POST',\n url: 'http://192.168.153.130:18806/v1/chat/completions',\n headers: { 'Content-Type': 'application/json' },\n body: {\n model: MODEL,\n temperature: 0.2,\n max_tokens: 260,\n messages: [\n {\n role: 'system',\n content: 'You create concise nightly operations notes for an Obsidian vault. Prefer concrete facts from the provided sources. If there are unresolved drifts or follow-ups, mention them briefly. Do not invent incidents.'\n },\n { role: 'user', content: prompt }\n ]\n },\n json: true,\n timeout: 60000,\n});\n\nlet body = (((llm || {}).choices || [])[0] || {}).message?.content || '';\nbody = body.replace(/^```(?:markdown)?\\s*/i, '').replace(/```\\s*$/i, '').trim();\nif (!body) {\n body = '# Nightly Vault Sync\\n\\n## Summary\\n\\nAutomated nightly note ran, but the local LLM returned an empty response.\\n\\n## Current State\\n\\n- n8n health: ok\\n- local model endpoint reachable\\n\\n## Follow-ups\\n\\n- Check the local LLM response path if this repeats.';\n}\n\nconst date = getDate();\nconst notePath = `Notes/${date} Nightly Vault Sync.md`;\nconst full = `---\\ntitle: Nightly Vault Sync\\narea: infrastructure\\ntags: [infrastructure, obsidian, automation, nightly, assistant]\\ncreated: ${date}\\nupdated: ${date}\\nstatus: active\\nrelated: [[Infrastructure/Architecture]], [[Infrastructure/Automation/n8n Workflows]], [[Infrastructure/Automation/Cron Jobs]], [[Infrastructure/Services/Docker Services]]\\n---\\n\\n${body}\\n`;\n\nawait http({\n method: 'PUT',\n url: `${OBS_BASE}/vault/${enc(notePath)}`,\n headers: { ...obsHeaders, 'Content-Type': 'text/markdown' },\n body: full,\n timeout: 20000,\n});\n\nreturn [{ json: { notePath, model: MODEL, sourceNotes: notePaths, n8nHealth, modelCount: (modelInfo.data || []).length } }];\n" + } + } + ], + "connections": { + "Schedule Trigger": { + "main": [ + [ + { + "node": "Generate Nightly Vault Sync", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "callerPolicy": "workflowsFromSameOwner", + "availableInMCP": false + }, + "staticData": { + "node:Schedule Trigger": { + "recurrenceRules": [] + } + }, + "meta": null, + "pinData": null, + "versionId": "9585256a-29c2-444a-aa55-0eaf259f032d", + "activeVersionId": null, + "versionCounter": 66, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-03-27T23:10:47.871Z", + "createdAt": "2026-03-27T23:10:47.871Z", + "role": "workflow:owner", + "workflowId": "75JCevkdgkyCr2qH", + "projectId": "WGdp8QunI1tHpjXa", + "project": { + "updatedAt": "2026-03-11T21:08:10.005Z", + "createdAt": "2026-03-11T21:05:11.541Z", + "id": "WGdp8QunI1tHpjXa", + "name": "will will ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5" + } + } + ], + "tags": [], + "activeVersion": null +} \ No newline at end of file diff --git a/swarm-common/n8n-workflows/G9ylNbHbnJ6fWX2C.json b/swarm-common/n8n-workflows/G9ylNbHbnJ6fWX2C.json index 7962dd1..2916582 100644 --- a/swarm-common/n8n-workflows/G9ylNbHbnJ6fWX2C.json +++ b/swarm-common/n8n-workflows/G9ylNbHbnJ6fWX2C.json @@ -1 +1,534 @@ -{"updatedAt":"2026-05-12T17:06:07.191Z","createdAt":"2026-05-12T16:59:40.394Z","id":"G9ylNbHbnJ6fWX2C","name":"n8n Failure Digest","description":null,"active":true,"isArchived":false,"nodes":[{"parameters":{},"type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[-920,-120],"id":"a673b342-0e9e-44ae-a470-0a7ba93d135e","name":"Manual Trigger"},{"parameters":{"rule":{"interval":[{"field":"cronExpression","expression":"0 10 * * * *"}]}},"type":"n8n-nodes-base.scheduleTrigger","typeVersion":1.3,"position":[-920,80],"id":"6b8a395f-eadd-479d-980d-6f744f411c7d","name":"Hourly Schedule"},{"parameters":{"url":"http://127.0.0.1:5678/api/v1/executions?status=error&limit=100","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","options":{}},"type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[-660,0],"id":"afbf364e-4aca-4c7f-a43a-62a5e0b05d3b","name":"List Failed Executions","credentials":{"httpHeaderAuth":{"id":"UPAHgUJVRqZQceL4","name":"n8n Public API (Failure Digest)"}}},{"parameters":{"mode":"runOnceForAllItems","jsCode":"const data = Array.isArray($json.data) ? $json.data : [];\nconst windowMinutes = 65;\nconst cutoff = Date.now() - windowMinutes * 60 * 1000;\nconst selfName = 'n8n Failure Digest';\nconst seen = new Set();\nconst out = [];\nfor (const ex of data) {\n const status = String(ex.status || '').toLowerCase();\n if (!['error', 'crashed'].includes(status)) continue;\n const t = Date.parse(ex.stoppedAt || ex.startedAt || ex.createdAt || '');\n if (Number.isFinite(t) && t < cutoff) continue;\n const id = String(ex.id || '');\n if (!id || seen.has(id)) continue;\n seen.add(id);\n out.push({ json: { id, status, startedAt: ex.startedAt, stoppedAt: ex.stoppedAt, workflowId: ex.workflowId, windowMinutes } });\n}\nreturn out;"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[-420,0],"id":"00f4d7aa-3890-4eb4-bcb4-64afd7675767","name":"Recent Failure IDs"},{"parameters":{"url":"=http://127.0.0.1:5678/api/v1/executions/{{$json.id}}?includeData=true","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","options":{}},"type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[-180,0],"id":"4de4125e-75d6-4896-93d1-1ce20dce2db8","name":"Fetch Failure Details","credentials":{"httpHeaderAuth":{"id":"UPAHgUJVRqZQceL4","name":"n8n Public API (Failure Digest)"}}},{"parameters":{"mode":"runOnceForAllItems","jsCode":"const items = $input.all();\nconst windowMinutes = 65;\nconst now = Date.now();\nconst selfName = 'n8n Failure Digest';\nfunction arr(v) { return Array.isArray(v) ? v : (v == null ? [] : [v]); }\nfunction msg(err) {\n if (!err) return 'Unknown error';\n return String(err.message || err.description || err.name || err.code || JSON.stringify(err)).trim() || 'Unknown error';\n}\nfunction errType(err) { return String(err?.name || err?.type || err?.code || err?.httpCode || 'Error'); }\nfunction sig(s) {\n return String(s).split('\\n')[0]\n .replace(/https?:\\/\\/\\S+/g, '')\n .replace(/[0-9a-f]{8,}/gi, '')\n .replace(/\\b\\d{4,}\\b/g, '')\n .slice(0, 180);\n}\nfunction findErr(ex) {\n const rd = ex.data?.resultData || {};\n if (rd.error) return { node: rd.error.node?.name || rd.error.node || rd.lastNodeExecuted || 'unknown', error: rd.error };\n const runData = rd.runData || {};\n for (const [nodeName, attempts] of Object.entries(runData)) {\n for (const attempt of arr(attempts).slice().reverse()) {\n if (attempt?.error) return { node: nodeName, error: attempt.error };\n }\n }\n return { node: rd.lastNodeExecuted || 'unknown', error: ex.error || {} };\n}\nconst failures = [];\nfor (const item of items) {\n const ex = item.json || {};\n const workflowName = ex.workflowData?.name || ex.workflow?.name || `Workflow ${ex.workflowId || 'unknown'}`;\n if (workflowName === selfName) continue;\n const found = findErr(ex);\n const message = msg(found.error);\n const when = ex.stoppedAt || ex.startedAt || ex.createdAt || new Date(now).toISOString();\n failures.push({\n id: ex.id,\n workflowId: ex.workflowId || ex.workflowData?.id || 'unknown',\n workflowName,\n node: found.node || 'unknown',\n errorType: errType(found.error),\n message,\n signature: sig(message),\n when,\n status: ex.status || 'unknown',\n });\n}\nconst groups = new Map();\nfor (const f of failures) {\n const key = `${f.workflowId}\\u0000${f.node}\\u0000${f.errorType}\\u0000${f.signature}`;\n if (!groups.has(key)) groups.set(key, { workflowName: f.workflowName, workflowId: f.workflowId, node: f.node, errorType: f.errorType, signature: f.signature, count: 0, ids: [], latest: f.when });\n const g = groups.get(key);\n g.count++;\n if (g.ids.length < 8) g.ids.push(f.id);\n if (String(f.when) > String(g.latest)) g.latest = f.when;\n}\nconst sorted = [...groups.values()].sort((a,b) => b.count - a.count || String(b.latest).localeCompare(String(a.latest))).slice(0, 12);\nif (!sorted.length) return [];\nconst lines = [];\nlines.push(`🚨 n8n Failure Digest: ${failures.length} failed execution(s) in the last ${windowMinutes} min`);\nlines.push('');\nsorted.forEach((g, i) => {\n lines.push(`${i+1}. ${g.workflowName}`);\n lines.push(` Node: ${g.node}`);\n lines.push(` ${g.count}x ${g.errorType}: ${g.signature}`);\n lines.push(` Execs: ${g.ids.join(', ')} | latest ${g.latest}`);\n});\nlines.push('');\nlines.push('Open n8n: http://127.0.0.1:18808');\nreturn [{ json: { text: lines.join('\\n'), totalFailures: failures.length, groups: sorted, generatedAt: new Date(now).toISOString() } }];"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[80,0],"id":"f6b4eab8-7017-43e6-97c8-dce63873e097","name":"Build Digest"},{"parameters":{"chatId":"8367012007","text":"={{ $json.text }}","additionalFields":{"parse_mode":"Markdown"}},"type":"n8n-nodes-base.telegram","typeVersion":1,"position":[340,0],"id":"cf49d05d-5d81-404b-a751-ce56794985a9","name":"Send Telegram Digest","credentials":{"telegramApi":{"id":"aox4dyIWVSRdcH5z","name":"Telegram Bot (OpenClaw)"}}},{"parameters":{"method":"POST","url":"https://discord.com/api/v10/channels/1494453542243532932/messages","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify({ content: $json.text.substring(0, 2000) }) }}","options":{"response":{"response":{"responseFormat":"text"}}}},"type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[340,200],"id":"6c3086e4-0869-4003-94c3-66b4975f94e9","name":"Send Discord Digest","credentials":{"httpHeaderAuth":{"id":"UgPqYcoCNNIgr55m","name":"Discord Bot Auth"}}}],"connections":{"Manual Trigger":{"main":[[{"node":"List Failed Executions","type":"main","index":0}]]},"Hourly Schedule":{"main":[[{"node":"List Failed Executions","type":"main","index":0}]]},"List Failed Executions":{"main":[[{"node":"Recent Failure IDs","type":"main","index":0}]]},"Recent Failure IDs":{"main":[[{"node":"Fetch Failure Details","type":"main","index":0}]]},"Fetch Failure Details":{"main":[[{"node":"Build Digest","type":"main","index":0}]]},"Build Digest":{"main":[[{"node":"Send Telegram Digest","type":"main","index":0},{"node":"Send Discord Digest","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","timezone":"America/Los_Angeles","saveDataErrorExecution":"all","saveDataSuccessExecution":"none","callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":{"node:Hourly Schedule":{"recurrenceRules":[]}},"meta":null,"pinData":null,"versionId":"c198c473-6fed-4fa8-b203-9465bb084e89","activeVersionId":"c198c473-6fed-4fa8-b203-9465bb084e89","versionCounter":19,"triggerCount":1,"shared":[{"updatedAt":"2026-05-12T16:59:40.395Z","createdAt":"2026-05-12T16:59:40.395Z","role":"workflow:owner","workflowId":"G9ylNbHbnJ6fWX2C","projectId":"WGdp8QunI1tHpjXa","project":{"updatedAt":"2026-03-11T21:08:10.005Z","createdAt":"2026-03-11T21:05:11.541Z","id":"WGdp8QunI1tHpjXa","name":"will will ","type":"personal","icon":null,"description":null,"creatorId":"5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"}}],"tags":[],"activeVersion":{"updatedAt":"2026-05-13T21:29:18.280Z","createdAt":"2026-05-13T21:29:18.280Z","versionId":"c198c473-6fed-4fa8-b203-9465bb084e89","workflowId":"G9ylNbHbnJ6fWX2C","nodes":[{"parameters":{},"type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[-920,-120],"id":"a673b342-0e9e-44ae-a470-0a7ba93d135e","name":"Manual Trigger"},{"parameters":{"rule":{"interval":[{"field":"cronExpression","expression":"0 10 * * * *"}]}},"type":"n8n-nodes-base.scheduleTrigger","typeVersion":1.3,"position":[-920,80],"id":"6b8a395f-eadd-479d-980d-6f744f411c7d","name":"Hourly Schedule"},{"parameters":{"url":"http://127.0.0.1:5678/api/v1/executions?status=error&limit=100","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","options":{}},"type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[-660,0],"id":"afbf364e-4aca-4c7f-a43a-62a5e0b05d3b","name":"List Failed Executions","credentials":{"httpHeaderAuth":{"id":"UPAHgUJVRqZQceL4","name":"n8n Public API (Failure Digest)"}}},{"parameters":{"mode":"runOnceForAllItems","jsCode":"const data = Array.isArray($json.data) ? $json.data : [];\nconst windowMinutes = 65;\nconst cutoff = Date.now() - windowMinutes * 60 * 1000;\nconst selfName = 'n8n Failure Digest';\nconst seen = new Set();\nconst out = [];\nfor (const ex of data) {\n const status = String(ex.status || '').toLowerCase();\n if (!['error', 'crashed'].includes(status)) continue;\n const t = Date.parse(ex.stoppedAt || ex.startedAt || ex.createdAt || '');\n if (Number.isFinite(t) && t < cutoff) continue;\n const id = String(ex.id || '');\n if (!id || seen.has(id)) continue;\n seen.add(id);\n out.push({ json: { id, status, startedAt: ex.startedAt, stoppedAt: ex.stoppedAt, workflowId: ex.workflowId, windowMinutes } });\n}\nreturn out;"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[-420,0],"id":"00f4d7aa-3890-4eb4-bcb4-64afd7675767","name":"Recent Failure IDs"},{"parameters":{"url":"=http://127.0.0.1:5678/api/v1/executions/{{$json.id}}?includeData=true","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","options":{}},"type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[-180,0],"id":"4de4125e-75d6-4896-93d1-1ce20dce2db8","name":"Fetch Failure Details","credentials":{"httpHeaderAuth":{"id":"UPAHgUJVRqZQceL4","name":"n8n Public API (Failure Digest)"}}},{"parameters":{"mode":"runOnceForAllItems","jsCode":"const items = $input.all();\nconst windowMinutes = 65;\nconst now = Date.now();\nconst selfName = 'n8n Failure Digest';\nfunction arr(v) { return Array.isArray(v) ? v : (v == null ? [] : [v]); }\nfunction msg(err) {\n if (!err) return 'Unknown error';\n return String(err.message || err.description || err.name || err.code || JSON.stringify(err)).trim() || 'Unknown error';\n}\nfunction errType(err) { return String(err?.name || err?.type || err?.code || err?.httpCode || 'Error'); }\nfunction sig(s) {\n return String(s).split('\\n')[0]\n .replace(/https?:\\/\\/\\S+/g, '')\n .replace(/[0-9a-f]{8,}/gi, '')\n .replace(/\\b\\d{4,}\\b/g, '')\n .slice(0, 180);\n}\nfunction findErr(ex) {\n const rd = ex.data?.resultData || {};\n if (rd.error) return { node: rd.error.node?.name || rd.error.node || rd.lastNodeExecuted || 'unknown', error: rd.error };\n const runData = rd.runData || {};\n for (const [nodeName, attempts] of Object.entries(runData)) {\n for (const attempt of arr(attempts).slice().reverse()) {\n if (attempt?.error) return { node: nodeName, error: attempt.error };\n }\n }\n return { node: rd.lastNodeExecuted || 'unknown', error: ex.error || {} };\n}\nconst failures = [];\nfor (const item of items) {\n const ex = item.json || {};\n const workflowName = ex.workflowData?.name || ex.workflow?.name || `Workflow ${ex.workflowId || 'unknown'}`;\n if (workflowName === selfName) continue;\n const found = findErr(ex);\n const message = msg(found.error);\n const when = ex.stoppedAt || ex.startedAt || ex.createdAt || new Date(now).toISOString();\n failures.push({\n id: ex.id,\n workflowId: ex.workflowId || ex.workflowData?.id || 'unknown',\n workflowName,\n node: found.node || 'unknown',\n errorType: errType(found.error),\n message,\n signature: sig(message),\n when,\n status: ex.status || 'unknown',\n });\n}\nconst groups = new Map();\nfor (const f of failures) {\n const key = `${f.workflowId}\\u0000${f.node}\\u0000${f.errorType}\\u0000${f.signature}`;\n if (!groups.has(key)) groups.set(key, { workflowName: f.workflowName, workflowId: f.workflowId, node: f.node, errorType: f.errorType, signature: f.signature, count: 0, ids: [], latest: f.when });\n const g = groups.get(key);\n g.count++;\n if (g.ids.length < 8) g.ids.push(f.id);\n if (String(f.when) > String(g.latest)) g.latest = f.when;\n}\nconst sorted = [...groups.values()].sort((a,b) => b.count - a.count || String(b.latest).localeCompare(String(a.latest))).slice(0, 12);\nif (!sorted.length) return [];\nconst lines = [];\nlines.push(`🚨 n8n Failure Digest: ${failures.length} failed execution(s) in the last ${windowMinutes} min`);\nlines.push('');\nsorted.forEach((g, i) => {\n lines.push(`${i+1}. ${g.workflowName}`);\n lines.push(` Node: ${g.node}`);\n lines.push(` ${g.count}x ${g.errorType}: ${g.signature}`);\n lines.push(` Execs: ${g.ids.join(', ')} | latest ${g.latest}`);\n});\nlines.push('');\nlines.push('Open n8n: http://127.0.0.1:18808');\nreturn [{ json: { text: lines.join('\\n'), totalFailures: failures.length, groups: sorted, generatedAt: new Date(now).toISOString() } }];"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[80,0],"id":"f6b4eab8-7017-43e6-97c8-dce63873e097","name":"Build Digest"},{"parameters":{"chatId":"8367012007","text":"={{ $json.text }}","additionalFields":{"parse_mode":"Markdown"}},"type":"n8n-nodes-base.telegram","typeVersion":1,"position":[340,0],"id":"cf49d05d-5d81-404b-a751-ce56794985a9","name":"Send Telegram Digest","credentials":{"telegramApi":{"id":"aox4dyIWVSRdcH5z","name":"Telegram Bot (OpenClaw)"}}},{"parameters":{"method":"POST","url":"https://discord.com/api/v10/channels/1494453542243532932/messages","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify({ content: $json.text.substring(0, 2000) }) }}","options":{"response":{"response":{"responseFormat":"text"}}}},"type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[340,200],"id":"6c3086e4-0869-4003-94c3-66b4975f94e9","name":"Send Discord Digest","credentials":{"httpHeaderAuth":{"id":"UgPqYcoCNNIgr55m","name":"Discord Bot Auth"}}}],"connections":{"Manual Trigger":{"main":[[{"node":"List Failed Executions","type":"main","index":0}]]},"Hourly Schedule":{"main":[[{"node":"List Failed Executions","type":"main","index":0}]]},"List Failed Executions":{"main":[[{"node":"Recent Failure IDs","type":"main","index":0}]]},"Recent Failure IDs":{"main":[[{"node":"Fetch Failure Details","type":"main","index":0}]]},"Fetch Failure Details":{"main":[[{"node":"Build Digest","type":"main","index":0}]]},"Build Digest":{"main":[[{"node":"Send Telegram Digest","type":"main","index":0},{"node":"Send Discord Digest","type":"main","index":0}]]}},"authors":"import","name":null,"description":null,"autosaved":false,"workflowPublishHistory":[{"createdAt":"2026-05-13T21:30:20.994Z","id":1429,"workflowId":"G9ylNbHbnJ6fWX2C","versionId":"c198c473-6fed-4fa8-b203-9465bb084e89","event":"activated","userId":"5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"}]}} \ No newline at end of file +{ + "updatedAt": "2026-05-12T17:06:07.191Z", + "createdAt": "2026-05-12T16:59:40.394Z", + "id": "G9ylNbHbnJ6fWX2C", + "name": "n8n Failure Digest", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + -920, + -120 + ], + "id": "a673b342-0e9e-44ae-a470-0a7ba93d135e", + "name": "Manual Trigger" + }, + { + "parameters": { + "rule": { + "interval": [ + { + "field": "cronExpression", + "expression": "0 10 * * * *" + } + ] + } + }, + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.3, + "position": [ + -920, + 80 + ], + "id": "6b8a395f-eadd-479d-980d-6f744f411c7d", + "name": "Hourly Schedule" + }, + { + "parameters": { + "url": "http://127.0.0.1:5678/api/v1/executions?status=error&limit=100", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -660, + 0 + ], + "id": "afbf364e-4aca-4c7f-a43a-62a5e0b05d3b", + "name": "List Failed Executions", + "credentials": { + "httpHeaderAuth": { + "id": "UPAHgUJVRqZQceL4", + "name": "n8n Public API (Failure Digest)" + } + } + }, + { + "parameters": { + "mode": "runOnceForAllItems", + "jsCode": "const data = Array.isArray($json.data) ? $json.data : [];\nconst windowMinutes = 65;\nconst cutoff = Date.now() - windowMinutes * 60 * 1000;\nconst selfName = 'n8n Failure Digest';\nconst seen = new Set();\nconst out = [];\nfor (const ex of data) {\n const status = String(ex.status || '').toLowerCase();\n if (!['error', 'crashed'].includes(status)) continue;\n const t = Date.parse(ex.stoppedAt || ex.startedAt || ex.createdAt || '');\n if (Number.isFinite(t) && t < cutoff) continue;\n const id = String(ex.id || '');\n if (!id || seen.has(id)) continue;\n seen.add(id);\n out.push({ json: { id, status, startedAt: ex.startedAt, stoppedAt: ex.stoppedAt, workflowId: ex.workflowId, windowMinutes } });\n}\nreturn out;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -420, + 0 + ], + "id": "00f4d7aa-3890-4eb4-bcb4-64afd7675767", + "name": "Recent Failure IDs" + }, + { + "parameters": { + "url": "=http://127.0.0.1:5678/api/v1/executions/{{$json.id}}?includeData=true", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -180, + 0 + ], + "id": "4de4125e-75d6-4896-93d1-1ce20dce2db8", + "name": "Fetch Failure Details", + "credentials": { + "httpHeaderAuth": { + "id": "UPAHgUJVRqZQceL4", + "name": "n8n Public API (Failure Digest)" + } + } + }, + { + "parameters": { + "mode": "runOnceForAllItems", + "jsCode": "const items = $input.all();\nconst windowMinutes = 65;\nconst now = Date.now();\nconst selfName = 'n8n Failure Digest';\nfunction arr(v) { return Array.isArray(v) ? v : (v == null ? [] : [v]); }\nfunction msg(err) {\n if (!err) return 'Unknown error';\n return String(err.message || err.description || err.name || err.code || JSON.stringify(err)).trim() || 'Unknown error';\n}\nfunction errType(err) { return String(err?.name || err?.type || err?.code || err?.httpCode || 'Error'); }\nfunction sig(s) {\n return String(s).split('\\n')[0]\n .replace(/https?:\\/\\/\\S+/g, '')\n .replace(/[0-9a-f]{8,}/gi, '')\n .replace(/\\b\\d{4,}\\b/g, '')\n .slice(0, 180);\n}\nfunction findErr(ex) {\n const rd = ex.data?.resultData || {};\n if (rd.error) return { node: rd.error.node?.name || rd.error.node || rd.lastNodeExecuted || 'unknown', error: rd.error };\n const runData = rd.runData || {};\n for (const [nodeName, attempts] of Object.entries(runData)) {\n for (const attempt of arr(attempts).slice().reverse()) {\n if (attempt?.error) return { node: nodeName, error: attempt.error };\n }\n }\n return { node: rd.lastNodeExecuted || 'unknown', error: ex.error || {} };\n}\nconst failures = [];\nfor (const item of items) {\n const ex = item.json || {};\n const workflowName = ex.workflowData?.name || ex.workflow?.name || `Workflow ${ex.workflowId || 'unknown'}`;\n if (workflowName === selfName) continue;\n const found = findErr(ex);\n const message = msg(found.error);\n const when = ex.stoppedAt || ex.startedAt || ex.createdAt || new Date(now).toISOString();\n failures.push({\n id: ex.id,\n workflowId: ex.workflowId || ex.workflowData?.id || 'unknown',\n workflowName,\n node: found.node || 'unknown',\n errorType: errType(found.error),\n message,\n signature: sig(message),\n when,\n status: ex.status || 'unknown',\n });\n}\nconst groups = new Map();\nfor (const f of failures) {\n const key = `${f.workflowId}\\u0000${f.node}\\u0000${f.errorType}\\u0000${f.signature}`;\n if (!groups.has(key)) groups.set(key, { workflowName: f.workflowName, workflowId: f.workflowId, node: f.node, errorType: f.errorType, signature: f.signature, count: 0, ids: [], latest: f.when });\n const g = groups.get(key);\n g.count++;\n if (g.ids.length < 8) g.ids.push(f.id);\n if (String(f.when) > String(g.latest)) g.latest = f.when;\n}\nconst sorted = [...groups.values()].sort((a,b) => b.count - a.count || String(b.latest).localeCompare(String(a.latest))).slice(0, 12);\nif (!sorted.length) return [];\nconst lines = [];\nlines.push(`\ud83d\udea8 n8n Failure Digest: ${failures.length} failed execution(s) in the last ${windowMinutes} min`);\nlines.push('');\nsorted.forEach((g, i) => {\n lines.push(`${i+1}. ${g.workflowName}`);\n lines.push(` Node: ${g.node}`);\n lines.push(` ${g.count}x ${g.errorType}: ${g.signature}`);\n lines.push(` Execs: ${g.ids.join(', ')} | latest ${g.latest}`);\n});\nlines.push('');\nlines.push('Open n8n: http://127.0.0.1:18808');\nreturn [{ json: { text: lines.join('\\n'), totalFailures: failures.length, groups: sorted, generatedAt: new Date(now).toISOString() } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 80, + 0 + ], + "id": "f6b4eab8-7017-43e6-97c8-dce63873e097", + "name": "Build Digest" + }, + { + "parameters": { + "chatId": "8367012007", + "text": "={{ $json.text }}", + "additionalFields": { + "parse_mode": "Markdown" + } + }, + "type": "n8n-nodes-base.telegram", + "typeVersion": 1, + "position": [ + 340, + 0 + ], + "id": "cf49d05d-5d81-404b-a751-ce56794985a9", + "name": "Send Telegram Digest", + "credentials": { + "telegramApi": { + "id": "aox4dyIWVSRdcH5z", + "name": "Telegram Bot (OpenClaw)" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://discord.com/api/v10/channels/1494453542243532932/messages", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ content: $json.text.substring(0, 2000) }) }}", + "options": { + "response": { + "response": { + "responseFormat": "text" + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 340, + 200 + ], + "id": "6c3086e4-0869-4003-94c3-66b4975f94e9", + "name": "Send Discord Digest", + "credentials": { + "httpHeaderAuth": { + "id": "UgPqYcoCNNIgr55m", + "name": "Discord Bot Auth" + } + } + } + ], + "connections": { + "Manual Trigger": { + "main": [ + [ + { + "node": "List Failed Executions", + "type": "main", + "index": 0 + } + ] + ] + }, + "Hourly Schedule": { + "main": [ + [ + { + "node": "List Failed Executions", + "type": "main", + "index": 0 + } + ] + ] + }, + "List Failed Executions": { + "main": [ + [ + { + "node": "Recent Failure IDs", + "type": "main", + "index": 0 + } + ] + ] + }, + "Recent Failure IDs": { + "main": [ + [ + { + "node": "Fetch Failure Details", + "type": "main", + "index": 0 + } + ] + ] + }, + "Fetch Failure Details": { + "main": [ + [ + { + "node": "Build Digest", + "type": "main", + "index": 0 + } + ] + ] + }, + "Build Digest": { + "main": [ + [ + { + "node": "Send Telegram Digest", + "type": "main", + "index": 0 + }, + { + "node": "Send Discord Digest", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "timezone": "America/Los_Angeles", + "saveDataErrorExecution": "all", + "saveDataSuccessExecution": "none", + "callerPolicy": "workflowsFromSameOwner", + "availableInMCP": false + }, + "staticData": { + "node:Hourly Schedule": { + "recurrenceRules": [] + } + }, + "meta": null, + "pinData": null, + "versionId": "c198c473-6fed-4fa8-b203-9465bb084e89", + "activeVersionId": "c198c473-6fed-4fa8-b203-9465bb084e89", + "versionCounter": 19, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-05-12T16:59:40.395Z", + "createdAt": "2026-05-12T16:59:40.395Z", + "role": "workflow:owner", + "workflowId": "G9ylNbHbnJ6fWX2C", + "projectId": "WGdp8QunI1tHpjXa", + "project": { + "updatedAt": "2026-03-11T21:08:10.005Z", + "createdAt": "2026-03-11T21:05:11.541Z", + "id": "WGdp8QunI1tHpjXa", + "name": "will will ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5" + } + } + ], + "tags": [], + "activeVersion": { + "updatedAt": "2026-05-13T21:29:18.280Z", + "createdAt": "2026-05-13T21:29:18.280Z", + "versionId": "c198c473-6fed-4fa8-b203-9465bb084e89", + "workflowId": "G9ylNbHbnJ6fWX2C", + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + -920, + -120 + ], + "id": "a673b342-0e9e-44ae-a470-0a7ba93d135e", + "name": "Manual Trigger" + }, + { + "parameters": { + "rule": { + "interval": [ + { + "field": "cronExpression", + "expression": "0 10 * * * *" + } + ] + } + }, + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.3, + "position": [ + -920, + 80 + ], + "id": "6b8a395f-eadd-479d-980d-6f744f411c7d", + "name": "Hourly Schedule" + }, + { + "parameters": { + "url": "http://127.0.0.1:5678/api/v1/executions?status=error&limit=100", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -660, + 0 + ], + "id": "afbf364e-4aca-4c7f-a43a-62a5e0b05d3b", + "name": "List Failed Executions", + "credentials": { + "httpHeaderAuth": { + "id": "UPAHgUJVRqZQceL4", + "name": "n8n Public API (Failure Digest)" + } + } + }, + { + "parameters": { + "mode": "runOnceForAllItems", + "jsCode": "const data = Array.isArray($json.data) ? $json.data : [];\nconst windowMinutes = 65;\nconst cutoff = Date.now() - windowMinutes * 60 * 1000;\nconst selfName = 'n8n Failure Digest';\nconst seen = new Set();\nconst out = [];\nfor (const ex of data) {\n const status = String(ex.status || '').toLowerCase();\n if (!['error', 'crashed'].includes(status)) continue;\n const t = Date.parse(ex.stoppedAt || ex.startedAt || ex.createdAt || '');\n if (Number.isFinite(t) && t < cutoff) continue;\n const id = String(ex.id || '');\n if (!id || seen.has(id)) continue;\n seen.add(id);\n out.push({ json: { id, status, startedAt: ex.startedAt, stoppedAt: ex.stoppedAt, workflowId: ex.workflowId, windowMinutes } });\n}\nreturn out;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -420, + 0 + ], + "id": "00f4d7aa-3890-4eb4-bcb4-64afd7675767", + "name": "Recent Failure IDs" + }, + { + "parameters": { + "url": "=http://127.0.0.1:5678/api/v1/executions/{{$json.id}}?includeData=true", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -180, + 0 + ], + "id": "4de4125e-75d6-4896-93d1-1ce20dce2db8", + "name": "Fetch Failure Details", + "credentials": { + "httpHeaderAuth": { + "id": "UPAHgUJVRqZQceL4", + "name": "n8n Public API (Failure Digest)" + } + } + }, + { + "parameters": { + "mode": "runOnceForAllItems", + "jsCode": "const items = $input.all();\nconst windowMinutes = 65;\nconst now = Date.now();\nconst selfName = 'n8n Failure Digest';\nfunction arr(v) { return Array.isArray(v) ? v : (v == null ? [] : [v]); }\nfunction msg(err) {\n if (!err) return 'Unknown error';\n return String(err.message || err.description || err.name || err.code || JSON.stringify(err)).trim() || 'Unknown error';\n}\nfunction errType(err) { return String(err?.name || err?.type || err?.code || err?.httpCode || 'Error'); }\nfunction sig(s) {\n return String(s).split('\\n')[0]\n .replace(/https?:\\/\\/\\S+/g, '')\n .replace(/[0-9a-f]{8,}/gi, '')\n .replace(/\\b\\d{4,}\\b/g, '')\n .slice(0, 180);\n}\nfunction findErr(ex) {\n const rd = ex.data?.resultData || {};\n if (rd.error) return { node: rd.error.node?.name || rd.error.node || rd.lastNodeExecuted || 'unknown', error: rd.error };\n const runData = rd.runData || {};\n for (const [nodeName, attempts] of Object.entries(runData)) {\n for (const attempt of arr(attempts).slice().reverse()) {\n if (attempt?.error) return { node: nodeName, error: attempt.error };\n }\n }\n return { node: rd.lastNodeExecuted || 'unknown', error: ex.error || {} };\n}\nconst failures = [];\nfor (const item of items) {\n const ex = item.json || {};\n const workflowName = ex.workflowData?.name || ex.workflow?.name || `Workflow ${ex.workflowId || 'unknown'}`;\n if (workflowName === selfName) continue;\n const found = findErr(ex);\n const message = msg(found.error);\n const when = ex.stoppedAt || ex.startedAt || ex.createdAt || new Date(now).toISOString();\n failures.push({\n id: ex.id,\n workflowId: ex.workflowId || ex.workflowData?.id || 'unknown',\n workflowName,\n node: found.node || 'unknown',\n errorType: errType(found.error),\n message,\n signature: sig(message),\n when,\n status: ex.status || 'unknown',\n });\n}\nconst groups = new Map();\nfor (const f of failures) {\n const key = `${f.workflowId}\\u0000${f.node}\\u0000${f.errorType}\\u0000${f.signature}`;\n if (!groups.has(key)) groups.set(key, { workflowName: f.workflowName, workflowId: f.workflowId, node: f.node, errorType: f.errorType, signature: f.signature, count: 0, ids: [], latest: f.when });\n const g = groups.get(key);\n g.count++;\n if (g.ids.length < 8) g.ids.push(f.id);\n if (String(f.when) > String(g.latest)) g.latest = f.when;\n}\nconst sorted = [...groups.values()].sort((a,b) => b.count - a.count || String(b.latest).localeCompare(String(a.latest))).slice(0, 12);\nif (!sorted.length) return [];\nconst lines = [];\nlines.push(`\ud83d\udea8 n8n Failure Digest: ${failures.length} failed execution(s) in the last ${windowMinutes} min`);\nlines.push('');\nsorted.forEach((g, i) => {\n lines.push(`${i+1}. ${g.workflowName}`);\n lines.push(` Node: ${g.node}`);\n lines.push(` ${g.count}x ${g.errorType}: ${g.signature}`);\n lines.push(` Execs: ${g.ids.join(', ')} | latest ${g.latest}`);\n});\nlines.push('');\nlines.push('Open n8n: http://127.0.0.1:18808');\nreturn [{ json: { text: lines.join('\\n'), totalFailures: failures.length, groups: sorted, generatedAt: new Date(now).toISOString() } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 80, + 0 + ], + "id": "f6b4eab8-7017-43e6-97c8-dce63873e097", + "name": "Build Digest" + }, + { + "parameters": { + "chatId": "8367012007", + "text": "={{ $json.text }}", + "additionalFields": { + "parse_mode": "Markdown" + } + }, + "type": "n8n-nodes-base.telegram", + "typeVersion": 1, + "position": [ + 340, + 0 + ], + "id": "cf49d05d-5d81-404b-a751-ce56794985a9", + "name": "Send Telegram Digest", + "credentials": { + "telegramApi": { + "id": "aox4dyIWVSRdcH5z", + "name": "Telegram Bot (OpenClaw)" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://discord.com/api/v10/channels/1494453542243532932/messages", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ content: $json.text.substring(0, 2000) }) }}", + "options": { + "response": { + "response": { + "responseFormat": "text" + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 340, + 200 + ], + "id": "6c3086e4-0869-4003-94c3-66b4975f94e9", + "name": "Send Discord Digest", + "credentials": { + "httpHeaderAuth": { + "id": "UgPqYcoCNNIgr55m", + "name": "Discord Bot Auth" + } + } + } + ], + "connections": { + "Manual Trigger": { + "main": [ + [ + { + "node": "List Failed Executions", + "type": "main", + "index": 0 + } + ] + ] + }, + "Hourly Schedule": { + "main": [ + [ + { + "node": "List Failed Executions", + "type": "main", + "index": 0 + } + ] + ] + }, + "List Failed Executions": { + "main": [ + [ + { + "node": "Recent Failure IDs", + "type": "main", + "index": 0 + } + ] + ] + }, + "Recent Failure IDs": { + "main": [ + [ + { + "node": "Fetch Failure Details", + "type": "main", + "index": 0 + } + ] + ] + }, + "Fetch Failure Details": { + "main": [ + [ + { + "node": "Build Digest", + "type": "main", + "index": 0 + } + ] + ] + }, + "Build Digest": { + "main": [ + [ + { + "node": "Send Telegram Digest", + "type": "main", + "index": 0 + }, + { + "node": "Send Discord Digest", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "authors": "import", + "name": null, + "description": null, + "autosaved": false, + "workflowPublishHistory": [ + { + "createdAt": "2026-05-13T21:30:20.994Z", + "id": 1429, + "workflowId": "G9ylNbHbnJ6fWX2C", + "versionId": "c198c473-6fed-4fa8-b203-9465bb084e89", + "event": "activated", + "userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5" + } + ] + } +} \ No newline at end of file diff --git a/swarm-common/n8n-workflows/PlZywwqL8MRNEAN6.json b/swarm-common/n8n-workflows/PlZywwqL8MRNEAN6.json new file mode 100644 index 0000000..310e2e8 --- /dev/null +++ b/swarm-common/n8n-workflows/PlZywwqL8MRNEAN6.json @@ -0,0 +1,839 @@ +{ + "updatedAt": "2026-05-13T21:40:33.847Z", + "createdAt": "2026-05-13T21:40:33.847Z", + "id": "PlZywwqL8MRNEAN6", + "name": "Evening Digest", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "rule": { + "interval": [ + { + "field": "cronExpression", + "expression": "0 21 * * *" + } + ] + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000001", + "name": "Daily 9PM Schedule", + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.2, + "position": [ + 0, + 0 + ], + "onError": "continueRegularOutput" + }, + { + "parameters": { + "method": "GET", + "url": "http://127.0.0.1:5678/api/v1/executions?status=success&limit=100", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000002", + "name": "n8n Success Executions", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 240, + -200 + ], + "onError": "continueRegularOutput", + "credentials": { + "httpHeaderAuth": { + "id": "UPAHgUJVRqZQceL4", + "name": "n8n Public API (Failure Digest)" + } + } + }, + { + "parameters": { + "method": "GET", + "url": "http://127.0.0.1:5678/api/v1/executions?status=error&limit=50", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000003", + "name": "n8n Failed Executions", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 240, + 0 + ], + "onError": "continueRegularOutput", + "credentials": { + "httpHeaderAuth": { + "id": "UPAHgUJVRqZQceL4", + "name": "n8n Public API (Failure Digest)" + } + } + }, + { + "parameters": { + "method": "GET", + "url": "http://172.19.0.1:18809/health", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000004", + "name": "Swarm Health", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 240, + 200 + ], + "onError": "continueRegularOutput" + }, + { + "parameters": { + "method": "GET", + "url": "http://172.19.0.1:27123/vault/Notes/", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000005", + "name": "New Obsidian Notes", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 240, + 400 + ], + "onError": "continueRegularOutput", + "credentials": { + "httpHeaderAuth": { + "id": "465Swz2b71O2KRAK", + "name": "Obsidian Local REST API" + } + } + }, + { + "parameters": { + "mode": "runOnceForAllItems", + "jsCode": "// Aggregate all collection results into a structured summary\nconst data = {};\n\n// Process successful executions\ntry {\n const successItems = $input.first()?.json?.data || [];\n const successByWorkflow = {};\n let totalSuccess = 0;\n for (const item of successItems) {\n const wfName = item.workflowData?.name || item.workflowId || 'Unknown';\n successByWorkflow[wfName] = (successByWorkflow[wfName] || 0) + 1;\n totalSuccess++;\n }\n data.successExecutions = { total: totalSuccess, byWorkflow: successByWorkflow };\n} catch(e) {\n data.successExecutions = { total: 0, byWorkflow: {}, error: e.message };\n}\n\n// Process failed executions\ntry {\n // Failed executions come from a separate input\n const failNode = $node['n8n Failed Executions']?.json;\n const failItems = failNode?.data || [];\n const failures = [];\n let totalFail = 0;\n for (const item of failItems) {\n const wfName = item.workflowData?.name || item.workflowId || 'Unknown';\n failures.push({\n workflow: wfName,\n id: item.id,\n stoppedAt: item.stoppedAt\n });\n totalFail++;\n }\n data.failedExecutions = { total: totalFail, failures: failures };\n} catch(e) {\n data.failedExecutions = { total: 0, failures: [], error: e.message };\n}\n\n// Swarm health\ntry {\n data.swarmHealth = $node['Swarm Health']?.json || { status: 'unavailable' };\n} catch(e) {\n data.swarmHealth = { status: 'error', error: e.message };\n}\n\n// New Obsidian notes\ntry {\n const obsResult = $node['New Obsidian Notes']?.json;\n const allFiles = obsResult?.files || [];\n // Filter for today's date in filename\n const today = new Intl.DateTimeFormat('en-CA', {\n timeZone: 'America/Los_Angeles',\n year: 'numeric', month: '2-digit', day: '2-digit'\n }).format(new Date()).replaceAll('/', '-');\n const todayFiles = allFiles.filter(f => {\n const name = typeof f === 'string' ? f : (f.name || f.path || '');\n return name.includes(today);\n });\n data.newNotes = todayFiles.map(f => typeof f === 'string' ? f : (f.name || f.path || JSON.stringify(f)));\n} catch(e) {\n data.newNotes = [];\n data.notesError = e.message;\n}\n\ndata.date = new Intl.DateTimeFormat('en-CA', {\n timeZone: 'America/Los_Angeles',\n year: 'numeric', month: '2-digit', day: '2-digit'\n}).format(new Date()).replaceAll('/', '-');\n\ndata.summary = JSON.stringify(data, null, 2);\n\nreturn [{ json: data }];" + }, + "id": "a1b2c3d4-0001-4000-8000-000000000006", + "name": "Aggregate Data", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 500, + 100 + ], + "onError": "continueRegularOutput" + }, + { + "parameters": { + "method": "POST", + "url": "http://172.19.0.1:18806/v1/chat/completions", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ model: 'gemma-4-26B-A4B-it-UD-IQ2_M.gguf', temperature: 0.3, max_tokens: 800, messages: [{ role: 'system', content: 'You are an evening digest assistant. Given data about today\\'s automation runs, failures, new notes, and infrastructure health, produce a concise evening digest under 400 words. Use emojis for section headers. Format for Telegram/Markdown. Sections: \ud83d\udd27 Executions Summary, \u26a0\ufe0f Failures, \ud83d\udcdd New Notes, \ud83c\udfe5 Infrastructure Health, \ud83d\udccb Action Items. Be factual and concise.' }, { role: 'user', content: 'Here is today\\'s data:\\n' + $json.summary }] }) }}", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000007", + "name": "LLM Synthesis", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 740, + 100 + ], + "onError": "continueRegularOutput" + }, + { + "parameters": { + "mode": "runOnceForAllItems", + "jsCode": "// Extract LLM response text and prepare messages for Telegram/Discord/Obsidian\nlet text = '';\ntry {\n const llmResponse = $input.first()?.json;\n text = llmResponse?.choices?.[0]?.message?.content || '';\n // Strip code fences if present\n text = text.replace(/^```(?:markdown)?\\s*/i, '').replace(/```\\s*$/i, '').trim();\n} catch(e) {\n text = 'Evening digest generation encountered an error.';\n}\n\nif (!text) {\n text = '\ud83c\udf19 Evening Digest\\n\\nNo data collected today. All collection nodes may have failed.';\n}\n\n// Escape special chars for Telegram MarkdownV1\nlet telegramText = text;\n// Replace problematic markdown chars for Telegram\ntelegramText = telegramText.replace(/([_*\\[\\]()~`>#+\\-=|{}.!])/g, (m) => {\n // Keep basic markdown formatting\n if (['*', '_', '`'].includes(m)) return m;\n return '\\\\' + m;\n});\n\nconst today = new Intl.DateTimeFormat('en-CA', {\n timeZone: 'America/Los_Angeles',\n year: 'numeric', month: '2-digit', day: '2-digit'\n}).format(new Date()).replaceAll('/', '-');\n\nreturn [{\n json: {\n text: telegramText,\n discordText: text.substring(0, 2000),\n obsidianContent: `---\\ntitle: Evening Digest\\narea: infrastructure\\ntags: [infrastructure, digest, automation, daily, evening]\\ncreated: ${today}\\nupdated: ${today}\\nstatus: active\\n---\\n\\n# Evening Digest - ${today}\\n\\n${text}\\n`,\n notePath: `Notes/${today} Evening Digest.md`,\n date: today\n }\n}];" + }, + "id": "a1b2c3d4-0001-4000-8000-000000000008", + "name": "Prepare Messages", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 980, + 100 + ], + "onError": "continueRegularOutput" + }, + { + "parameters": { + "chatId": "8367012007", + "text": "={{ $json.text }}", + "additionalFields": { + "parse_mode": "Markdown" + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000009", + "name": "Send Telegram", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1, + "position": [ + 1220, + -100 + ], + "credentials": { + "telegramApi": { + "id": "aox4dyIWVSRdcH5z", + "name": "Telegram Bot (OpenClaw)" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://discord.com/api/v10/channels/1494453542243532932/messages", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ content: $json.discordText }) }}", + "options": { + "response": { + "response": { + "responseFormat": "text" + } + } + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000010", + "name": "Send Discord", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1220, + 100 + ], + "credentials": { + "httpHeaderAuth": { + "id": "UgPqYcoCNNIgr55m", + "name": "Discord Bot Auth" + } + } + }, + { + "parameters": { + "method": "PUT", + "url": "={{ 'http://172.19.0.1:27123/vault/' + encodeURIComponent($json.notePath).replace(/%2F/g, '/') }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "raw", + "rawContentType": "text/markdown", + "body": "={{ $json.obsidianContent }}", + "options": {} + }, + "id": "a1b2c3d4-0001-4000-8000-000000000011", + "name": "Save to Obsidian", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1220, + 300 + ], + "credentials": { + "httpHeaderAuth": { + "id": "465Swz2b71O2KRAK", + "name": "Obsidian Local REST API" + } + } + } + ], + "connections": { + "Daily 9PM Schedule": { + "main": [ + [ + { + "node": "n8n Success Executions", + "type": "main", + "index": 0 + }, + { + "node": "n8n Failed Executions", + "type": "main", + "index": 0 + }, + { + "node": "Swarm Health", + "type": "main", + "index": 0 + }, + { + "node": "New Obsidian Notes", + "type": "main", + "index": 0 + } + ] + ] + }, + "n8n Success Executions": { + "main": [ + [ + { + "node": "Aggregate Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "n8n Failed Executions": { + "main": [ + [ + { + "node": "Aggregate Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "Swarm Health": { + "main": [ + [ + { + "node": "Aggregate Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "New Obsidian Notes": { + "main": [ + [ + { + "node": "Aggregate Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "Aggregate Data": { + "main": [ + [ + { + "node": "LLM Synthesis", + "type": "main", + "index": 0 + } + ] + ] + }, + "LLM Synthesis": { + "main": [ + [ + { + "node": "Prepare Messages", + "type": "main", + "index": 0 + } + ] + ] + }, + "Prepare Messages": { + "main": [ + [ + { + "node": "Send Telegram", + "type": "main", + "index": 0 + }, + { + "node": "Send Discord", + "type": "main", + "index": 0 + }, + { + "node": "Save to Obsidian", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "callerPolicy": "workflowsFromSameOwner", + "availableInMCP": false + }, + "staticData": { + "node:Daily 9PM Schedule": { + "recurrenceRules": [] + } + }, + "meta": null, + "pinData": null, + "versionId": "afb71f4d-6ac3-434d-b659-de003d47c339", + "activeVersionId": "afb71f4d-6ac3-434d-b659-de003d47c339", + "versionCounter": 4, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-05-13T21:40:33.849Z", + "createdAt": "2026-05-13T21:40:33.849Z", + "role": "workflow:owner", + "workflowId": "PlZywwqL8MRNEAN6", + "projectId": "WGdp8QunI1tHpjXa", + "project": { + "updatedAt": "2026-03-11T21:08:10.005Z", + "createdAt": "2026-03-11T21:05:11.541Z", + "id": "WGdp8QunI1tHpjXa", + "name": "will will ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5" + } + } + ], + "tags": [], + "activeVersion": { + "updatedAt": "2026-05-13T21:40:33.854Z", + "createdAt": "2026-05-13T21:40:33.854Z", + "versionId": "afb71f4d-6ac3-434d-b659-de003d47c339", + "workflowId": "PlZywwqL8MRNEAN6", + "nodes": [ + { + "parameters": { + "rule": { + "interval": [ + { + "field": "cronExpression", + "expression": "0 21 * * *" + } + ] + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000001", + "name": "Daily 9PM Schedule", + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.2, + "position": [ + 0, + 0 + ], + "onError": "continueRegularOutput" + }, + { + "parameters": { + "method": "GET", + "url": "http://127.0.0.1:5678/api/v1/executions?status=success&limit=100", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000002", + "name": "n8n Success Executions", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 240, + -200 + ], + "onError": "continueRegularOutput", + "credentials": { + "httpHeaderAuth": { + "id": "UPAHgUJVRqZQceL4", + "name": "n8n Public API (Failure Digest)" + } + } + }, + { + "parameters": { + "method": "GET", + "url": "http://127.0.0.1:5678/api/v1/executions?status=error&limit=50", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000003", + "name": "n8n Failed Executions", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 240, + 0 + ], + "onError": "continueRegularOutput", + "credentials": { + "httpHeaderAuth": { + "id": "UPAHgUJVRqZQceL4", + "name": "n8n Public API (Failure Digest)" + } + } + }, + { + "parameters": { + "method": "GET", + "url": "http://172.19.0.1:18809/health", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000004", + "name": "Swarm Health", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 240, + 200 + ], + "onError": "continueRegularOutput" + }, + { + "parameters": { + "method": "GET", + "url": "http://172.19.0.1:27123/vault/Notes/", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000005", + "name": "New Obsidian Notes", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 240, + 400 + ], + "onError": "continueRegularOutput", + "credentials": { + "httpHeaderAuth": { + "id": "465Swz2b71O2KRAK", + "name": "Obsidian Local REST API" + } + } + }, + { + "parameters": { + "mode": "runOnceForAllItems", + "jsCode": "// Aggregate all collection results into a structured summary\nconst data = {};\n\n// Process successful executions\ntry {\n const successItems = $input.first()?.json?.data || [];\n const successByWorkflow = {};\n let totalSuccess = 0;\n for (const item of successItems) {\n const wfName = item.workflowData?.name || item.workflowId || 'Unknown';\n successByWorkflow[wfName] = (successByWorkflow[wfName] || 0) + 1;\n totalSuccess++;\n }\n data.successExecutions = { total: totalSuccess, byWorkflow: successByWorkflow };\n} catch(e) {\n data.successExecutions = { total: 0, byWorkflow: {}, error: e.message };\n}\n\n// Process failed executions\ntry {\n // Failed executions come from a separate input\n const failNode = $node['n8n Failed Executions']?.json;\n const failItems = failNode?.data || [];\n const failures = [];\n let totalFail = 0;\n for (const item of failItems) {\n const wfName = item.workflowData?.name || item.workflowId || 'Unknown';\n failures.push({\n workflow: wfName,\n id: item.id,\n stoppedAt: item.stoppedAt\n });\n totalFail++;\n }\n data.failedExecutions = { total: totalFail, failures: failures };\n} catch(e) {\n data.failedExecutions = { total: 0, failures: [], error: e.message };\n}\n\n// Swarm health\ntry {\n data.swarmHealth = $node['Swarm Health']?.json || { status: 'unavailable' };\n} catch(e) {\n data.swarmHealth = { status: 'error', error: e.message };\n}\n\n// New Obsidian notes\ntry {\n const obsResult = $node['New Obsidian Notes']?.json;\n const allFiles = obsResult?.files || [];\n // Filter for today's date in filename\n const today = new Intl.DateTimeFormat('en-CA', {\n timeZone: 'America/Los_Angeles',\n year: 'numeric', month: '2-digit', day: '2-digit'\n }).format(new Date()).replaceAll('/', '-');\n const todayFiles = allFiles.filter(f => {\n const name = typeof f === 'string' ? f : (f.name || f.path || '');\n return name.includes(today);\n });\n data.newNotes = todayFiles.map(f => typeof f === 'string' ? f : (f.name || f.path || JSON.stringify(f)));\n} catch(e) {\n data.newNotes = [];\n data.notesError = e.message;\n}\n\ndata.date = new Intl.DateTimeFormat('en-CA', {\n timeZone: 'America/Los_Angeles',\n year: 'numeric', month: '2-digit', day: '2-digit'\n}).format(new Date()).replaceAll('/', '-');\n\ndata.summary = JSON.stringify(data, null, 2);\n\nreturn [{ json: data }];" + }, + "id": "a1b2c3d4-0001-4000-8000-000000000006", + "name": "Aggregate Data", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 500, + 100 + ], + "onError": "continueRegularOutput" + }, + { + "parameters": { + "method": "POST", + "url": "http://172.19.0.1:18806/v1/chat/completions", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ model: 'gemma-4-26B-A4B-it-UD-IQ2_M.gguf', temperature: 0.3, max_tokens: 800, messages: [{ role: 'system', content: 'You are an evening digest assistant. Given data about today\\'s automation runs, failures, new notes, and infrastructure health, produce a concise evening digest under 400 words. Use emojis for section headers. Format for Telegram/Markdown. Sections: \ud83d\udd27 Executions Summary, \u26a0\ufe0f Failures, \ud83d\udcdd New Notes, \ud83c\udfe5 Infrastructure Health, \ud83d\udccb Action Items. Be factual and concise.' }, { role: 'user', content: 'Here is today\\'s data:\\n' + $json.summary }] }) }}", + "options": { + "response": { + "response": { + "responseFormat": "json" + } + } + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000007", + "name": "LLM Synthesis", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 740, + 100 + ], + "onError": "continueRegularOutput" + }, + { + "parameters": { + "mode": "runOnceForAllItems", + "jsCode": "// Extract LLM response text and prepare messages for Telegram/Discord/Obsidian\nlet text = '';\ntry {\n const llmResponse = $input.first()?.json;\n text = llmResponse?.choices?.[0]?.message?.content || '';\n // Strip code fences if present\n text = text.replace(/^```(?:markdown)?\\s*/i, '').replace(/```\\s*$/i, '').trim();\n} catch(e) {\n text = 'Evening digest generation encountered an error.';\n}\n\nif (!text) {\n text = '\ud83c\udf19 Evening Digest\\n\\nNo data collected today. All collection nodes may have failed.';\n}\n\n// Escape special chars for Telegram MarkdownV1\nlet telegramText = text;\n// Replace problematic markdown chars for Telegram\ntelegramText = telegramText.replace(/([_*\\[\\]()~`>#+\\-=|{}.!])/g, (m) => {\n // Keep basic markdown formatting\n if (['*', '_', '`'].includes(m)) return m;\n return '\\\\' + m;\n});\n\nconst today = new Intl.DateTimeFormat('en-CA', {\n timeZone: 'America/Los_Angeles',\n year: 'numeric', month: '2-digit', day: '2-digit'\n}).format(new Date()).replaceAll('/', '-');\n\nreturn [{\n json: {\n text: telegramText,\n discordText: text.substring(0, 2000),\n obsidianContent: `---\\ntitle: Evening Digest\\narea: infrastructure\\ntags: [infrastructure, digest, automation, daily, evening]\\ncreated: ${today}\\nupdated: ${today}\\nstatus: active\\n---\\n\\n# Evening Digest - ${today}\\n\\n${text}\\n`,\n notePath: `Notes/${today} Evening Digest.md`,\n date: today\n }\n}];" + }, + "id": "a1b2c3d4-0001-4000-8000-000000000008", + "name": "Prepare Messages", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 980, + 100 + ], + "onError": "continueRegularOutput" + }, + { + "parameters": { + "chatId": "8367012007", + "text": "={{ $json.text }}", + "additionalFields": { + "parse_mode": "Markdown" + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000009", + "name": "Send Telegram", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1, + "position": [ + 1220, + -100 + ], + "credentials": { + "telegramApi": { + "id": "aox4dyIWVSRdcH5z", + "name": "Telegram Bot (OpenClaw)" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://discord.com/api/v10/channels/1494453542243532932/messages", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ content: $json.discordText }) }}", + "options": { + "response": { + "response": { + "responseFormat": "text" + } + } + } + }, + "id": "a1b2c3d4-0001-4000-8000-000000000010", + "name": "Send Discord", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1220, + 100 + ], + "credentials": { + "httpHeaderAuth": { + "id": "UgPqYcoCNNIgr55m", + "name": "Discord Bot Auth" + } + } + }, + { + "parameters": { + "method": "PUT", + "url": "={{ 'http://172.19.0.1:27123/vault/' + encodeURIComponent($json.notePath).replace(/%2F/g, '/') }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "raw", + "rawContentType": "text/markdown", + "body": "={{ $json.obsidianContent }}", + "options": {} + }, + "id": "a1b2c3d4-0001-4000-8000-000000000011", + "name": "Save to Obsidian", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1220, + 300 + ], + "credentials": { + "httpHeaderAuth": { + "id": "465Swz2b71O2KRAK", + "name": "Obsidian Local REST API" + } + } + } + ], + "connections": { + "Daily 9PM Schedule": { + "main": [ + [ + { + "node": "n8n Success Executions", + "type": "main", + "index": 0 + }, + { + "node": "n8n Failed Executions", + "type": "main", + "index": 0 + }, + { + "node": "Swarm Health", + "type": "main", + "index": 0 + }, + { + "node": "New Obsidian Notes", + "type": "main", + "index": 0 + } + ] + ] + }, + "n8n Success Executions": { + "main": [ + [ + { + "node": "Aggregate Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "n8n Failed Executions": { + "main": [ + [ + { + "node": "Aggregate Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "Swarm Health": { + "main": [ + [ + { + "node": "Aggregate Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "New Obsidian Notes": { + "main": [ + [ + { + "node": "Aggregate Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "Aggregate Data": { + "main": [ + [ + { + "node": "LLM Synthesis", + "type": "main", + "index": 0 + } + ] + ] + }, + "LLM Synthesis": { + "main": [ + [ + { + "node": "Prepare Messages", + "type": "main", + "index": 0 + } + ] + ] + }, + "Prepare Messages": { + "main": [ + [ + { + "node": "Send Telegram", + "type": "main", + "index": 0 + }, + { + "node": "Send Discord", + "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-13T21:40:40.515Z", + "id": 1432, + "workflowId": "PlZywwqL8MRNEAN6", + "versionId": "afb71f4d-6ac3-434d-b659-de003d47c339", + "event": "activated", + "userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5" + } + ] + } +} \ No newline at end of file