{ "name": "Server Health Monitor - Claude Code", "nodes": [ { "parameters": { "rule": { "interval": [ { "field": "minutes", "minutesInterval": 5 } ] } }, "id": "schedule-trigger", "name": "Every 5 Minutes", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2, "position": [0, 0] }, { "parameters": { "operation": "executeCommand", "command": "/root/.local/bin/claude -p \"Run python3 ~/.claude/skills/server-diagnostics/client.py health paper-dynasty and analyze the results. If any containers are not running or there are critical issues, summarize them. Otherwise just say 'All systems healthy'.\" --output-format json --json-schema '{\"type\":\"object\",\"properties\":{\"status\":{\"type\":\"string\",\"enum\":[\"healthy\",\"issues_found\"]},\"summary\":{\"type\":\"string\"},\"root_cause\":{\"type\":\"string\"},\"severity\":{\"type\":\"string\",\"enum\":[\"low\",\"medium\",\"high\",\"critical\"]},\"affected_services\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"actions_taken\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"status\",\"severity\",\"summary\"]}' --allowedTools \"Read,Grep,Glob,Bash(python3 ~/.claude/skills/server-diagnostics/client.py *)\" --append-system-prompt \"You are a server diagnostics agent. Use the server-diagnostics skill client.py for all operations. Never run destructive commands.\"", "options": {} }, "id": "ssh-claude-code", "name": "Run Claude Diagnostics", "type": "n8n-nodes-base.ssh", "typeVersion": 1, "position": [220, 0], "credentials": { "sshPassword": { "id": "REPLACE_WITH_CREDENTIAL_ID", "name": "Claude Code LXC" } } }, { "parameters": { "jsCode": "// Parse Claude Code JSON output (uses --json-schema for structured_output)\nconst stdout = $input.first().json.stdout || '';\n\ntry {\n const response = JSON.parse(stdout);\n const data = response.structured_output || JSON.parse(response.result || '{}');\n \n return [{\n json: {\n success: !response.is_error,\n status: data.status || 'unknown',\n summary: data.summary || response.result || 'No result',\n severity: data.severity || 'low',\n root_cause: data.root_cause || null,\n affected_services: data.affected_services || [],\n actions_taken: data.actions_taken || [],\n duration_ms: response.duration_ms,\n cost_usd: response.total_cost_usd,\n session_id: response.session_id,\n has_issues: data.status === 'issues_found',\n raw: response\n }\n }];\n} catch (e) {\n return [{\n json: {\n success: false,\n status: 'error',\n summary: 'Failed to parse Claude response',\n severity: 'high',\n error: e.message,\n raw_stdout: stdout,\n has_issues: true\n }\n }];\n}" }, "id": "parse-response", "name": "Parse Claude Response", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [440, 0] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" }, "conditions": [ { "id": "check-issues", "leftValue": "={{ $json.has_issues }}", "rightValue": true, "operator": { "type": "boolean", "operation": "equals" } } ], "combinator": "and" }, "options": {} }, "id": "check-issues", "name": "Has Issues?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [660, 0] }, { "parameters": { "operation": "executeCommand", "command": "=/root/.local/bin/claude -p \"The previous health check found issues. Investigate deeper: check container logs, resource usage, and recent events. Provide a detailed root cause analysis and recommended remediation steps.\" --resume \"{{ $json.session_id }}\" --output-format json --json-schema '{\"type\":\"object\",\"properties\":{\"root_cause_detail\":{\"type\":\"string\"},\"container_logs\":{\"type\":\"string\"},\"resource_status\":{\"type\":\"string\"},\"remediation_steps\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"requires_human\":{\"type\":\"boolean\"}},\"required\":[\"root_cause_detail\",\"remediation_steps\",\"requires_human\"]}' --allowedTools \"Read,Grep,Glob,Bash(python3 ~/.claude/skills/server-diagnostics/client.py *)\" --max-turns 15 --append-system-prompt \"You are a server diagnostics agent performing a follow-up investigation. The initial health check found issues. Dig deeper into logs and metrics. Never run destructive commands.\"", "options": {} }, "id": "ssh-followup", "name": "Follow Up Diagnostics", "type": "n8n-nodes-base.ssh", "typeVersion": 1, "position": [880, -200], "credentials": { "sshPassword": { "id": "REPLACE_WITH_CREDENTIAL_ID", "name": "Claude Code LXC" } } }, { "parameters": { "jsCode": "// Parse follow-up diagnostics response\nconst stdout = $input.first().json.stdout || '';\nconst initial = $('Parse Claude Response').first().json;\n\ntry {\n const response = JSON.parse(stdout);\n const data = response.structured_output || JSON.parse(response.result || '{}');\n \n return [{\n json: {\n ...initial,\n followup: {\n root_cause_detail: data.root_cause_detail || 'No detail available',\n container_logs: data.container_logs || '',\n resource_status: data.resource_status || '',\n remediation_steps: data.remediation_steps || [],\n requires_human: data.requires_human || false,\n cost_usd: response.total_cost_usd,\n session_id: response.session_id\n },\n total_cost_usd: (initial.cost_usd || 0) + (response.total_cost_usd || 0)\n }\n }];\n} catch (e) {\n return [{\n json: {\n ...initial,\n followup: {\n error: e.message,\n root_cause_detail: 'Follow-up parse failed',\n remediation_steps: [],\n requires_human: true\n },\n total_cost_usd: initial.cost_usd || 0\n }\n }];\n}" }, "id": "parse-followup", "name": "Parse Follow-up Response", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [1100, -200] }, { "parameters": { "method": "POST", "url": "https://discord.com/api/webhooks/1451783909409816763/O9PMDiNt6ZIWRf8HKocIZ_E4vMGV_lEwq50aAiZ9HVFR2UGwO6J1N9_wOm82p0MetIqT", "sendBody": true, "specifyBody": "json", "jsonBody": "={\n \"embeds\": [{\n \"title\": \"{{ $json.severity === 'critical' ? '🔴' : $json.severity === 'high' ? '🟠' : '🟡' }} Server Alert\",\n \"description\": {{ JSON.stringify($json.summary) }},\n \"color\": {{ $json.severity === 'critical' ? 15158332 : $json.severity === 'high' ? 15105570 : 16776960 }},\n \"fields\": [\n {\n \"name\": \"Severity\",\n \"value\": \"{{ $json.severity.toUpperCase() }}\",\n \"inline\": true\n },\n {\n \"name\": \"Server\",\n \"value\": \"paper-dynasty (10.10.0.88)\",\n \"inline\": true\n },\n {\n \"name\": \"Cost\",\n \"value\": \"${{ $json.total_cost_usd ? $json.total_cost_usd.toFixed(4) : '0.0000' }}\",\n \"inline\": true\n },\n {\n \"name\": \"Root Cause\",\n \"value\": {{ JSON.stringify(($json.followup && $json.followup.root_cause_detail) || $json.root_cause || 'N/A') }},\n \"inline\": false\n },\n {\n \"name\": \"Affected Services\",\n \"value\": \"{{ $json.affected_services.length ? $json.affected_services.join(', ') : 'None' }}\",\n \"inline\": false\n },\n {\n \"name\": \"Remediation Steps\",\n \"value\": {{ JSON.stringify(($json.followup && $json.followup.remediation_steps.length) ? $json.followup.remediation_steps.map((s, i) => (i+1) + '. ' + s).join('\\n') : ($json.actions_taken.length ? $json.actions_taken.join('\\n') : 'None')) }},\n \"inline\": false\n },\n {\n \"name\": \"Requires Human?\",\n \"value\": \"{{ ($json.followup && $json.followup.requires_human) ? '⚠️ Yes' : '✅ No' }}\",\n \"inline\": true\n }\n ],\n \"timestamp\": \"{{ new Date().toISOString() }}\"\n }]\n}", "options": {} }, "id": "discord-alert", "name": "Discord Alert", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [1320, -200] }, { "parameters": { "method": "POST", "url": "https://discord.com/api/webhooks/1451783909409816763/O9PMDiNt6ZIWRf8HKocIZ_E4vMGV_lEwq50aAiZ9HVFR2UGwO6J1N9_wOm82p0MetIqT", "sendBody": true, "specifyBody": "json", "jsonBody": "={\n \"embeds\": [{\n \"title\": \"Health Check OK\",\n \"description\": {{ JSON.stringify($json.result) }},\n \"color\": 3066993,\n \"fields\": [\n {\n \"name\": \"Server\",\n \"value\": \"paper-dynasty (10.10.0.88)\",\n \"inline\": true\n },\n {\n \"name\": \"Duration\",\n \"value\": \"{{ $json.duration_ms }}ms\",\n \"inline\": true\n }\n ],\n \"timestamp\": \"{{ new Date().toISOString() }}\"\n }]\n}", "options": {} }, "id": "discord-ok", "name": "Discord OK (Optional)", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [880, 100], "disabled": true } ], "connections": { "Every 5 Minutes": { "main": [ [ { "node": "Run Claude Diagnostics", "type": "main", "index": 0 } ] ] }, "Run Claude Diagnostics": { "main": [ [ { "node": "Parse Claude Response", "type": "main", "index": 0 } ] ] }, "Parse Claude Response": { "main": [ [ { "node": "Has Issues?", "type": "main", "index": 0 } ] ] }, "Has Issues?": { "main": [ [ { "node": "Follow Up Diagnostics", "type": "main", "index": 0 } ], [ { "node": "Discord OK (Optional)", "type": "main", "index": 0 } ] ] }, "Follow Up Diagnostics": { "main": [ [ { "node": "Parse Follow-up Response", "type": "main", "index": 0 } ] ] }, "Parse Follow-up Response": { "main": [ [ { "node": "Discord Alert", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1" }, "staticData": null, "tags": [], "triggerCount": 0, "pinData": {} }