node-red-contrib-linux-copilot 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/linux-copilot.html +17 -18
- package/linux-copilot.js +12 -23
- package/package.json +4 -36
package/README.md
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
cat > ~/.node-red/node_modules/node-red-contrib-linux-copilot/README.md << 'EOF'
|
|
2
|
+
# node-red-contrib-linux-copilot
|
|
2
3
|
|
|
4
|
+
An advanced, autonomous SRE (Site Reliability Engineering) agent for Node-RED. This node doesn't just talk; it thinks, executes Linux commands, analyzes the output, and follows up until the diagnostic is complete.
|
|
3
5
|
|
|
6
|
+
## 🚀 Key Features
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
* **Multi-Engine Failover**: Supports **DeepSeek**, **OpenRouter (Llama 3.3)**, and **Google Gemini 2.0**.
|
|
9
|
+
* **Autonomous Diagnostic Loop**: If the AI suggests a command, the node executes it, reads the output, and sends it back to the AI for further analysis (up to 5 iterations).
|
|
10
|
+
* **Auto-Language Detection**: Speak to it in French, English, Spanish, or any language; it will detect and respond in kind.
|
|
11
|
+
* **SRE Expertise**: Specialized in system health, performance bottleneck identification, and log analysis.
|
|
12
|
+
* **Secure by Design**: Restricted to a safe list of audit and monitoring commands (`df`, `top`, `free`, `systemctl`, etc.).
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🛠 Installation
|
|
11
17
|
|
|
12
|
-
## Installation
|
|
13
18
|
Run the following command in your Node-RED user directory (typically `~/.node-red`):
|
|
19
|
+
|
|
14
20
|
```bash
|
|
15
21
|
npm install node-red-contrib-linux-copilot
|
package/linux-copilot.html
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
name: {value:""},
|
|
7
7
|
prioDS: {value:"1"},
|
|
8
8
|
prioOR: {value:"2"},
|
|
9
|
-
prioGEM: {value:"3"}
|
|
9
|
+
prioGEM: {value:"3"},
|
|
10
|
+
chatId: {value:"1457427557"}
|
|
10
11
|
},
|
|
11
12
|
credentials: {
|
|
12
13
|
deepseekKey: {type:"password"},
|
|
@@ -16,36 +17,34 @@
|
|
|
16
17
|
inputs:1,
|
|
17
18
|
outputs:1,
|
|
18
19
|
icon: "font-awesome/fa-terminal",
|
|
19
|
-
label: function() {
|
|
20
|
-
return this.name || "linux-copilot";
|
|
21
|
-
}
|
|
20
|
+
label: function() { return this.name || "linux-copilot"; }
|
|
22
21
|
});
|
|
23
22
|
</script>
|
|
24
23
|
|
|
25
24
|
<script type="text/html" data-template-name="linux-copilot">
|
|
26
25
|
<div class="form-row">
|
|
27
|
-
<label for="node-input-name"><i class="fa fa-tag"></i>
|
|
28
|
-
<input type="text" id="node-input-name" placeholder="
|
|
26
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Nom</label>
|
|
27
|
+
<input type="text" id="node-input-name" placeholder="Linux Copilot">
|
|
28
|
+
</div>
|
|
29
|
+
<div class="form-row">
|
|
30
|
+
<label for="node-input-chatId"><i class="fa fa-comment"></i> Chat ID</label>
|
|
31
|
+
<input type="text" id="node-input-chatId">
|
|
29
32
|
</div>
|
|
30
33
|
<hr>
|
|
31
|
-
<h4>
|
|
34
|
+
<h4>Priorités (1 = Premier utilisé)</h4>
|
|
32
35
|
<div class="form-row">
|
|
33
36
|
<label for="node-input-prioDS">DeepSeek</label>
|
|
34
|
-
<input type="number" id="node-input-prioDS" style="width:50px">
|
|
35
|
-
<input type="password" id="node-input-deepseekKey" placeholder="API
|
|
37
|
+
<input type="number" id="node-input-prioDS" style="width:50px" min="1">
|
|
38
|
+
<input type="password" id="node-input-deepseekKey" placeholder="Clé API" style="width:200px">
|
|
36
39
|
</div>
|
|
37
40
|
<div class="form-row">
|
|
38
41
|
<label for="node-input-prioOR">OpenRouter</label>
|
|
39
|
-
<input type="number" id="node-input-prioOR" style="width:50px">
|
|
40
|
-
<input type="password" id="node-input-openrouterKey" placeholder="API
|
|
42
|
+
<input type="number" id="node-input-prioOR" style="width:50px" min="1">
|
|
43
|
+
<input type="password" id="node-input-openrouterKey" placeholder="Clé API" style="width:200px">
|
|
41
44
|
</div>
|
|
42
45
|
<div class="form-row">
|
|
43
|
-
<label for="node-input-prioGEM">Gemini
|
|
44
|
-
<input type="number" id="node-input-prioGEM" style="width:50px">
|
|
45
|
-
<input type="password" id="node-input-geminiKey" placeholder="API
|
|
46
|
+
<label for="node-input-prioGEM">Gemini</label>
|
|
47
|
+
<input type="number" id="node-input-prioGEM" style="width:50px" min="1">
|
|
48
|
+
<input type="password" id="node-input-geminiKey" placeholder="Clé API" style="width:200px">
|
|
46
49
|
</div>
|
|
47
50
|
</script>
|
|
48
|
-
|
|
49
|
-
<script type="text/html" data-help-name="linux-copilot">
|
|
50
|
-
<p>A multi-LLM Linux SRE Copilot. Supports language detection and auto-sequential diagnostics.</p>
|
|
51
|
-
</script>
|
package/linux-copilot.js
CHANGED
|
@@ -6,13 +6,12 @@ module.exports = function(RED) {
|
|
|
6
6
|
RED.nodes.createNode(this, config);
|
|
7
7
|
const node = this;
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
const omniPrompt = `Tu es un Expert Linux SRE.
|
|
9
|
+
const omniPrompt = `Tu es un Expert Linux SRE polyglotte.
|
|
11
10
|
RÈGLES CRUCIALES :
|
|
12
|
-
1. LANGUE : Détecte la langue de l'utilisateur
|
|
13
|
-
2. ANALYSE :
|
|
14
|
-
3. ACTION : Propose
|
|
15
|
-
4. FORMAT JSON : {"speech": "ton explication dans la langue
|
|
11
|
+
1. LANGUE : Détecte la langue de l'utilisateur. Tu DOIS répondre EXCLUSIVEMENT dans cette langue (si on te parle en Français, réponds en Français; si c'est en Espagnol, réponds en Espagnol).
|
|
12
|
+
2. ANALYSE : Analyse brièvement les résultats techniques.
|
|
13
|
+
3. ACTION : Propose la commande suivante pour continuer le diagnostic.
|
|
14
|
+
4. FORMAT JSON : {"speech": "ton explication dans la langue de l'utilisateur", "cmd": "commande linux ou none"}`;
|
|
16
15
|
|
|
17
16
|
const formatHistory = (history) => history.map(h => ({
|
|
18
17
|
role: h.role === "model" ? "assistant" : h.role,
|
|
@@ -32,10 +31,7 @@ module.exports = function(RED) {
|
|
|
32
31
|
const res = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
|
|
33
32
|
model: "meta-llama/llama-3.3-70b-instruct:free",
|
|
34
33
|
messages: [{ role: 'system', content: omniPrompt }, ...formatHistory(history)]
|
|
35
|
-
}, {
|
|
36
|
-
headers: { 'Authorization': `Bearer ${key.trim()}`, 'HTTP-Referer': 'https://nodered.org', 'Content-Type': 'application/json' },
|
|
37
|
-
timeout: 25000
|
|
38
|
-
});
|
|
34
|
+
}, { headers: { 'Authorization': `Bearer ${key.trim()}`, 'Content-Type': 'application/json' }, timeout: 25000 });
|
|
39
35
|
const match = res.data.choices[0].message.content.match(/\{[\s\S]*\}/);
|
|
40
36
|
return JSON.parse(match ? match[0] : res.data.choices[0].message.content);
|
|
41
37
|
},
|
|
@@ -59,7 +55,7 @@ module.exports = function(RED) {
|
|
|
59
55
|
let history = node.context().get('history') || [];
|
|
60
56
|
if (userText.toLowerCase() === "reset") {
|
|
61
57
|
node.context().set('history', []);
|
|
62
|
-
return node.send({ payload: { chatId
|
|
58
|
+
return node.send({ payload: { chatId, type: "message", content: "♻️ Historique effacé." } });
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
if (!userText) return;
|
|
@@ -82,34 +78,27 @@ module.exports = function(RED) {
|
|
|
82
78
|
} catch (err) { node.warn(`Échec ${e.n}`); }
|
|
83
79
|
}
|
|
84
80
|
|
|
85
|
-
if (!aiData) return;
|
|
81
|
+
if (!aiData) return node.status({fill:"red", text:"Erreur API"});
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
node.send({ payload: { chatId: chatId, type: "message", content: `🤖 <b>${engineUsed}</b> : ${aiData.speech}`, options: { parse_mode: "HTML" } } });
|
|
89
|
-
}
|
|
83
|
+
node.send({ payload: { chatId, type: "message", content: `🤖 <b>${engineUsed}</b> : ${aiData.speech}`, options: { parse_mode: "HTML" } } });
|
|
90
84
|
|
|
91
85
|
let cmd = (aiData.cmd || "").trim();
|
|
92
86
|
const safeWords = ['ls', 'df', 'free', 'uptime', 'top', 'ps', 'cat', 'grep', 'iostat', 'netstat', 'ss', 'ip', 'systemctl', 'journalctl'];
|
|
93
87
|
|
|
94
88
|
if (cmd && cmd !== "none" && safeWords.some(w => cmd.includes(w))) {
|
|
95
89
|
if (cmd.startsWith("top") && !cmd.includes("-b")) cmd = "top -b -n 1 | head -n 12";
|
|
96
|
-
|
|
97
90
|
exec(cmd, { timeout: 10000 }, (err, stdout, stderr) => {
|
|
98
91
|
let res = (stdout || stderr || "OK").substring(0, 800);
|
|
99
|
-
node.send({ payload: { chatId
|
|
100
|
-
|
|
92
|
+
node.send({ payload: { chatId, type: "message", content: `📟 <b>Terminal (${cmd})</b> :\n<pre>${res}</pre>`, options: { parse_mode: "HTML" } } });
|
|
101
93
|
setTimeout(() => {
|
|
102
|
-
node.emit("input", {
|
|
103
|
-
payload: { chatId: chatId, content: `RÉSULTAT ${cmd} :\n${res}` },
|
|
104
|
-
loopCount: loopCount + 1
|
|
105
|
-
});
|
|
94
|
+
node.emit("input", { payload: { chatId, content: `RÉSULTAT ${cmd} :\n${res}` }, loopCount: loopCount + 1 });
|
|
106
95
|
}, 1200);
|
|
107
96
|
});
|
|
108
97
|
}
|
|
109
98
|
|
|
110
99
|
history.push({ role: "assistant", content: aiData.speech || "Action" });
|
|
111
100
|
node.context().set('history', history.slice(-10));
|
|
112
|
-
node.status({fill:"green", text:`
|
|
101
|
+
node.status({fill:"green", text:`Réponse via ${engineUsed}`});
|
|
113
102
|
});
|
|
114
103
|
}
|
|
115
104
|
RED.nodes.registerType('linux-copilot', LinuxCopilotNode, {
|
package/package.json
CHANGED
|
@@ -1,58 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
2
|
"name": "node-red-contrib-linux-copilot",
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"description": "A versatile SRE & SysAdmin AI FREE Copilot for Linux, supporting Gemini and OpenRouter (Llama 3.3).",
|
|
8
|
-
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "Agent SRE intelligent (DeepSeek/Gemini) avec exécution terminal sécurisée",
|
|
9
5
|
"main": "linux-copilot.js",
|
|
10
|
-
|
|
11
6
|
"scripts": {
|
|
12
|
-
|
|
13
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
|
-
|
|
15
8
|
},
|
|
16
|
-
|
|
17
9
|
"keywords": [
|
|
18
|
-
|
|
19
10
|
"node-red",
|
|
20
|
-
|
|
21
11
|
"linux",
|
|
22
|
-
|
|
23
12
|
"sre",
|
|
24
|
-
|
|
25
|
-
"ai",
|
|
26
|
-
|
|
27
13
|
"copilot",
|
|
28
|
-
|
|
14
|
+
"deepseek",
|
|
29
15
|
"gemini",
|
|
30
|
-
|
|
31
|
-
"openrouter",
|
|
32
|
-
|
|
33
|
-
"sysadmin"
|
|
34
|
-
|
|
16
|
+
"bash"
|
|
35
17
|
],
|
|
36
|
-
|
|
37
|
-
"author": "surprise_dev",
|
|
38
|
-
|
|
39
|
-
"license": "MIT",
|
|
40
|
-
|
|
41
18
|
"node-red": {
|
|
42
|
-
|
|
43
19
|
"nodes": {
|
|
44
|
-
|
|
45
20
|
"linux-copilot": "linux-copilot.js"
|
|
46
|
-
|
|
47
21
|
}
|
|
48
|
-
|
|
49
22
|
},
|
|
50
|
-
|
|
51
23
|
"dependencies": {
|
|
52
|
-
|
|
53
24
|
"axios": "^1.6.0"
|
|
54
|
-
|
|
55
25
|
}
|
|
56
|
-
|
|
57
26
|
}
|
|
58
|
-
|