fix(n8n): harden swarm automation workflows
This commit is contained in:
+2
-2
@@ -323,8 +323,8 @@ services:
|
||||
- N8N_PROTOCOL=http
|
||||
- N8N_EDITOR_BASE_URL=http://localhost:18808
|
||||
- WEBHOOK_URL=http://localhost:18808/
|
||||
- TZ=UTC
|
||||
- GENERIC_TIMEZONE=UTC
|
||||
- TZ=America/Los_Angeles
|
||||
- GENERIC_TIMEZONE=America/Los_Angeles
|
||||
- N8N_SECURE_COOKIE=false
|
||||
volumes:
|
||||
- n8n-agent-data:/home/node/.n8n
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,486 @@
|
||||
{
|
||||
"updatedAt": "2026-05-14T00:03:13.116Z",
|
||||
"createdAt": "2026-05-12T17:56:05.279Z",
|
||||
"id": "El1BHJZ56JlzhrRZ",
|
||||
"name": "Voice Memo Capture (Audio URL + Local Whisper)",
|
||||
"description": null,
|
||||
"active": true,
|
||||
"isArchived": false,
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "voice-memo",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
-980,
|
||||
0
|
||||
],
|
||||
"id": "9f1da0a8-32db-4e67-a6e4-18cf8b4d42ee",
|
||||
"name": "Webhook - Voice Memo",
|
||||
"webhookId": "06796590-13b3-4347-9582-1ac92719c95d"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const body = $json.body ?? $json;\n\nconst audio_url = String(body.audio_url || body.url || '').trim();\nconst telegram_file_id = String(body.telegram_file_id || body.file_id || '').trim();\nconst discord_audio_url = String(body.discord_audio_url || '').trim();\nconst audio_base64 = String(body.audio_base64 || '').trim();\nconst audio_format = String(body.audio_format || body.format || 'ogg').trim();\nconst language = String(body.language || 'en').trim();\nconst title = String(body.title || 'Voice Memo').trim();\nconst tags = Array.isArray(body.tags) ? body.tags : String(body.tags || 'voice,memo').split(',').map(s => s.trim()).filter(Boolean);\nconst include_tts = body.include_tts === true || body.tts_readback === true;\nconst voice = String(body.voice || body.tts_voice || 'af_heart').trim();\nif (!audio_url && !telegram_file_id && !discord_audio_url && !audio_base64) {\n throw new Error('POST JSON must include audio_url, telegram_file_id, discord_audio_url, or audio_base64');\n}\nreturn [{ json: { audio_url, telegram_file_id, discord_audio_url, audio_base64, audio_format, language, title, tags, include_tts, voice } }];"
|
||||
},
|
||||
"id": "vm-normalize-v2",
|
||||
"name": "Normalize Input",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-680,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://172.19.0.1:18813/process",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ audio_url: $json.audio_url, telegram_file_id: $json.telegram_file_id, discord_audio_url: $json.discord_audio_url, title: $json.title, tags: $json.tags, include_tts: $json.include_tts, voice: $json.voice }) }}",
|
||||
"options": {
|
||||
"timeout": 180000,
|
||||
"fullResponse": false
|
||||
}
|
||||
},
|
||||
"id": "vm-process-v2",
|
||||
"name": "Process Voice Memo",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-460,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const input = $('Normalize Input').first().json;\nconst proc = $input.first().json;\n\nfunction slugify(s) { return String(s || 'voice-memo').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 80) || 'voice-memo'; }\nfunction yaml(s) { return String(s ?? '').split('\\n').join(' ').replaceAll('\"', '\\\\\"'); }\n\nconst date = new Date(proc.created_at || Date.now());\nconst ymd = date.toISOString().slice(0,10);\nconst notePath = `Voice Memos/${ymd}-${slugify(proc.title || input.title)}.md`;\n\nconst title = proc.title || input.title || 'Voice Memo';\nconst tags = proc.tags || input.tags || ['voice', 'memo'];\nconst tagLines = tags.map(t => ` - ${yaml(t)}`).join('\\n');\nconst sourceType = proc.source_type || input.source || 'unknown';\nconst sourceUrl = input.source_url || '';\n\nlet audioNote = '';\nif (proc.tts_audio_url) {\n audioNote = `\\n## Audio Summary\\n\\n> Listen to the AI-generated summary: ${proc.tts_audio_url}\\n`;\n}\n\nconst markdown = `---\\ntitle: \"${yaml(title)}\"\\nsource: \"${yaml(sourceUrl)}\"\\nsource_type: \"${sourceType}\"\\ncreated: \"${date.toISOString()}\"\\ntags:\\n${tagLines}\\n---\\n\\n# ${title}\\n\\n## Summary\\n\\n${(proc.summary || '').trim()}\\n${audioNote}\\n## Transcript\\n\\n${proc.transcript || 'No transcript available.'}\\n`;\n\nreturn [{ json: { ...input, notePath, markdown, title, tts_audio_url: proc.tts_audio_url || null } }];\n"
|
||||
},
|
||||
"id": "vm-build-obsidian-v2",
|
||||
"name": "Build Obsidian Note",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-240,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "PUT",
|
||||
"url": "={{'http://172.19.0.1:27123/vault/' + encodeURIComponent($json.notePath).replace(/%2F/g, '/')}}",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "text/markdown"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "text/markdown",
|
||||
"body": "={{$json.markdown}}",
|
||||
"options": {
|
||||
"timeout": 30000
|
||||
},
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "vm-write-obsidian-v2",
|
||||
"name": "Write Note to Obsidian",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "465Swz2b71O2KRAK",
|
||||
"name": "Obsidian Local REST API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"chatId": "8367012007",
|
||||
"text": "={{ \"Voice memo captured (\" + $json.source_type + \"): \" + $json.title + \"\\nObsidian: \" + $json.notePath + ($json.tts_audio_url ? \"\\nAudio summary: \" + $json.tts_audio_url : \"\") }}",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
1160,
|
||||
-80
|
||||
],
|
||||
"id": "41bf5a55-2047-400a-87c7-44744a0f2a42",
|
||||
"name": "Send Telegram Notification",
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "aox4dyIWVSRdcH5z",
|
||||
"name": "Telegram Bot (OpenClaw)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ JSON.stringify({ ok: true, notePath: $json.notePath, title: $json.title, source_type: $json.source_type, tts_audio_url: $json.tts_audio_url || null }) }}"
|
||||
},
|
||||
"id": "vm-respond-v2",
|
||||
"name": "Respond",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
460,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Webhook - Voice Memo": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Normalize Input",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Normalize Input": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Process Voice Memo",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Process Voice Memo": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Obsidian Note",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Obsidian Note": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Write Note to Obsidian",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Write Note to Obsidian": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Telegram Notification",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Send Telegram Notification": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"timezone": "America/Los_Angeles",
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "none",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"staticData": null,
|
||||
"meta": null,
|
||||
"pinData": null,
|
||||
"versionId": "4511e901-afab-493e-9b17-99a9d9865147",
|
||||
"activeVersionId": "4511e901-afab-493e-9b17-99a9d9865147",
|
||||
"versionCounter": 38,
|
||||
"triggerCount": 1,
|
||||
"shared": [
|
||||
{
|
||||
"updatedAt": "2026-05-12T17:56:05.281Z",
|
||||
"createdAt": "2026-05-12T17:56:05.281Z",
|
||||
"role": "workflow:owner",
|
||||
"workflowId": "El1BHJZ56JlzhrRZ",
|
||||
"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:03:13.117Z",
|
||||
"createdAt": "2026-05-14T00:03:13.117Z",
|
||||
"versionId": "4511e901-afab-493e-9b17-99a9d9865147",
|
||||
"workflowId": "El1BHJZ56JlzhrRZ",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "voice-memo",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
-980,
|
||||
0
|
||||
],
|
||||
"id": "9f1da0a8-32db-4e67-a6e4-18cf8b4d42ee",
|
||||
"name": "Webhook - Voice Memo",
|
||||
"webhookId": "06796590-13b3-4347-9582-1ac92719c95d"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const body = $json.body ?? $json;\n\nconst audio_url = String(body.audio_url || body.url || '').trim();\nconst telegram_file_id = String(body.telegram_file_id || body.file_id || '').trim();\nconst discord_audio_url = String(body.discord_audio_url || '').trim();\nconst audio_base64 = String(body.audio_base64 || '').trim();\nconst audio_format = String(body.audio_format || body.format || 'ogg').trim();\nconst language = String(body.language || 'en').trim();\nconst title = String(body.title || 'Voice Memo').trim();\nconst tags = Array.isArray(body.tags) ? body.tags : String(body.tags || 'voice,memo').split(',').map(s => s.trim()).filter(Boolean);\nconst include_tts = body.include_tts === true || body.tts_readback === true;\nconst voice = String(body.voice || body.tts_voice || 'af_heart').trim();\nif (!audio_url && !telegram_file_id && !discord_audio_url && !audio_base64) {\n throw new Error('POST JSON must include audio_url, telegram_file_id, discord_audio_url, or audio_base64');\n}\nreturn [{ json: { audio_url, telegram_file_id, discord_audio_url, audio_base64, audio_format, language, title, tags, include_tts, voice } }];"
|
||||
},
|
||||
"id": "vm-normalize-v2",
|
||||
"name": "Normalize Input",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-680,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://172.19.0.1:18813/process",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ audio_url: $json.audio_url, telegram_file_id: $json.telegram_file_id, discord_audio_url: $json.discord_audio_url, title: $json.title, tags: $json.tags, include_tts: $json.include_tts, voice: $json.voice }) }}",
|
||||
"options": {
|
||||
"timeout": 180000,
|
||||
"fullResponse": false
|
||||
}
|
||||
},
|
||||
"id": "vm-process-v2",
|
||||
"name": "Process Voice Memo",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-460,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const input = $('Normalize Input').first().json;\nconst proc = $input.first().json;\n\nfunction slugify(s) { return String(s || 'voice-memo').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 80) || 'voice-memo'; }\nfunction yaml(s) { return String(s ?? '').split('\\n').join(' ').replaceAll('\"', '\\\\\"'); }\n\nconst date = new Date(proc.created_at || Date.now());\nconst ymd = date.toISOString().slice(0,10);\nconst notePath = `Voice Memos/${ymd}-${slugify(proc.title || input.title)}.md`;\n\nconst title = proc.title || input.title || 'Voice Memo';\nconst tags = proc.tags || input.tags || ['voice', 'memo'];\nconst tagLines = tags.map(t => ` - ${yaml(t)}`).join('\\n');\nconst sourceType = proc.source_type || input.source || 'unknown';\nconst sourceUrl = input.source_url || '';\n\nlet audioNote = '';\nif (proc.tts_audio_url) {\n audioNote = `\\n## Audio Summary\\n\\n> Listen to the AI-generated summary: ${proc.tts_audio_url}\\n`;\n}\n\nconst markdown = `---\\ntitle: \"${yaml(title)}\"\\nsource: \"${yaml(sourceUrl)}\"\\nsource_type: \"${sourceType}\"\\ncreated: \"${date.toISOString()}\"\\ntags:\\n${tagLines}\\n---\\n\\n# ${title}\\n\\n## Summary\\n\\n${(proc.summary || '').trim()}\\n${audioNote}\\n## Transcript\\n\\n${proc.transcript || 'No transcript available.'}\\n`;\n\nreturn [{ json: { ...input, notePath, markdown, title, tts_audio_url: proc.tts_audio_url || null } }];\n"
|
||||
},
|
||||
"id": "vm-build-obsidian-v2",
|
||||
"name": "Build Obsidian Note",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-240,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "PUT",
|
||||
"url": "={{'http://172.19.0.1:27123/vault/' + encodeURIComponent($json.notePath).replace(/%2F/g, '/')}}",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "text/markdown"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "text/markdown",
|
||||
"body": "={{$json.markdown}}",
|
||||
"options": {
|
||||
"timeout": 30000
|
||||
},
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "vm-write-obsidian-v2",
|
||||
"name": "Write Note to Obsidian",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "465Swz2b71O2KRAK",
|
||||
"name": "Obsidian Local REST API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"chatId": "8367012007",
|
||||
"text": "={{ \"Voice memo captured (\" + $json.source_type + \"): \" + $json.title + \"\\nObsidian: \" + $json.notePath + ($json.tts_audio_url ? \"\\nAudio summary: \" + $json.tts_audio_url : \"\") }}",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
1160,
|
||||
-80
|
||||
],
|
||||
"id": "41bf5a55-2047-400a-87c7-44744a0f2a42",
|
||||
"name": "Send Telegram Notification",
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "aox4dyIWVSRdcH5z",
|
||||
"name": "Telegram Bot (OpenClaw)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ JSON.stringify({ ok: true, notePath: $json.notePath, title: $json.title, source_type: $json.source_type, tts_audio_url: $json.tts_audio_url || null }) }}"
|
||||
},
|
||||
"id": "vm-respond-v2",
|
||||
"name": "Respond",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
460,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Webhook - Voice Memo": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Normalize Input",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Normalize Input": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Process Voice Memo",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Process Voice Memo": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Obsidian Note",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Obsidian Note": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Write Note to Obsidian",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Write Note to Obsidian": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Telegram Notification",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Send Telegram Notification": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"authors": "will will",
|
||||
"name": null,
|
||||
"description": null,
|
||||
"autosaved": false,
|
||||
"workflowPublishHistory": [
|
||||
{
|
||||
"createdAt": "2026-05-14T00:03:13.146Z",
|
||||
"id": 1475,
|
||||
"workflowId": "El1BHJZ56JlzhrRZ",
|
||||
"versionId": "4511e901-afab-493e-9b17-99a9d9865147",
|
||||
"event": "activated",
|
||||
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
||||
},
|
||||
{
|
||||
"createdAt": "2026-05-14T00:03:13.139Z",
|
||||
"id": 1474,
|
||||
"workflowId": "El1BHJZ56JlzhrRZ",
|
||||
"versionId": "4511e901-afab-493e-9b17-99a9d9865147",
|
||||
"event": "deactivated",
|
||||
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"updatedAt": "2026-05-12T17:06:07.191Z",
|
||||
"updatedAt": "2026-05-14T00:18:01.110Z",
|
||||
"createdAt": "2026-05-12T16:59:40.394Z",
|
||||
"id": "G9ylNbHbnJ6fWX2C",
|
||||
"name": "n8n Failure Digest",
|
||||
@@ -99,7 +99,7 @@
|
||||
{
|
||||
"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, '<url>')\n .replace(/[0-9a-f]{8,}/gi, '<hex>')\n .replace(/\\b\\d{4,}\\b/g, '<num>')\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() } }];"
|
||||
"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, '<url>')\n .replace(/[0-9a-f]{8,}/gi, '<hex>')\n .replace(/\\b\\d{4,}\\b/g, '<num>')\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 [];\nfunction telegramSafe(s) { return String(s || '').replace(/[\\u0000-\\u001f\\u007f]/g, ' ').slice(0, 3500); }\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');\n// Telegram node defaults to legacy Markdown, so escape characters that\n// commonly occur in workflow/node/error names (notably underscores).\nfunction telegramMarkdownSafe(s) { return String(s).replace(/([_*`\\[])/g, '\\\\$1'); }\nconst text = telegramMarkdownSafe(lines.join('\\n'));\nreturn [{ json: { text, totalFailures: failures.length, groups: sorted, generatedAt: new Date(now).toISOString() } }];"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
@@ -115,7 +115,8 @@
|
||||
"chatId": "8367012007",
|
||||
"text": "={{ $json.text }}",
|
||||
"additionalFields": {
|
||||
"parse_mode": "Markdown"
|
||||
"parse_mode": "",
|
||||
"disable_web_page_preview": true
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
@@ -253,10 +254,9 @@
|
||||
}
|
||||
},
|
||||
"meta": null,
|
||||
"pinData": null,
|
||||
"versionId": "c198c473-6fed-4fa8-b203-9465bb084e89",
|
||||
"activeVersionId": "c198c473-6fed-4fa8-b203-9465bb084e89",
|
||||
"versionCounter": 19,
|
||||
"versionId": "2d85e3bf-d8cf-4274-bf61-5377241897da",
|
||||
"activeVersionId": "2d85e3bf-d8cf-4274-bf61-5377241897da",
|
||||
"versionCounter": 36,
|
||||
"triggerCount": 1,
|
||||
"shared": [
|
||||
{
|
||||
@@ -279,9 +279,9 @@
|
||||
],
|
||||
"tags": [],
|
||||
"activeVersion": {
|
||||
"updatedAt": "2026-05-13T21:29:18.280Z",
|
||||
"createdAt": "2026-05-13T21:29:18.280Z",
|
||||
"versionId": "c198c473-6fed-4fa8-b203-9465bb084e89",
|
||||
"updatedAt": "2026-05-14T00:18:01.111Z",
|
||||
"createdAt": "2026-05-14T00:18:01.111Z",
|
||||
"versionId": "2d85e3bf-d8cf-4274-bf61-5377241897da",
|
||||
"workflowId": "G9ylNbHbnJ6fWX2C",
|
||||
"nodes": [
|
||||
{
|
||||
@@ -376,7 +376,7 @@
|
||||
{
|
||||
"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, '<url>')\n .replace(/[0-9a-f]{8,}/gi, '<hex>')\n .replace(/\\b\\d{4,}\\b/g, '<num>')\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() } }];"
|
||||
"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, '<url>')\n .replace(/[0-9a-f]{8,}/gi, '<hex>')\n .replace(/\\b\\d{4,}\\b/g, '<num>')\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 [];\nfunction telegramSafe(s) { return String(s || '').replace(/[\\u0000-\\u001f\\u007f]/g, ' ').slice(0, 3500); }\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');\n// Telegram node defaults to legacy Markdown, so escape characters that\n// commonly occur in workflow/node/error names (notably underscores).\nfunction telegramMarkdownSafe(s) { return String(s).replace(/([_*`\\[])/g, '\\\\$1'); }\nconst text = telegramMarkdownSafe(lines.join('\\n'));\nreturn [{ json: { text, totalFailures: failures.length, groups: sorted, generatedAt: new Date(now).toISOString() } }];"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
@@ -392,7 +392,8 @@
|
||||
"chatId": "8367012007",
|
||||
"text": "={{ $json.text }}",
|
||||
"additionalFields": {
|
||||
"parse_mode": "Markdown"
|
||||
"parse_mode": "",
|
||||
"disable_web_page_preview": true
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
@@ -516,19 +517,19 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"authors": "import",
|
||||
"authors": "will will",
|
||||
"name": null,
|
||||
"description": null,
|
||||
"autosaved": false,
|
||||
"workflowPublishHistory": [
|
||||
{
|
||||
"createdAt": "2026-05-13T21:30:20.994Z",
|
||||
"id": 1429,
|
||||
"createdAt": "2026-05-14T00:18:01.158Z",
|
||||
"id": 1491,
|
||||
"workflowId": "G9ylNbHbnJ6fWX2C",
|
||||
"versionId": "c198c473-6fed-4fa8-b203-9465bb084e89",
|
||||
"versionId": "2d85e3bf-d8cf-4274-bf61-5377241897da",
|
||||
"event": "activated",
|
||||
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
{
|
||||
"updatedAt": "2026-05-14T00:01:22.299Z",
|
||||
"createdAt": "2026-05-12T17:48:01.214Z",
|
||||
"id": "GSmzuA5dgGgyRg5v",
|
||||
"name": "Web-to-Notes Capture (Local LLM + Obsidian)",
|
||||
"description": null,
|
||||
"active": true,
|
||||
"isArchived": false,
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "web-to-notes",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"id": "02979a5e-67e7-43ae-8c9f-4694a5b36e56",
|
||||
"name": "Webhook - Capture URL",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
-900,
|
||||
0
|
||||
],
|
||||
"webhookId": "7958ecbc-c714-41d5-a829-882447ab95f8"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const body = $json.body ?? $json;\nconst url = String(body.url || body.link || '').trim();\nif (!url || !/^https?:\\/\\//i.test(url)) throw new Error('POST JSON must include url starting with http:// or https://');\nconst title = String(body.title || '').trim();\nconst notes = String(body.notes || body.note || body.comment || '').trim();\nconst tags = Array.isArray(body.tags) ? body.tags : String(body.tags || 'web-capture').split(',').map(s => s.trim()).filter(Boolean);\nreturn [{ json: { url, title, notes, tags, capturedAt: new Date().toISOString() } }];"
|
||||
},
|
||||
"id": "22ba0ac9-af51-4469-a8bd-b3d3c1dd049b",
|
||||
"name": "Normalize Input",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-680,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://172.19.0.1:18806/v1/chat/completions",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ model: \"gemma-4-26b\", messages: [{ role: \"system\", content: \"You are a concise summarizer. Extract key points, claims, and notable details. Format as clear markdown with a summary section and key points list.\" }, { role: \"user\", content: `Summarize this ${$json.content_type || \"web\"} content titled \"${$json.title || \"untitled\"}\":\\n\\n${($json.text || \"\").slice(0, 8000)}` }], temperature: 0.3, max_tokens: 1600 }) }}",
|
||||
"options": {
|
||||
"timeout": 120000
|
||||
}
|
||||
},
|
||||
"id": "2ea254be-4a88-426a-97ff-16a80196b462",
|
||||
"name": "Summarize with llama.cpp",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"continueOnFail": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const extracted = $('Extract Content').first().json;\nconst input = $('Normalize Input').first().json;\n\nlet summary = '';\ntry { summary = $json.choices?.[0]?.message?.content || $json.body?.choices?.[0]?.message?.content || ''; } catch (e) {}\n// Dedent summary (LLM sometimes returns indented markdown)\nsummary = summary.split('\\n').map(l => l.replace(/^\\s{4}/, '')).join('\\n').trim();\nif (!summary) summary = 'LLM summary unavailable.\\n\\nContent excerpt:\\n\\n> ' + (extracted.text || '').slice(0, 1200);\n\nconst contentType = extracted.content_type || 'web';\nconst title = extracted.title || input.title || 'Untitled';\nconst sourceUrl = extracted.metadata?.source_url || input.url;\nconst notes = input.notes || '';\nconst tags = input.tags || ['web-capture'];\n\nif (contentType === 'youtube') tags.push('youtube', 'video-transcript');\nelse if (contentType === 'pdf') tags.push('pdf', 'document');\n\nconst meta = extracted.metadata || {};\nlet metaSection = '';\nif (contentType === 'youtube') {\n metaSection = `**Video ID:** ${meta.video_id || 'N/A'} \\n**Transcript Entries:** ${meta.transcript_entries || 0}`;\n} else if (contentType === 'pdf') {\n metaSection = `**Author:** ${meta.author || 'N/A'} \\n**Pages:** ${meta.page_count || 'N/A'}`;\n}\n\nfunction slugify(s) { return String(s || 'untitled').toLowerCase().replace(/https?:\\/\\//,'').replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,'').slice(0,80) || 'untitled'; }\nfunction yamlSafe(s) { return String(s || '').replace(/'/g, \"''\").replace(/\\n/g, ' '); }\n\nconst date = new Date().toISOString().split('T')[0];\nconst notePath = `Clippings/${date}-${slugify(title)}.md`;\n\nconst frontmatter = [\n '---',\n `title: '${yamlSafe(title)}'`,\n `source_url: ${sourceUrl}`,\n `content_type: ${contentType}`,\n `date: ${date}`,\n `tags: [${tags.map(t => \"'\" + t + \"'\").join(', ')}]`,\n '---',\n].join('\\n');\n\nconst body = [\n frontmatter,\n '',\n `# ${title}`,\n '',\n `> Source: [${title}](${sourceUrl})`,\n ...(metaSection ? ['', metaSection] : []),\n ...(notes ? ['', `## Notes\\n${notes}`] : []),\n '',\n '## Summary',\n '',\n summary,\n '',\n '---',\n `*Captured via Web-to-Notes (${contentType})*`,\n].join('\\n');\n\nreturn [{ json: { notePath, body, title, contentType, sourceUrl } }];\n"
|
||||
},
|
||||
"id": "403dff8b-5789-4018-89ec-69d45569cd25",
|
||||
"name": "Build Markdown Note",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
220,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "PUT",
|
||||
"url": "={{'http://172.19.0.1:27123/vault/' + encodeURIComponent($json.notePath).replace(/%2F/g, '/')}}",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "text/markdown"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "text/markdown",
|
||||
"body": "={{$json.body}}",
|
||||
"options": {
|
||||
"timeout": 30000
|
||||
},
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "1d00b920-985e-415c-b445-4a28674287a0",
|
||||
"name": "Write Note to Obsidian",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
460,
|
||||
0
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "465Swz2b71O2KRAK",
|
||||
"name": "Obsidian Local REST API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{JSON.stringify({ok: true, notePath: $json.notePath, title: $json.title, source: $json.url})}}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "c3d45b9e-a4d3-43ee-855a-7a76030e8888",
|
||||
"name": "Respond",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.5,
|
||||
"position": [
|
||||
700,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://172.19.0.1:18812/extract",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ url: $json.url }) }}",
|
||||
"options": {
|
||||
"timeout": 120000,
|
||||
"fullResponse": false
|
||||
}
|
||||
},
|
||||
"id": "extract-content-v2",
|
||||
"name": "Extract Content",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-240,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Webhook - Capture URL": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Normalize Input",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Normalize Input": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Content",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Content": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Summarize with llama.cpp",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Summarize with llama.cpp": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Markdown Note",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Markdown Note": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Write Note to Obsidian",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Write Note to Obsidian": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"staticData": null,
|
||||
"meta": null,
|
||||
"pinData": null,
|
||||
"versionId": "f503ca32-52bf-42ef-9dd4-ceecf538ed08",
|
||||
"activeVersionId": "f503ca32-52bf-42ef-9dd4-ceecf538ed08",
|
||||
"versionCounter": 30,
|
||||
"triggerCount": 1,
|
||||
"shared": [
|
||||
{
|
||||
"updatedAt": "2026-05-12T17:48:01.217Z",
|
||||
"createdAt": "2026-05-12T17:48:01.217Z",
|
||||
"role": "workflow:owner",
|
||||
"workflowId": "GSmzuA5dgGgyRg5v",
|
||||
"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:01:22.300Z",
|
||||
"createdAt": "2026-05-14T00:01:22.300Z",
|
||||
"versionId": "f503ca32-52bf-42ef-9dd4-ceecf538ed08",
|
||||
"workflowId": "GSmzuA5dgGgyRg5v",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "web-to-notes",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"id": "02979a5e-67e7-43ae-8c9f-4694a5b36e56",
|
||||
"name": "Webhook - Capture URL",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
-900,
|
||||
0
|
||||
],
|
||||
"webhookId": "7958ecbc-c714-41d5-a829-882447ab95f8"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const body = $json.body ?? $json;\nconst url = String(body.url || body.link || '').trim();\nif (!url || !/^https?:\\/\\//i.test(url)) throw new Error('POST JSON must include url starting with http:// or https://');\nconst title = String(body.title || '').trim();\nconst notes = String(body.notes || body.note || body.comment || '').trim();\nconst tags = Array.isArray(body.tags) ? body.tags : String(body.tags || 'web-capture').split(',').map(s => s.trim()).filter(Boolean);\nreturn [{ json: { url, title, notes, tags, capturedAt: new Date().toISOString() } }];"
|
||||
},
|
||||
"id": "22ba0ac9-af51-4469-a8bd-b3d3c1dd049b",
|
||||
"name": "Normalize Input",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-680,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://172.19.0.1:18806/v1/chat/completions",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ model: \"gemma-4-26b\", messages: [{ role: \"system\", content: \"You are a concise summarizer. Extract key points, claims, and notable details. Format as clear markdown with a summary section and key points list.\" }, { role: \"user\", content: `Summarize this ${$json.content_type || \"web\"} content titled \"${$json.title || \"untitled\"}\":\\n\\n${($json.text || \"\").slice(0, 8000)}` }], temperature: 0.3, max_tokens: 1600 }) }}",
|
||||
"options": {
|
||||
"timeout": 120000
|
||||
}
|
||||
},
|
||||
"id": "2ea254be-4a88-426a-97ff-16a80196b462",
|
||||
"name": "Summarize with llama.cpp",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"continueOnFail": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const extracted = $('Extract Content').first().json;\nconst input = $('Normalize Input').first().json;\n\nlet summary = '';\ntry { summary = $json.choices?.[0]?.message?.content || $json.body?.choices?.[0]?.message?.content || ''; } catch (e) {}\n// Dedent summary (LLM sometimes returns indented markdown)\nsummary = summary.split('\\n').map(l => l.replace(/^\\s{4}/, '')).join('\\n').trim();\nif (!summary) summary = 'LLM summary unavailable.\\n\\nContent excerpt:\\n\\n> ' + (extracted.text || '').slice(0, 1200);\n\nconst contentType = extracted.content_type || 'web';\nconst title = extracted.title || input.title || 'Untitled';\nconst sourceUrl = extracted.metadata?.source_url || input.url;\nconst notes = input.notes || '';\nconst tags = input.tags || ['web-capture'];\n\nif (contentType === 'youtube') tags.push('youtube', 'video-transcript');\nelse if (contentType === 'pdf') tags.push('pdf', 'document');\n\nconst meta = extracted.metadata || {};\nlet metaSection = '';\nif (contentType === 'youtube') {\n metaSection = `**Video ID:** ${meta.video_id || 'N/A'} \\n**Transcript Entries:** ${meta.transcript_entries || 0}`;\n} else if (contentType === 'pdf') {\n metaSection = `**Author:** ${meta.author || 'N/A'} \\n**Pages:** ${meta.page_count || 'N/A'}`;\n}\n\nfunction slugify(s) { return String(s || 'untitled').toLowerCase().replace(/https?:\\/\\//,'').replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,'').slice(0,80) || 'untitled'; }\nfunction yamlSafe(s) { return String(s || '').replace(/'/g, \"''\").replace(/\\n/g, ' '); }\n\nconst date = new Date().toISOString().split('T')[0];\nconst notePath = `Clippings/${date}-${slugify(title)}.md`;\n\nconst frontmatter = [\n '---',\n `title: '${yamlSafe(title)}'`,\n `source_url: ${sourceUrl}`,\n `content_type: ${contentType}`,\n `date: ${date}`,\n `tags: [${tags.map(t => \"'\" + t + \"'\").join(', ')}]`,\n '---',\n].join('\\n');\n\nconst body = [\n frontmatter,\n '',\n `# ${title}`,\n '',\n `> Source: [${title}](${sourceUrl})`,\n ...(metaSection ? ['', metaSection] : []),\n ...(notes ? ['', `## Notes\\n${notes}`] : []),\n '',\n '## Summary',\n '',\n summary,\n '',\n '---',\n `*Captured via Web-to-Notes (${contentType})*`,\n].join('\\n');\n\nreturn [{ json: { notePath, body, title, contentType, sourceUrl } }];\n"
|
||||
},
|
||||
"id": "403dff8b-5789-4018-89ec-69d45569cd25",
|
||||
"name": "Build Markdown Note",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
220,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "PUT",
|
||||
"url": "={{'http://172.19.0.1:27123/vault/' + encodeURIComponent($json.notePath).replace(/%2F/g, '/')}}",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "text/markdown"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "text/markdown",
|
||||
"body": "={{$json.body}}",
|
||||
"options": {
|
||||
"timeout": 30000
|
||||
},
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "1d00b920-985e-415c-b445-4a28674287a0",
|
||||
"name": "Write Note to Obsidian",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
460,
|
||||
0
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "465Swz2b71O2KRAK",
|
||||
"name": "Obsidian Local REST API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{JSON.stringify({ok: true, notePath: $json.notePath, title: $json.title, source: $json.url})}}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "c3d45b9e-a4d3-43ee-855a-7a76030e8888",
|
||||
"name": "Respond",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.5,
|
||||
"position": [
|
||||
700,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://172.19.0.1:18812/extract",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ url: $json.url }) }}",
|
||||
"options": {
|
||||
"timeout": 120000,
|
||||
"fullResponse": false
|
||||
}
|
||||
},
|
||||
"id": "extract-content-v2",
|
||||
"name": "Extract Content",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-240,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Webhook - Capture URL": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Normalize Input",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Normalize Input": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Content",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Content": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Summarize with llama.cpp",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Summarize with llama.cpp": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Markdown Note",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Markdown Note": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Write Note to Obsidian",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Write Note to Obsidian": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"authors": "will will",
|
||||
"name": null,
|
||||
"description": null,
|
||||
"autosaved": false,
|
||||
"workflowPublishHistory": [
|
||||
{
|
||||
"createdAt": "2026-05-14T00:01:22.328Z",
|
||||
"id": 1462,
|
||||
"workflowId": "GSmzuA5dgGgyRg5v",
|
||||
"versionId": "f503ca32-52bf-42ef-9dd4-ceecf538ed08",
|
||||
"event": "activated",
|
||||
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
||||
},
|
||||
{
|
||||
"createdAt": "2026-05-14T00:01:22.316Z",
|
||||
"id": 1461,
|
||||
"workflowId": "GSmzuA5dgGgyRg5v",
|
||||
"versionId": "f503ca32-52bf-42ef-9dd4-ceecf538ed08",
|
||||
"event": "deactivated",
|
||||
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"updatedAt": "2026-05-13T21:40:33.847Z",
|
||||
"updatedAt": "2026-05-14T00:04:59.343Z",
|
||||
"createdAt": "2026-05-13T21:40:33.847Z",
|
||||
"id": "PlZywwqL8MRNEAN6",
|
||||
"name": "Evening Digest",
|
||||
@@ -161,7 +161,7 @@
|
||||
"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 }] }) }}",
|
||||
"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: 🔧 Executions Summary, ⚠️ Failures, 📝 New Notes, 🏥 Infrastructure Health, 📋 Action Items. Be factual and concise.' }, { role: 'user', content: 'Here is today\\'s data:\\n' + $json.summary }] }) }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
@@ -183,7 +183,7 @@
|
||||
{
|
||||
"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}];"
|
||||
"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 = '🌙 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",
|
||||
@@ -396,7 +396,8 @@
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
"availableInMCP": false,
|
||||
"timezone": "America/Los_Angeles"
|
||||
},
|
||||
"staticData": {
|
||||
"node:Daily 9PM Schedule": {
|
||||
@@ -407,7 +408,7 @@
|
||||
"pinData": null,
|
||||
"versionId": "afb71f4d-6ac3-434d-b659-de003d47c339",
|
||||
"activeVersionId": "afb71f4d-6ac3-434d-b659-de003d47c339",
|
||||
"versionCounter": 4,
|
||||
"versionCounter": 11,
|
||||
"triggerCount": 1,
|
||||
"shared": [
|
||||
{
|
||||
@@ -589,7 +590,7 @@
|
||||
"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 }] }) }}",
|
||||
"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: 🔧 Executions Summary, ⚠️ Failures, 📝 New Notes, 🏥 Infrastructure Health, 📋 Action Items. Be factual and concise.' }, { role: 'user', content: 'Here is today\\'s data:\\n' + $json.summary }] }) }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
@@ -611,7 +612,7 @@
|
||||
{
|
||||
"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}];"
|
||||
"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 = '🌙 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",
|
||||
@@ -833,7 +834,39 @@
|
||||
"versionId": "afb71f4d-6ac3-434d-b659-de003d47c339",
|
||||
"event": "activated",
|
||||
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
||||
},
|
||||
{
|
||||
"createdAt": "2026-05-14T00:04:59.370Z",
|
||||
"id": 1483,
|
||||
"workflowId": "PlZywwqL8MRNEAN6",
|
||||
"versionId": "afb71f4d-6ac3-434d-b659-de003d47c339",
|
||||
"event": "activated",
|
||||
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
||||
},
|
||||
{
|
||||
"createdAt": "2026-05-14T00:04:59.415Z",
|
||||
"id": 1485,
|
||||
"workflowId": "PlZywwqL8MRNEAN6",
|
||||
"versionId": "afb71f4d-6ac3-434d-b659-de003d47c339",
|
||||
"event": "activated",
|
||||
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
||||
},
|
||||
{
|
||||
"createdAt": "2026-05-14T00:04:59.362Z",
|
||||
"id": 1482,
|
||||
"workflowId": "PlZywwqL8MRNEAN6",
|
||||
"versionId": "afb71f4d-6ac3-434d-b659-de003d47c339",
|
||||
"event": "deactivated",
|
||||
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
||||
},
|
||||
{
|
||||
"createdAt": "2026-05-14T00:04:59.388Z",
|
||||
"id": 1484,
|
||||
"workflowId": "PlZywwqL8MRNEAN6",
|
||||
"versionId": "afb71f4d-6ac3-434d-b659-de003d47c339",
|
||||
"event": "deactivated",
|
||||
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
{
|
||||
"updatedAt": "2026-05-14T00:01:24.692Z",
|
||||
"createdAt": "2026-03-18T20:17:45.262Z",
|
||||
"id": "QRCCdHNXZUHc2Oz4",
|
||||
"name": "Calendar to Obsidian Notes",
|
||||
"description": null,
|
||||
"active": true,
|
||||
"isArchived": false,
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "hours",
|
||||
"hoursInterval": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-trigger",
|
||||
"name": "Schedule Trigger",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
240,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"calendar": {
|
||||
"__rl": true,
|
||||
"value": "william.valentin.info@gmail.com",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Perso"
|
||||
},
|
||||
"limit": 20,
|
||||
"options": {
|
||||
"timeMin": "={{ new Date().toISOString() }}",
|
||||
"timeMax": "={{ new Date(Date.now() + 7*24*60*60*1000).toISOString() }}",
|
||||
"singleEvents": true,
|
||||
"orderBy": "startTime"
|
||||
}
|
||||
},
|
||||
"id": "get-events",
|
||||
"name": "Get Upcoming Events",
|
||||
"type": "n8n-nodes-base.googleCalendar",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
464,
|
||||
304
|
||||
],
|
||||
"credentials": {
|
||||
"googleCalendarOAuth2Api": {
|
||||
"id": "458fY4bs1z49OTeZ",
|
||||
"name": "Google Calendar account"
|
||||
}
|
||||
},
|
||||
"continueOnFail": true,
|
||||
"alwaysOutputData": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const event = $input.item.json || {};\nconst now = new Date();\nconst today = now.toISOString().slice(0, 10);\nconst hasUsableEvent = event.start && (event.summary || event.id || event.htmlLink);\nif (event.error || event.message || !hasUsableEvent) {\n const detail = String(event.error?.message || event.message || event.error || 'Google Calendar returned no usable event; credentials may need reauthorization.').replace(/`/g, \"'\").slice(0, 1000);\n const content = `---\ntitle: \"Google Calendar sync needs attention\"\narea: notes\ntags: [calendar, automation, degraded]\ncreated: ${today}\nupdated: ${today}\nstatus: needs-reauth\n---\n\n# Google Calendar sync needs attention\n\nThe n8n Calendar to Obsidian workflow could not read Google Calendar events.\n\nLikely cause: expired Google OAuth credentials in n8n.\n\nAction: reauthorize the Google Calendar credential used by workflow QRCCdHNXZUHc2Oz4, then run the workflow manually.\n\nLast observed detail:\n\n> ${detail}\n`;\n return [{ json: { path: `Notes/Calendar Sync Status/${today} Google Calendar Needs Reauth.md`, content, title: 'Google Calendar sync needs attention', date: today, degraded: true } }];\n}\nconst event = $input.item.json;\nconst startRaw = event.start?.dateTime || event.start?.date || \"\";\nconst date = startRaw.split(\"T\")[0];\nconst title = (event.summary || \"Untitled Event\").replace(/[\\/\\\\?%*:|\"<>]/g, \"-\").substring(0, 80);\nconst location = event.location || \"\";\nconst description = event.description || \"\";\nconst attendees = (event.attendees || []).map(a => a.email).join(\", \");\nconst endRaw = event.end?.dateTime || event.end?.date || \"\";\nconst startTime = startRaw.includes(\"T\") ? startRaw.split(\"T\")[1].substring(0,5) : \"All day\";\nconst endTime = endRaw.includes(\"T\") ? endRaw.split(\"T\")[1].substring(0,5) : \"\";\nconst timeStr = endTime ? `${startTime} – ${endTime}` : startTime;\nconst frontmatter = `---\\ntitle: \"${title}\"\\narea: notes\\ntags: [calendar, event]\\ncreated: ${date}\\nupdated: ${date}\\nstatus: active\\nevent_date: ${date}\\nevent_time: \"${timeStr}\"\\n---`;\nconst content = `${frontmatter}\\n\\n# ${title}\\n\\n**Date:** ${date}\\n**Time:** ${timeStr}\\n${location ? `**Location:** ${location}\\n` : \"\"}${attendees ? `**Attendees:** ${attendees}\\n` : \"\"}\\n## Description\\n\\n${description || \"_No description_\"}\\n\\n## Notes\\n\\n_Add notes here_\\n`;\nreturn [{ json: { path: `Notes/${date} ${title}.md`, content, title, date, timeStr } }];"
|
||||
},
|
||||
"id": "format-note",
|
||||
"name": "Format Event Note",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
688,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "PUT",
|
||||
"url": "=http://192.168.153.130:27123/vault/{{ encodeURIComponent($json.path).replace(/%2F/g, \"/\") }}",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "text/markdown",
|
||||
"body": "={{ $json.content }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "write-to-vault",
|
||||
"name": "Write to Vault",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4,
|
||||
"position": [
|
||||
912,
|
||||
304
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "465Swz2b71O2KRAK",
|
||||
"name": "Obsidian Local REST API"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get Upcoming Events",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get Upcoming Events": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Format Event Note",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Format Event Note": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Write to Vault",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"staticData": {
|
||||
"node:Schedule Trigger": {
|
||||
"recurrenceRules": [
|
||||
6
|
||||
]
|
||||
}
|
||||
},
|
||||
"meta": null,
|
||||
"pinData": {},
|
||||
"versionId": "40b22838-7ce4-4632-b186-b78ccda438c4",
|
||||
"activeVersionId": "40b22838-7ce4-4632-b186-b78ccda438c4",
|
||||
"versionCounter": 1636,
|
||||
"triggerCount": 1,
|
||||
"shared": [
|
||||
{
|
||||
"updatedAt": "2026-03-18T20:17:45.264Z",
|
||||
"createdAt": "2026-03-18T20:17:45.264Z",
|
||||
"role": "workflow:owner",
|
||||
"workflowId": "QRCCdHNXZUHc2Oz4",
|
||||
"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": [
|
||||
{
|
||||
"updatedAt": "2026-03-19T04:40:29.915Z",
|
||||
"createdAt": "2026-03-19T04:40:29.915Z",
|
||||
"id": "GLr9Awuvw8uO7ZRP",
|
||||
"name": "calendar"
|
||||
},
|
||||
{
|
||||
"updatedAt": "2026-03-19T04:40:29.892Z",
|
||||
"createdAt": "2026-03-19T04:40:29.892Z",
|
||||
"id": "VfqIkUpiu2YMBSHw",
|
||||
"name": "obsidian-sync"
|
||||
}
|
||||
],
|
||||
"activeVersion": {
|
||||
"updatedAt": "2026-05-14T00:01:24.693Z",
|
||||
"createdAt": "2026-05-14T00:01:24.693Z",
|
||||
"versionId": "40b22838-7ce4-4632-b186-b78ccda438c4",
|
||||
"workflowId": "QRCCdHNXZUHc2Oz4",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "hours",
|
||||
"hoursInterval": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-trigger",
|
||||
"name": "Schedule Trigger",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
240,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"calendar": {
|
||||
"__rl": true,
|
||||
"value": "william.valentin.info@gmail.com",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Perso"
|
||||
},
|
||||
"limit": 20,
|
||||
"options": {
|
||||
"timeMin": "={{ new Date().toISOString() }}",
|
||||
"timeMax": "={{ new Date(Date.now() + 7*24*60*60*1000).toISOString() }}",
|
||||
"singleEvents": true,
|
||||
"orderBy": "startTime"
|
||||
}
|
||||
},
|
||||
"id": "get-events",
|
||||
"name": "Get Upcoming Events",
|
||||
"type": "n8n-nodes-base.googleCalendar",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
464,
|
||||
304
|
||||
],
|
||||
"credentials": {
|
||||
"googleCalendarOAuth2Api": {
|
||||
"id": "458fY4bs1z49OTeZ",
|
||||
"name": "Google Calendar account"
|
||||
}
|
||||
},
|
||||
"continueOnFail": true,
|
||||
"alwaysOutputData": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const event = $input.item.json || {};\nconst now = new Date();\nconst today = now.toISOString().slice(0, 10);\nconst hasUsableEvent = event.start && (event.summary || event.id || event.htmlLink);\nif (event.error || event.message || !hasUsableEvent) {\n const detail = String(event.error?.message || event.message || event.error || 'Google Calendar returned no usable event; credentials may need reauthorization.').replace(/`/g, \"'\").slice(0, 1000);\n const content = `---\ntitle: \"Google Calendar sync needs attention\"\narea: notes\ntags: [calendar, automation, degraded]\ncreated: ${today}\nupdated: ${today}\nstatus: needs-reauth\n---\n\n# Google Calendar sync needs attention\n\nThe n8n Calendar to Obsidian workflow could not read Google Calendar events.\n\nLikely cause: expired Google OAuth credentials in n8n.\n\nAction: reauthorize the Google Calendar credential used by workflow QRCCdHNXZUHc2Oz4, then run the workflow manually.\n\nLast observed detail:\n\n> ${detail}\n`;\n return [{ json: { path: `Notes/Calendar Sync Status/${today} Google Calendar Needs Reauth.md`, content, title: 'Google Calendar sync needs attention', date: today, degraded: true } }];\n}\nconst event = $input.item.json;\nconst startRaw = event.start?.dateTime || event.start?.date || \"\";\nconst date = startRaw.split(\"T\")[0];\nconst title = (event.summary || \"Untitled Event\").replace(/[\\/\\\\?%*:|\"<>]/g, \"-\").substring(0, 80);\nconst location = event.location || \"\";\nconst description = event.description || \"\";\nconst attendees = (event.attendees || []).map(a => a.email).join(\", \");\nconst endRaw = event.end?.dateTime || event.end?.date || \"\";\nconst startTime = startRaw.includes(\"T\") ? startRaw.split(\"T\")[1].substring(0,5) : \"All day\";\nconst endTime = endRaw.includes(\"T\") ? endRaw.split(\"T\")[1].substring(0,5) : \"\";\nconst timeStr = endTime ? `${startTime} – ${endTime}` : startTime;\nconst frontmatter = `---\\ntitle: \"${title}\"\\narea: notes\\ntags: [calendar, event]\\ncreated: ${date}\\nupdated: ${date}\\nstatus: active\\nevent_date: ${date}\\nevent_time: \"${timeStr}\"\\n---`;\nconst content = `${frontmatter}\\n\\n# ${title}\\n\\n**Date:** ${date}\\n**Time:** ${timeStr}\\n${location ? `**Location:** ${location}\\n` : \"\"}${attendees ? `**Attendees:** ${attendees}\\n` : \"\"}\\n## Description\\n\\n${description || \"_No description_\"}\\n\\n## Notes\\n\\n_Add notes here_\\n`;\nreturn [{ json: { path: `Notes/${date} ${title}.md`, content, title, date, timeStr } }];"
|
||||
},
|
||||
"id": "format-note",
|
||||
"name": "Format Event Note",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
688,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "PUT",
|
||||
"url": "=http://192.168.153.130:27123/vault/{{ encodeURIComponent($json.path).replace(/%2F/g, \"/\") }}",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "text/markdown",
|
||||
"body": "={{ $json.content }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "write-to-vault",
|
||||
"name": "Write to Vault",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4,
|
||||
"position": [
|
||||
912,
|
||||
304
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "465Swz2b71O2KRAK",
|
||||
"name": "Obsidian Local REST API"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get Upcoming Events",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get Upcoming Events": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Format Event Note",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Format Event Note": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Write to Vault",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"authors": "will will",
|
||||
"name": null,
|
||||
"description": null,
|
||||
"autosaved": false,
|
||||
"workflowPublishHistory": [
|
||||
{
|
||||
"createdAt": "2026-05-14T00:01:24.723Z",
|
||||
"id": 1466,
|
||||
"workflowId": "QRCCdHNXZUHc2Oz4",
|
||||
"versionId": "40b22838-7ce4-4632-b186-b78ccda438c4",
|
||||
"event": "activated",
|
||||
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
||||
},
|
||||
{
|
||||
"createdAt": "2026-05-14T00:01:24.711Z",
|
||||
"id": 1465,
|
||||
"workflowId": "QRCCdHNXZUHc2Oz4",
|
||||
"versionId": "40b22838-7ce4-4632-b186-b78ccda438c4",
|
||||
"event": "deactivated",
|
||||
"userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user