feat(05-01): replace NLU/Claude with keyword routing
- Remove Prepare Claude Request, Claude Intent Parser, Parse Intent, Intent Router, Send Unknown Intent, Send Intent Error nodes - Remove Anthropic API credential reference - Rename Route Message to Keyword Router with updated rules - Update IF User Authenticated to connect to Keyword Router - Update Parse and Match to work without NLU context - Update Parse Action Command to parse from message text directly - Update Match Container to reference Parse Action Command - Update Parse Logs Command to work with keyword routing Keyword Router handles: /start, status, restart, start, stop, update, logs with fallback to menu Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+149
-504
@@ -157,17 +157,39 @@
|
|||||||
"rules": {
|
"rules": {
|
||||||
"values": [
|
"values": [
|
||||||
{
|
{
|
||||||
"id": "docker-query-route",
|
"id": "keyword-menu-start",
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": false,
|
||||||
|
"typeValidation": "loose"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"id": "starts-with-start-cmd",
|
||||||
|
"leftValue": "={{ $json.message.text }}",
|
||||||
|
"rightValue": "/start",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "startsWith"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "menu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "keyword-status",
|
||||||
"conditions": {
|
"conditions": {
|
||||||
"options": {
|
"options": {
|
||||||
"caseSensitive": false,
|
"caseSensitive": false,
|
||||||
"leftValue": "",
|
|
||||||
"typeValidation": "loose"
|
"typeValidation": "loose"
|
||||||
},
|
},
|
||||||
"conditions": [
|
"conditions": [
|
||||||
{
|
{
|
||||||
"id": "contains-status",
|
"id": "contains-status",
|
||||||
"leftValue": "={{ $json.message.text.toLowerCase() }}",
|
"leftValue": "={{ $json.message.text }}",
|
||||||
"rightValue": "status",
|
"rightValue": "status",
|
||||||
"operator": {
|
"operator": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -175,96 +197,125 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"combinator": "or"
|
"combinator": "and"
|
||||||
},
|
},
|
||||||
"renameOutput": false
|
"renameOutput": true,
|
||||||
|
"outputKey": "status"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "action-command-route",
|
"id": "keyword-restart",
|
||||||
"conditions": {
|
"conditions": {
|
||||||
"options": {
|
"options": {
|
||||||
"caseSensitive": false,
|
"caseSensitive": false,
|
||||||
"leftValue": "",
|
|
||||||
"typeValidation": "loose"
|
"typeValidation": "loose"
|
||||||
},
|
},
|
||||||
"conditions": [
|
"conditions": [
|
||||||
{
|
{
|
||||||
"id": "starts-with-start",
|
"id": "contains-restart",
|
||||||
"leftValue": "={{ $json.message.text.toLowerCase().trim() }}",
|
"leftValue": "={{ $json.message.text }}",
|
||||||
"rightValue": "start ",
|
|
||||||
"operator": {
|
|
||||||
"type": "string",
|
|
||||||
"operation": "startsWith"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "starts-with-stop",
|
|
||||||
"leftValue": "={{ $json.message.text.toLowerCase().trim() }}",
|
|
||||||
"rightValue": "stop ",
|
|
||||||
"operator": {
|
|
||||||
"type": "string",
|
|
||||||
"operation": "startsWith"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "starts-with-restart",
|
|
||||||
"leftValue": "={{ $json.message.text.toLowerCase().trim() }}",
|
|
||||||
"rightValue": "restart",
|
"rightValue": "restart",
|
||||||
"operator": {
|
"operator": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"operation": "startsWith"
|
"operation": "contains"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"combinator": "or"
|
"combinator": "and"
|
||||||
},
|
},
|
||||||
"renameOutput": false
|
"renameOutput": true,
|
||||||
|
"outputKey": "restart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "update-command-route",
|
"id": "keyword-start",
|
||||||
"conditions": {
|
"conditions": {
|
||||||
"options": {
|
"options": {
|
||||||
"caseSensitive": false,
|
"caseSensitive": false,
|
||||||
"leftValue": "",
|
|
||||||
"typeValidation": "loose"
|
"typeValidation": "loose"
|
||||||
},
|
},
|
||||||
"conditions": [
|
"conditions": [
|
||||||
{
|
{
|
||||||
"id": "starts-with-update",
|
"id": "contains-start",
|
||||||
"leftValue": "={{ $json.message.text.toLowerCase().trim() }}",
|
"leftValue": "={{ $json.message.text }}",
|
||||||
|
"rightValue": "start",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "contains"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "keyword-stop",
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": false,
|
||||||
|
"typeValidation": "loose"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"id": "contains-stop",
|
||||||
|
"leftValue": "={{ $json.message.text }}",
|
||||||
|
"rightValue": "stop",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "contains"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "stop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "keyword-update",
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": false,
|
||||||
|
"typeValidation": "loose"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"id": "contains-update",
|
||||||
|
"leftValue": "={{ $json.message.text }}",
|
||||||
"rightValue": "update",
|
"rightValue": "update",
|
||||||
"operator": {
|
"operator": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"operation": "startsWith"
|
"operation": "contains"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"combinator": "or"
|
"combinator": "and"
|
||||||
},
|
},
|
||||||
"renameOutput": false
|
"renameOutput": true,
|
||||||
|
"outputKey": "update"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "logs-command-route",
|
"id": "keyword-logs",
|
||||||
"conditions": {
|
"conditions": {
|
||||||
"options": {
|
"options": {
|
||||||
"caseSensitive": false,
|
"caseSensitive": false,
|
||||||
"leftValue": "",
|
|
||||||
"typeValidation": "loose"
|
"typeValidation": "loose"
|
||||||
},
|
},
|
||||||
"conditions": [
|
"conditions": [
|
||||||
{
|
{
|
||||||
"id": "matches-logs",
|
"id": "contains-logs",
|
||||||
"leftValue": "={{ $json.message.text.toLowerCase().trim() }}",
|
"leftValue": "={{ $json.message.text }}",
|
||||||
"rightValue": "^(show\\s+)?logs\\s+",
|
"rightValue": "logs",
|
||||||
"operator": {
|
"operator": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"operation": "regex"
|
"operation": "contains"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"combinator": "or"
|
"combinator": "and"
|
||||||
},
|
},
|
||||||
"renameOutput": false
|
"renameOutput": true,
|
||||||
|
"outputKey": "logs"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -272,8 +323,8 @@
|
|||||||
"fallbackOutput": "extra"
|
"fallbackOutput": "extra"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"id": "switch-route",
|
"id": "switch-keyword-router",
|
||||||
"name": "Route Message",
|
"name": "Keyword Router",
|
||||||
"type": "n8n-nodes-base.switch",
|
"type": "n8n-nodes-base.switch",
|
||||||
"typeVersion": 3.2,
|
"typeVersion": 3.2,
|
||||||
"position": [
|
"position": [
|
||||||
@@ -297,7 +348,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Get Docker API response and intent data\nconst dockerOutput = $input.item.json.stdout;\nconst intent = $('Parse Intent').item.json;\nconst chatId = intent.original_message.chat.id;\n\n// Parse JSON response - only error if we can't parse valid JSON from stdout\nlet containers;\ntry {\n if (!dockerOutput || dockerOutput.trim() === '') {\n throw new Error('Empty response');\n }\n containers = JSON.parse(dockerOutput);\n} catch (e) {\n return [{\n json: {\n chatId: chatId,\n error: true,\n text: \"Can't reach Docker - check if n8n has socket access\"\n }\n }];\n}\n\n// Function to normalize container names\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, '') // Remove leading slash\n .replace(/^(linuxserver[-_]|binhex[-_])/i, '') // Remove common prefixes\n .toLowerCase();\n}\n\n// Check if user requested a specific container or general list\nconst requestedName = intent.container;\n\n// If no container name specified, return summary\nif (!requestedName || requestedName === '' || intent.action === 'list_containers') {\n const counts = containers.reduce((acc, c) => {\n acc[c.State] = (acc[c.State] || 0) + 1;\n return acc;\n }, {});\n \n const total = containers.length;\n const running = counts['running'] || 0;\n const stopped = counts['exited'] || 0;\n const other = total - running - stopped;\n \n let summary = `<b>Docker Containers</b>\\n\\n`;\n summary += `Running: ${running}/${total}\\n`;\n if (stopped > 0) summary += `Stopped: ${stopped}\\n`;\n if (other > 0) summary += `Other: ${other}\\n`;\n \n // List running containers\n const runningContainers = containers\n .filter(c => c.State === 'running')\n .map(c => {\n const name = normalizeName(c.Names[0]);\n const uptime = c.Status;\n return ` \u2022 ${name} - ${uptime}`;\n });\n \n if (runningContainers.length > 0) {\n summary += `\\n<b>Running:</b>\\n${runningContainers.join('\\n')}`;\n }\n \n return [{\n json: {\n chatId: chatId,\n text: summary\n }\n }];\n}\n\n// Find matching containers\nconst matches = containers.filter(c => {\n const containerName = normalizeName(c.Names[0]);\n return containerName.includes(requestedName);\n});\n\nif (matches.length === 0) {\n return [{\n json: {\n chatId: chatId,\n error: true,\n text: `No container found matching \"${requestedName}\"`\n }\n }];\n}\n\nif (matches.length === 1) {\n const container = matches[0];\n const name = normalizeName(container.Names[0]);\n const state = container.State;\n const status = container.Status;\n const image = container.Image;\n \n let text = `<b>${name}</b>\\n`;\n text += `State: ${state}\\n`;\n text += `Status: ${status}\\n`;\n text += `Image: ${image}`;\n \n return [{\n json: {\n chatId: chatId,\n text: text\n }\n }];\n}\n\n// Multiple matches - list them\nconst matchList = matches.map(c => {\n const name = normalizeName(c.Names[0]);\n return ` \u2022 ${name} (${c.State})`;\n}).join('\\n');\n\nreturn [{\n json: {\n chatId: chatId,\n text: `Multiple matches for \"${requestedName}\":\\n${matchList}`\n }\n}];"
|
"jsCode": "// Get Docker API response and message\nconst dockerOutput = $input.item.json.stdout;\nconst message = $('Keyword Router').item.json.message;\nconst chatId = message.chat.id;\n\n// Parse JSON response - only error if we can't parse valid JSON from stdout\nlet containers;\ntry {\n if (!dockerOutput || dockerOutput.trim() === '') {\n throw new Error('Empty response');\n }\n containers = JSON.parse(dockerOutput);\n} catch (e) {\n return [{\n json: {\n chatId: chatId,\n error: true,\n text: \"Can't reach Docker - check if n8n has socket access\"\n }\n }];\n}\n\n// Function to normalize container names\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, '') // Remove leading slash\n .replace(/^(linuxserver[-_]|binhex[-_])/i, '') // Remove common prefixes\n .toLowerCase();\n}\n\n// Check if user specified a container name in message (e.g., \"status plex\")\nconst text = (message.text || '').toLowerCase().trim();\nlet requestedName = null;\n\n// Extract container name if message is more than just \"status\"\nconst parts = text.split(/\\s+/);\nif (parts.length > 1) {\n // Join all parts except \"status\" as the container query\n requestedName = parts.filter(p => p !== 'status').join(' ');\n}\n\n// If no container name specified, return summary\nif (!requestedName) {\n const counts = containers.reduce((acc, c) => {\n acc[c.State] = (acc[c.State] || 0) + 1;\n return acc;\n }, {});\n \n const total = containers.length;\n const running = counts['running'] || 0;\n const stopped = counts['exited'] || 0;\n const other = total - running - stopped;\n \n let summary = '<b>Docker Containers</b>\\n\\n';\n summary += 'Running: ' + running + '/' + total + '\\n';\n if (stopped > 0) summary += 'Stopped: ' + stopped + '\\n';\n if (other > 0) summary += 'Other: ' + other + '\\n';\n \n // List running containers\n const runningContainers = containers\n .filter(c => c.State === 'running')\n .map(c => {\n const name = normalizeName(c.Names[0]);\n const uptime = c.Status;\n return ' \\u2022 ' + name + ' - ' + uptime;\n });\n \n if (runningContainers.length > 0) {\n summary += '\\n<b>Running:</b>\\n' + runningContainers.join('\\n');\n }\n \n return [{\n json: {\n chatId: chatId,\n text: summary\n }\n }];\n}\n\n// Find matching containers\nconst matches = containers.filter(c => {\n const containerName = normalizeName(c.Names[0]);\n return containerName.includes(requestedName);\n});\n\nif (matches.length === 0) {\n return [{\n json: {\n chatId: chatId,\n error: true,\n text: 'No container found matching \"' + requestedName + '\"'\n }\n }];\n}\n\nif (matches.length === 1) {\n const container = matches[0];\n const name = normalizeName(container.Names[0]);\n const state = container.State;\n const status = container.Status;\n const image = container.Image;\n \n let text = '<b>' + name + '</b>\\n';\n text += 'State: ' + state + '\\n';\n text += 'Status: ' + status + '\\n';\n text += 'Image: ' + image;\n \n return [{\n json: {\n chatId: chatId,\n text: text\n }\n }];\n}\n\n// Multiple matches - list them\nconst matchList = matches.map(c => {\n const name = normalizeName(c.Names[0]);\n return ' \\u2022 ' + name + ' (' + c.State + ')';\n}).join('\\n');\n\nreturn [{\n json: {\n chatId: chatId,\n text: 'Multiple matches for \"' + requestedName + '\":\\n' + matchList\n }\n}];"
|
||||||
},
|
},
|
||||||
"id": "code-parse-match",
|
"id": "code-parse-match",
|
||||||
"name": "Parse and Match",
|
"name": "Parse and Match",
|
||||||
@@ -348,48 +399,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "const message = $input.item.json.message;\nconst timestamp = new Date().toISOString();\nconst text = message.text || '(no text)';\n\nreturn {\n json: {\n chatId: message.chat.id,\n text: `Got: ${text}\\n\\nProcessed: ${timestamp}`\n }\n};"
|
"jsCode": "// Parse action from message text directly\nconst message = $json.message;\nconst text = message.text.toLowerCase().trim();\nconst chatId = message.chat.id;\nconst messageId = message.message_id;\n\n// Determine action from the text\nlet requestedAction = null;\nlet containerQuery = '';\n\n// Parse action and container name from message\nif (text.startsWith('start ')) {\n requestedAction = 'start';\n containerQuery = text.substring(6).trim();\n} else if (text.startsWith('stop ')) {\n requestedAction = 'stop';\n containerQuery = text.substring(5).trim();\n} else if (text.startsWith('restart ')) {\n requestedAction = 'restart';\n containerQuery = text.substring(8).trim();\n} else if (text.includes('start')) {\n requestedAction = 'start';\n containerQuery = text.replace('start', '').trim();\n} else if (text.includes('stop')) {\n requestedAction = 'stop';\n containerQuery = text.replace('stop', '').trim();\n} else if (text.includes('restart')) {\n requestedAction = 'restart';\n containerQuery = text.replace('restart', '').trim();\n}\n\nif (!requestedAction || !containerQuery) {\n return {\n json: {\n error: true,\n errorMessage: 'Please specify action and container name (e.g., \"start plex\")',\n chatId: chatId\n }\n };\n}\n\nreturn {\n json: {\n action: requestedAction,\n containerQuery: containerQuery,\n chatId: chatId,\n messageId: messageId\n }\n};"
|
||||||
},
|
},
|
||||||
"id": "code-format-echo",
|
"id": "code-parse-action-command",
|
||||||
"name": "Format Echo",
|
"name": "Parse Action Command",
|
||||||
"type": "n8n-nodes-base.code",
|
|
||||||
"typeVersion": 2,
|
|
||||||
"position": [
|
|
||||||
900,
|
|
||||||
800
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"resource": "message",
|
|
||||||
"operation": "sendMessage",
|
|
||||||
"chatId": "={{ $json.chatId }}",
|
|
||||||
"text": "={{ $json.text }}",
|
|
||||||
"additionalFields": {
|
|
||||||
"parse_mode": "HTML"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "telegram-send",
|
|
||||||
"name": "Send Echo",
|
|
||||||
"type": "n8n-nodes-base.telegram",
|
|
||||||
"typeVersion": 1.2,
|
|
||||||
"position": [
|
|
||||||
1120,
|
|
||||||
800
|
|
||||||
],
|
|
||||||
"credentials": {
|
|
||||||
"telegramApi": {
|
|
||||||
"id": "telegram-credential",
|
|
||||||
"name": "Telegram API"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"jsCode": "// Parse action from intent\n// Intent structure: { action: \"container_action\", container: \"name\", parameters: { action: \"restart\" } }\nconst text = $json.original_message.text.toLowerCase().trim();\nconst chatId = $json.original_message.chat.id;\nconst messageId = $json.original_message.message_id;\n\n// If intent provided an action, use it\nlet requestedAction = $json.parameters.action || null;\nlet containerQuery = $json.container;\n\n// If no action in parameters, try to infer from the original text\nif (!requestedAction) {\n if (text.includes('start') && !text.includes('restart')) {\n requestedAction = 'start';\n } else if (text.includes('stop')) {\n requestedAction = 'stop';\n } else if (text.includes('restart')) {\n requestedAction = 'restart';\n } else if (text.includes('update')) {\n requestedAction = 'update';\n }\n}\n\nif (!requestedAction || !containerQuery) {\n return {\n json: {\n error: true,\n errorMessage: 'Please specify an action (start/stop/restart/update) and container name.',\n chatId: chatId\n }\n };\n}\n\n// Map action names\nconst actionMap = {\n 'start': 'start',\n 'stop': 'stop',\n 'restart': 'restart',\n 'update': 'update'\n};\n\nconst action = actionMap[requestedAction.toLowerCase()];\n\nif (!action) {\n return {\n json: {\n error: true,\n errorMessage: 'Unknown action. Valid actions: start, stop, restart, update',\n chatId: chatId\n }\n };\n}\n\nreturn {\n json: {\n action: action,\n containerQuery: containerQuery,\n chatId: chatId,\n messageId: messageId\n }\n};"
|
|
||||||
},
|
|
||||||
"id": "code-parse-action",
|
|
||||||
"name": "Parse Action",
|
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
@@ -413,7 +426,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Get Docker API response and action info from Parse Intent\nconst dockerOutput = $input.item.json.stdout;\nconst intent = $('Parse Intent').item.json;\nconst action = intent.parameters?.action || 'restart'; // Default action\nconst containerQuery = intent.container || '';\nconst chatId = intent.original_message?.chat?.id;\n\n// Parse JSON response\nlet containers;\ntry {\n if (!dockerOutput || dockerOutput.trim() === '') {\n throw new Error('Empty response');\n }\n containers = JSON.parse(dockerOutput);\n} catch (e) {\n return [{\n json: {\n matchCount: -1,\n error: true,\n chatId: chatId,\n text: \"Can't reach Docker - check if n8n has socket access\"\n }\n }];\n}\n\n// Function to normalize container names\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, '') // Remove leading slash\n .replace(/^(linuxserver[-_]|binhex[-_])/i, '') // Remove common prefixes\n .toLowerCase();\n}\n\n// Find matching containers using fuzzy matching\nconst matches = containers.filter(c => {\n const containerName = normalizeName(c.Names[0]);\n const normalized = containerQuery.toLowerCase();\n return containerName.includes(normalized) || normalized.includes(containerName);\n});\n\n// Return match results with all necessary context\n// Include allContainers for suggestion finding when no matches\nreturn [{\n json: {\n matches: matches.map(c => ({\n Id: c.Id,\n Name: c.Names[0].replace(/^\\//, ''),\n State: c.State\n })),\n matchCount: matches.length,\n action: action,\n containerQuery: containerQuery,\n chatId: chatId,\n allContainers: containers\n }\n}];"
|
"jsCode": "// Get Docker API response and action info from Parse Action Command\nconst dockerOutput = $input.item.json.stdout;\nconst actionData = $('Parse Action Command').item.json;\nconst action = actionData.action || 'restart';\nconst containerQuery = actionData.containerQuery || '';\nconst chatId = actionData.chatId;\n\n// Parse JSON response\nlet containers;\ntry {\n if (!dockerOutput || dockerOutput.trim() === '') {\n throw new Error('Empty response');\n }\n containers = JSON.parse(dockerOutput);\n} catch (e) {\n return [{\n json: {\n matchCount: -1,\n error: true,\n chatId: chatId,\n text: \"Can't reach Docker - check if n8n has socket access\"\n }\n }];\n}\n\n// Function to normalize container names\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, '') // Remove leading slash\n .replace(/^(linuxserver[-_]|binhex[-_])/i, '') // Remove common prefixes\n .toLowerCase();\n}\n\n// Find matching containers using fuzzy matching\nconst matches = containers.filter(c => {\n const containerName = normalizeName(c.Names[0]);\n const normalized = containerQuery.toLowerCase();\n return containerName.includes(normalized) || normalized.includes(containerName);\n});\n\n// Return match results with all necessary context\nreturn [{\n json: {\n matches: matches.map(c => ({\n Id: c.Id,\n Name: c.Names[0].replace(/^\\//, ''),\n State: c.State\n })),\n matchCount: matches.length,\n action: action,\n containerQuery: containerQuery,\n chatId: chatId,\n allContainers: containers\n }\n}];"
|
||||||
},
|
},
|
||||||
"id": "code-match-container",
|
"id": "code-match-container",
|
||||||
"name": "Match Container",
|
"name": "Match Container",
|
||||||
@@ -1797,7 +1810,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Parse logs command from intent\n// Intent structure: { action: \"view_logs\", container: \"name\", parameters: { lines: 50 } }\nconst chatId = $json.original_message.chat.id;\nconst messageId = $json.original_message.message_id;\nconst containerQuery = $json.container;\nconst lines = $json.parameters.lines || 50;\n\nif (!containerQuery) {\n return {\n json: {\n error: true,\n errorMessage: 'Please specify which container logs you want to see.',\n chatId: chatId\n }\n };\n}\n\n// Validate line count (reasonable limits)\nconst validatedLines = Math.min(Math.max(lines, 1), 1000);\n\nreturn {\n json: {\n containerQuery: containerQuery,\n lines: validatedLines,\n chatId: chatId,\n messageId: messageId\n }\n};"
|
"jsCode": "// Parse logs command from message\nconst message = $json.message;\nconst text = message.text.toLowerCase().trim();\nconst chatId = message.chat.id;\nconst messageId = message.message_id;\n\n// Extract container name and optional line count\n// Formats: \"logs plex\", \"logs plex 100\", \"show logs plex\"\nlet containerQuery = '';\nlet lineCount = 50; // default\n\n// Remove \"show\" prefix if present\nlet cleanText = text.replace(/^show\\s+/, '');\n// Remove \"logs\" keyword\ncleanText = cleanText.replace(/^logs\\s*/, '');\n\n// Check for line count at the end\nconst parts = cleanText.trim().split(/\\s+/);\nif (parts.length > 1) {\n const lastPart = parts[parts.length - 1];\n if (/^\\d+$/.test(lastPart)) {\n lineCount = Math.min(Math.max(parseInt(lastPart), 1), 1000);\n parts.pop();\n }\n}\ncontainerQuery = parts.join(' ').trim();\n\nif (!containerQuery) {\n return {\n json: {\n error: true,\n chatId: chatId,\n text: 'Please specify a container name (e.g., \"logs plex\" or \"logs plex 100\")'\n }\n };\n}\n\nreturn {\n json: {\n containerQuery: containerQuery,\n lineCount: lineCount,\n chatId: chatId,\n messageId: messageId\n }\n};"
|
||||||
},
|
},
|
||||||
"id": "code-parse-logs",
|
"id": "code-parse-logs",
|
||||||
"name": "Parse Logs Command",
|
"name": "Parse Logs Command",
|
||||||
@@ -2056,321 +2069,22 @@
|
|||||||
600
|
600
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"jsCode": "// Build Claude API request body\nconst userMessage = $json.message?.text || '';\n\nconst systemPrompt = `You are a Docker container management assistant. Parse user requests and return ONLY valid JSON.\n\nValid actions:\n- view_logs: User wants to see container logs\n- query_stats: User asks about resource usage (memory, CPU)\n- container_action: User wants to start/stop/restart/update a container\n- container_status: User asks about container status\n- list_containers: User wants to see all containers\n- unknown: Cannot determine intent\n\nRespond with JSON: {\"action\": \"<action>\", \"container\": \"<name or null>\", \"parameters\": {}}\n\nExamples:\n- \"show me plex logs\" -> {\"action\": \"view_logs\", \"container\": \"plex\", \"parameters\": {\"lines\": 50}}\n- \"what's using the most memory?\" -> {\"action\": \"query_stats\", \"container\": null, \"parameters\": {\"metric\": \"memory\", \"sort\": \"desc\"}}\n- \"restart nginx\" -> {\"action\": \"container_action\", \"container\": \"nginx\", \"parameters\": {\"action\": \"restart\"}}\n- \"how's sonarr doing?\" -> {\"action\": \"container_status\", \"container\": \"sonarr\", \"parameters\": {}}\n- \"hello\" -> {\"action\": \"unknown\", \"container\": null, \"parameters\": {\"message\": \"I can help with Docker containers. Try: 'show logs', 'restart plex', or 'what's using memory?'\"}}`;\n\nconst body = {\n model: 'claude-sonnet-4-5-20250929',\n max_tokens: 256,\n system: [{\n type: 'text',\n text: systemPrompt,\n cache_control: { type: 'ephemeral' }\n }],\n messages: [{\n role: 'user',\n content: userMessage\n }]\n};\n\nreturn {\n claudeBody: JSON.stringify(body),\n message: $json.message\n};"
|
|
||||||
},
|
|
||||||
"id": "code-prepare-claude-body",
|
|
||||||
"name": "Prepare Claude Request",
|
|
||||||
"type": "n8n-nodes-base.code",
|
|
||||||
"typeVersion": 2,
|
|
||||||
"position": [
|
|
||||||
900,
|
|
||||||
300
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
"url": "https://api.anthropic.com/v1/messages",
|
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/sendMessage",
|
||||||
"authentication": "genericCredentialType",
|
|
||||||
"genericAuthType": "httpHeaderAuth",
|
|
||||||
"sendHeaders": true,
|
|
||||||
"headerParameters": {
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "anthropic-version",
|
|
||||||
"value": "2023-06-01"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sendBody": true,
|
"sendBody": true,
|
||||||
"specifyBody": "json",
|
"specifyBody": "json",
|
||||||
"jsonBody": "={{ $json.claudeBody }}",
|
"jsonBody": "={{ JSON.stringify({ \"chat_id\": $json.message.chat.id, \"text\": \"Use buttons below or type commands:\", \"parse_mode\": \"HTML\", \"reply_markup\": { \"keyboard\": [[{\"text\": \"Status\"}], [{\"text\": \"Start\"}, {\"text\": \"Stop\"}], [{\"text\": \"Restart\"}, {\"text\": \"Update\"}], [{\"text\": \"Logs\"}]], \"is_persistent\": true, \"resize_keyboard\": true, \"one_time_keyboard\": false } }) }}",
|
||||||
"options": {
|
"options": {}
|
||||||
"timeout": 30000,
|
|
||||||
"retry": {
|
|
||||||
"enabled": true,
|
|
||||||
"maxTries": 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"id": "http-claude-intent",
|
"id": "http-show-menu",
|
||||||
"name": "Claude Intent Parser",
|
"name": "Show Menu",
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
"typeVersion": 4.2,
|
"typeVersion": 4.2,
|
||||||
"position": [
|
|
||||||
1010,
|
|
||||||
300
|
|
||||||
],
|
|
||||||
"credentials": {
|
|
||||||
"httpHeaderAuth": {
|
|
||||||
"id": "anthropic-api-key",
|
|
||||||
"name": "Anthropic API Key"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"jsCode": "// Parse and validate Claude's intent response\nconst response = $input.item.json;\n\n// Claude response structure: { content: [{ type: \"text\", text: \"...\" }] }\nlet intentText = '';\ntry {\n intentText = response.content[0].text;\n} catch (e) {\n return {\n action: 'error',\n error: 'Invalid Claude response structure',\n raw: JSON.stringify(response)\n };\n}\n\n// Parse JSON from Claude's response\nlet intent;\ntry {\n // Claude might wrap JSON in markdown code blocks, strip them\n const cleaned = intentText.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n intent = JSON.parse(cleaned);\n} catch (e) {\n return {\n action: 'error',\n error: 'Could not parse intent JSON',\n raw: intentText\n };\n}\n\n// Validate required fields\nconst validActions = ['view_logs', 'query_stats', 'container_action', 'container_status', 'list_containers', 'unknown'];\nif (!intent.action || !validActions.includes(intent.action)) {\n return {\n action: 'unknown',\n error: 'Invalid or missing action',\n parameters: { message: 'I didn\\'t understand that. Try: \"show logs plex\" or \"restart nginx\"' }\n };\n}\n\n// Normalize container name if present\nif (intent.container) {\n intent.container = intent.container.toLowerCase().trim();\n}\n\n// Set defaults for parameters\nintent.parameters = intent.parameters || {};\n\n// Preserve original message for fallback - get from Prepare Claude Request node\nintent.original_message = $('Prepare Claude Request').item.json.message || {};\n\nreturn intent;"
|
|
||||||
},
|
|
||||||
"id": "code-parse-intent",
|
|
||||||
"name": "Parse Intent",
|
|
||||||
"type": "n8n-nodes-base.code",
|
|
||||||
"typeVersion": 2,
|
|
||||||
"position": [
|
"position": [
|
||||||
1120,
|
1120,
|
||||||
300
|
300
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"rules": {
|
|
||||||
"values": [
|
|
||||||
{
|
|
||||||
"id": "route-view-logs",
|
|
||||||
"conditions": {
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true,
|
|
||||||
"leftValue": "",
|
|
||||||
"typeValidation": "strict"
|
|
||||||
},
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"id": "is-view-logs",
|
|
||||||
"leftValue": "={{ $json.action }}",
|
|
||||||
"rightValue": "view_logs",
|
|
||||||
"operator": {
|
|
||||||
"type": "string",
|
|
||||||
"operation": "equals"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"combinator": "and"
|
|
||||||
},
|
|
||||||
"renameOutput": true,
|
|
||||||
"outputKey": "view_logs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "route-query-stats",
|
|
||||||
"conditions": {
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true,
|
|
||||||
"leftValue": "",
|
|
||||||
"typeValidation": "strict"
|
|
||||||
},
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"id": "is-query-stats",
|
|
||||||
"leftValue": "={{ $json.action }}",
|
|
||||||
"rightValue": "query_stats",
|
|
||||||
"operator": {
|
|
||||||
"type": "string",
|
|
||||||
"operation": "equals"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"combinator": "and"
|
|
||||||
},
|
|
||||||
"renameOutput": true,
|
|
||||||
"outputKey": "query_stats"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "route-container-action",
|
|
||||||
"conditions": {
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true,
|
|
||||||
"leftValue": "",
|
|
||||||
"typeValidation": "strict"
|
|
||||||
},
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"id": "is-container-action",
|
|
||||||
"leftValue": "={{ $json.action }}",
|
|
||||||
"rightValue": "container_action",
|
|
||||||
"operator": {
|
|
||||||
"type": "string",
|
|
||||||
"operation": "equals"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"combinator": "and"
|
|
||||||
},
|
|
||||||
"renameOutput": true,
|
|
||||||
"outputKey": "container_action"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "route-container-status",
|
|
||||||
"conditions": {
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true,
|
|
||||||
"leftValue": "",
|
|
||||||
"typeValidation": "strict"
|
|
||||||
},
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"id": "is-container-status",
|
|
||||||
"leftValue": "={{ $json.action }}",
|
|
||||||
"rightValue": "container_status",
|
|
||||||
"operator": {
|
|
||||||
"type": "string",
|
|
||||||
"operation": "equals"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"combinator": "and"
|
|
||||||
},
|
|
||||||
"renameOutput": true,
|
|
||||||
"outputKey": "container_status"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "route-list-containers",
|
|
||||||
"conditions": {
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true,
|
|
||||||
"leftValue": "",
|
|
||||||
"typeValidation": "strict"
|
|
||||||
},
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"id": "is-list-containers",
|
|
||||||
"leftValue": "={{ $json.action }}",
|
|
||||||
"rightValue": "list_containers",
|
|
||||||
"operator": {
|
|
||||||
"type": "string",
|
|
||||||
"operation": "equals"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"combinator": "and"
|
|
||||||
},
|
|
||||||
"renameOutput": true,
|
|
||||||
"outputKey": "list_containers"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "route-unknown",
|
|
||||||
"conditions": {
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true,
|
|
||||||
"leftValue": "",
|
|
||||||
"typeValidation": "strict"
|
|
||||||
},
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"id": "is-unknown",
|
|
||||||
"leftValue": "={{ $json.action }}",
|
|
||||||
"rightValue": "unknown",
|
|
||||||
"operator": {
|
|
||||||
"type": "string",
|
|
||||||
"operation": "equals"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"combinator": "and"
|
|
||||||
},
|
|
||||||
"renameOutput": true,
|
|
||||||
"outputKey": "unknown"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "route-error",
|
|
||||||
"conditions": {
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true,
|
|
||||||
"leftValue": "",
|
|
||||||
"typeValidation": "strict"
|
|
||||||
},
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"id": "is-error",
|
|
||||||
"leftValue": "={{ $json.action }}",
|
|
||||||
"rightValue": "error",
|
|
||||||
"operator": {
|
|
||||||
"type": "string",
|
|
||||||
"operation": "equals"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"combinator": "and"
|
|
||||||
},
|
|
||||||
"renameOutput": true,
|
|
||||||
"outputKey": "error"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"fallbackOutput": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "switch-intent-router",
|
|
||||||
"name": "Intent Router",
|
|
||||||
"type": "n8n-nodes-base.switch",
|
|
||||||
"typeVersion": 3.2,
|
|
||||||
"position": [
|
|
||||||
1340,
|
|
||||||
300
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"chatId": "={{ $json.original_message.chat.id }}",
|
|
||||||
"text": "={{ $json.parameters.message || \"I can help manage your Docker containers. Try:\\n- 'show logs plex'\\n- 'restart sonarr'\\n- 'what containers are running?'\\n- 'what's using the most memory?'\" }}",
|
|
||||||
"additionalFields": {
|
|
||||||
"parse_mode": "HTML"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "telegram-send-unknown",
|
|
||||||
"name": "Send Unknown Intent",
|
|
||||||
"type": "n8n-nodes-base.telegram",
|
|
||||||
"typeVersion": 1.2,
|
|
||||||
"position": [
|
|
||||||
1560,
|
|
||||||
100
|
|
||||||
],
|
|
||||||
"credentials": {
|
|
||||||
"telegramApi": {
|
|
||||||
"id": "telegram-credential",
|
|
||||||
"name": "Telegram API"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"chatId": "={{ $json.original_message.chat.id }}",
|
|
||||||
"text": "Stats queries coming soon! For now, try 'status' to see running containers.",
|
|
||||||
"additionalFields": {
|
|
||||||
"parse_mode": "HTML"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "telegram-send-stats-placeholder",
|
|
||||||
"name": "Send Stats Placeholder",
|
|
||||||
"type": "n8n-nodes-base.telegram",
|
|
||||||
"typeVersion": 1.2,
|
|
||||||
"position": [
|
|
||||||
1560,
|
|
||||||
200
|
|
||||||
],
|
|
||||||
"credentials": {
|
|
||||||
"telegramApi": {
|
|
||||||
"id": "telegram-credential",
|
|
||||||
"name": "Telegram API"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"chatId": "={{ $json.original_message.chat.id }}",
|
|
||||||
"text": "Sorry, I encountered an error understanding your request. Please try again.",
|
|
||||||
"additionalFields": {
|
|
||||||
"parse_mode": "HTML"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "telegram-send-intent-error",
|
|
||||||
"name": "Send Intent Error",
|
|
||||||
"type": "n8n-nodes-base.telegram",
|
|
||||||
"typeVersion": 1.2,
|
|
||||||
"position": [
|
|
||||||
1560,
|
|
||||||
500
|
|
||||||
],
|
],
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"telegramApi": {
|
"telegramApi": {
|
||||||
@@ -2414,7 +2128,7 @@
|
|||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Prepare Claude Request",
|
"node": "Keyword Router",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
@@ -2422,17 +2136,6 @@
|
|||||||
[]
|
[]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Prepare Claude Request": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Claude Intent Parser",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"IF Callback Authenticated": {
|
"IF Callback Authenticated": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
@@ -2587,38 +2290,6 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Route Message": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Docker List Containers",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Parse Action",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Parse Update Command",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Format Echo",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Docker List Containers": {
|
"Docker List Containers": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
@@ -2652,28 +2323,6 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Format Echo": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Send Echo",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Parse Action": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Docker List for Action",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Docker List for Action": {
|
"Docker List for Action": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
@@ -3308,30 +2957,50 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Claude Intent Parser": {
|
"Keyword Router": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Parse Intent",
|
"node": "Show Menu",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
]
|
|
||||||
},
|
|
||||||
"Parse Intent": {
|
|
||||||
"main": [
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Intent Router",
|
"node": "Docker List Containers",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
]
|
[
|
||||||
},
|
{
|
||||||
"Intent Router": {
|
"node": "Parse Action Command",
|
||||||
"main": [
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Parse Action Command",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Parse Action Command",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Parse Update Command",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Parse Logs Command",
|
"node": "Parse Logs Command",
|
||||||
@@ -3341,45 +3010,21 @@
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Send Stats Placeholder",
|
"node": "Show Menu",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Parse Action Command": {
|
||||||
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Docker List for Action",
|
"node": "Docker List for Action",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Docker List Containers",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Docker List Containers",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Send Unknown Intent",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Send Intent Error",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user