fix: script.py guillemets, cron bad minute, system_prompt scripts bash

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 19:11:23 +00:00
parent d3ca3eee68
commit ba59e3b4c5
4 changed files with 54 additions and 15 deletions
+20 -7
View File
@@ -22,17 +22,30 @@ Tu reçois des instructions via MQTT (depuis Nexus) ou XMPP (directement).
1. Utilise toujours le skill le plus spécifique disponible 1. Utilise toujours le skill le plus spécifique disponible
2. Préfère plusieurs appels de skills atomiques plutôt qu'une commande shell complexe 2. Préfère plusieurs appels de skills atomiques plutôt qu'une commande shell complexe
3. Après chaque action importante (install, restart, delete), vérifie le résultat 3. Après chaque action importante (install, restart, delete), vérifie le résultat
4. Si une tâche génère un script, utilise SKILL:script pour le créer et l'exécuter, 4. **OBLIGATOIRE : tout script bash doit être créé via `SKILL:script ARGS:save <nom> | <contenu>` et jamais via `filesystem write`.** N'utilise JAMAIS filesystem pour écrire un fichier .sh.
et le résultat sera automatiquement renvoyé via MQTT
5. En cas d'erreur, diagnostique avant de réessayer 5. En cas d'erreur, diagnostique avant de réessayer
6. Réponds toujours en français 6. Réponds toujours en français
7. Sois concis dans tes réponses — l'essentiel, pas tout le stdout brut 7. Sois concis dans tes réponses — l'essentiel, pas tout le stdout brut
## Communication MQTT ## Écriture de scripts bash — règles strictes
### ❌ Interdit dans les scripts bash
- `muc_send`, `mqtt_send`, `shell` et tous les noms de skills — ce ne sont PAS des commandes bash
- Les guillemets échappés : écris `"texte"` et non `\"texte\"`
### ✅ Pour envoyer un message depuis un script
Variables injectées automatiquement : `$MQTT_BROKER`, `$MQTT_REPLY_TOPIC`, `$AGENT_ID`
```bash
mosquitto_pub -h "$MQTT_BROKER" -t "$MQTT_REPLY_TOPIC" -m "mon résultat"
```
### ✅ Bonnes pratiques
- Commence toujours par `#!/bin/bash` et `set -euo pipefail`
- Guillemets doubles autour des variables : `"$VAR"`
- Gère les cas d'erreur avec des messages explicites
## Communication MQTT (depuis le LLM, pas depuis un script)
Tu peux envoyer des messages à d'autres agents : Tu peux envoyer des messages à d'autres agents :
SKILL:mqtt_send ARGS:agents/nexus/inbox | {"type":"result","payload":"mon résultat"} SKILL:mqtt_send ARGS:agents/nexus/inbox | {"type":"result","payload":"mon résultat"}
Pour les scripts qui doivent retourner un résultat :
Les variables $MQTT_BROKER et $MQTT_REPLY_TOPIC sont disponibles dans l'environnement.
mosquitto_pub -h $MQTT_BROKER -t $MQTT_REPLY_TOPIC -m "résultat"
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Récupération des mises à jour en filtrant l'entête \"Listing...\"
updates=$(apt list --upgradable 2>/dev/null | grep -v \"^Listing\")
if [ -z \"$updates\" ]; then
payload='{\"type\":\"updates\",\"payload\":\"Aucune mise à jour disponible\"}'
else
payload=$(jq -nc --arg p \"$updates\" '{\"type\":\"updates\",\"payload\":$p}')
fi
mosquitto_pub -h \"${MQTT_BROKER}\" -t \"${MQTT_REPLY_TOPIC}\" -m \"${payload}\"
+21 -7
View File
@@ -35,6 +35,16 @@ def _run(cmd: str, timeout: int = 10) -> str:
return str(e) return str(e)
def _get_current_crontab() -> str:
"""Retourne le crontab actuel, ou chaîne vide si inexistant."""
result = subprocess.run(
"crontab -l", shell=True, text=True, capture_output=True
)
if result.returncode != 0:
return ""
return result.stdout.strip()
def run(args: str, context) -> str: def run(args: str, context) -> str:
parts = args.strip().split(None, 1) parts = args.strip().split(None, 1)
action = parts[0].lower() if parts else "list" action = parts[0].lower() if parts else "list"
@@ -54,26 +64,28 @@ def run(args: str, context) -> str:
command = " ".join(words[5:]) command = " ".join(words[5:])
entry = f"{cron_expr} {command}" entry = f"{cron_expr} {command}"
current = _run("crontab -l 2>/dev/null") current = _get_current_crontab()
if entry in current: if entry in current:
return f"Cette entrée existe déjà : {entry}" return f"Cette entrée existe déjà : {entry}"
def _do_add(): def _do_add():
with tempfile.NamedTemporaryFile(mode="w", suffix=".cron", delete=False) as f: with tempfile.NamedTemporaryFile(mode="w", suffix=".cron", delete=False) as f:
if current and "no crontab" not in current.lower(): if current:
f.write(current + "\n") f.write(current + "\n")
f.write(entry + "\n") f.write(entry + "\n")
tmpfile = f.name tmpfile = f.name
out = _run(f"crontab {tmpfile}") result = subprocess.run(f"crontab {tmpfile}", shell=True, text=True, capture_output=True)
os.unlink(tmpfile) os.unlink(tmpfile)
return f"Entrée ajoutée : {entry}\n{out}" if result.returncode != 0:
return f"❌ Erreur crontab : {(result.stdout + result.stderr).strip()}"
return f"✅ Entrée ajoutée : {entry}"
return _confirm_or_execute(context, f"Ajouter cron : {entry}", _do_add) return _confirm_or_execute(context, f"Ajouter cron : {entry}", _do_add)
if action == "remove": if action == "remove":
if not rest: if not rest:
return "Précise le pattern à supprimer." return "Précise le pattern à supprimer."
current = _run("crontab -l 2>/dev/null") current = _get_current_crontab()
lines = [l for l in current.splitlines() if rest not in l] lines = [l for l in current.splitlines() if rest not in l]
removed_count = len(current.splitlines()) - len(lines) removed_count = len(current.splitlines()) - len(lines)
if removed_count == 0: if removed_count == 0:
@@ -84,9 +96,11 @@ def run(args: str, context) -> str:
with tempfile.NamedTemporaryFile(mode="w", suffix=".cron", delete=False) as f: with tempfile.NamedTemporaryFile(mode="w", suffix=".cron", delete=False) as f:
f.write(new_cron + "\n") f.write(new_cron + "\n")
tmpfile = f.name tmpfile = f.name
out = _run(f"crontab {tmpfile}") result = subprocess.run(f"crontab {tmpfile}", shell=True, text=True, capture_output=True)
os.unlink(tmpfile) os.unlink(tmpfile)
return f"{removed_count} entrée(s) supprimée(s) contenant '{rest}'.\n{out}" if result.returncode != 0:
return f"❌ Erreur crontab : {(result.stdout + result.stderr).strip()}"
return f"{removed_count} entrée(s) supprimée(s) contenant '{rest}'."
return _confirm_or_execute(context, f"Supprimer {removed_count} cron contenant '{rest}'", _do_remove) return _confirm_or_execute(context, f"Supprimer {removed_count} cron contenant '{rest}'", _do_remove)
+1 -1
View File
@@ -147,7 +147,7 @@ def run(args: str, context) -> str:
return "Format : save <nom> | <contenu du script>" return "Format : save <nom> | <contenu du script>"
name_raw, content = rest.split("|", 1) name_raw, content = rest.split("|", 1)
name = _safe_name(name_raw) name = _safe_name(name_raw)
content = content.strip().replace("\\n", "\n") content = content.strip().replace("\\n", "\n").replace('\\"', '"').replace("\\'", "'")
if not name: if not name:
return "Nom de script invalide." return "Nom de script invalide."