hormigasais-edge-starter 0.1.2 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,48 @@
1
+ HormigasAIS Source-Available License (Proprietary)
2
+ Versión 1.0 — hormigasais-edge-starter
3
+
4
+ Copyright (c) 2026 Cristhiam Leonardo Hernández Quiñonez (CLHQ)
5
+ Todos los derechos reservados.
6
+
7
+ 1. Naturaleza de esta licencia
8
+
9
+ Esta no es una licencia de código abierto ni una Business Source License.
10
+ Es una licencia propietaria de código fuente visible ("source-available"):
11
+ el código puede leerse, estudiarse y auditarse públicamente, pero su uso,
12
+ copia, modificación y redistribución requieren autorización explícita del
13
+ Licenciante.
14
+
15
+ 2. Lo que SÍ está permitido sin autorización adicional
16
+
17
+ - Leer y estudiar el código fuente con fines de evaluación o auditoría.
18
+ - Hacer fork privado únicamente para revisión personal, sin distribuir
19
+ ni desplegar en producción.
20
+ - Instalar el paquete desde el registro npm y ejecutar el generador
21
+ (create-hormigas-node) para crear y probar un nodo propio con fines
22
+ de evaluación, aprendizaje o uso personal no comercial. Los archivos
23
+ de configuración generados para el nodo (p. ej. node_config.json)
24
+ pertenecen a quien los genera. El uso en producción sigue sujeto
25
+ a la sección 3.
26
+
27
+ 3. Lo que requiere autorización explícita de CLHQ
28
+
29
+ - Uso en producción, propio o de terceros.
30
+ - Redistribución, en forma original o modificada.
31
+ - Creación de obras derivadas destinadas a distribución o uso público.
32
+ - Cualquier uso comercial.
33
+
34
+ 4. Vigencia
35
+
36
+ Esta licencia no tiene fecha de conversión a una licencia de código
37
+ abierto. El Licenciante (CLHQ) retiene control indefinido sobre los
38
+ términos de uso de hormigasais-edge-starter, y puede otorgar licencias adicionales
39
+ o cambiar estos términos para versiones futuras a su propio criterio.
40
+
41
+ 5. Sin garantía
42
+
43
+ EL SOFTWARE SE PROPORCIONA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO,
44
+ EXPRESA O IMPLÍCITA, INCLUYENDO PERO NO LIMITADO A GARANTÍAS DE
45
+ COMERCIABILIDAD, IDONEIDAD PARA UN PROPÓSITO PARTICULAR Y NO INFRACCIÓN.
46
+
47
+ Para solicitar una licencia de uso, contactar a CLHQ a través de
48
+ github.com/Thrumanshow.
package/README.md CHANGED
@@ -4,28 +4,31 @@ Starter oficial para desplegar un **nodo soberano** de HormigasAIS con agentes L
4
4
 
5
5
  > "Soberanía digital en el edge"
6
6
 
7
- ## 🚀 Instalación Rápida
7
+ ## 🚀 Instalación
8
8
 
9
- ### Opción 1: Desde GitHub (Recomendada actualmente)
9
+ ### Opción 1 — Starter CLI vía npm (recomendado)
10
10
 
11
- \`\`\`bash
12
- npx create-hormigas-node@latest
13
- \`\`\`
11
+ El starter técnico se distribuye como paquete npm independiente, sin el contenido pedagógico completo:
14
12
 
15
- O manualmente:
13
+ npm install -g @hormigasais/edge-starter
14
+ hormigasais-edge --help
15
+ hormigasais-edge bootstrap
16
+ hormigasais-edge validate
16
17
 
17
- \`\`\`bash
18
- git clone https://github.com/Thrumanshow/hormigasais-edge-starter.git
19
- cd hormigasais-edge-starter
20
- bash bootstrap.sh
21
- python3 generator.py
22
- \`\`\`
18
+ Por defecto, el laboratorio se crea en .hormigasais-edge-lab/ en el directorio actual. Puedes cambiar la ubicación con LAB_DIR:
23
19
 
24
- ### Opción 2:
20
+ LAB_DIR="$HOME/hormigasais-edge-lab" hormigasais-edge bootstrap
25
21
 
26
- \`\`\`bash
27
- npx create-hormigas-node
28
- \`\`\`
22
+ Paquete: [@hormigasais/edge-starter](https://www.npmjs.com/package/@hormigasais/edge-starter)
23
+
24
+ ### Opción 2 — Clonar el repositorio completo (incluye el curso)
25
+
26
+ git clone https://github.com/Thrumanshow/hormigasais-edge-starter.git
27
+ cd hormigasais-edge-starter
28
+ bash bootstrap.sh
29
+ python3 generator.py
30
+
31
+ Usa esta opción si además del starter quieres acceso al curso práctico completo (./course/).
29
32
 
30
33
  ## ¿Qué incluye?
31
34
 
@@ -65,3 +68,30 @@ Author: Cristhiam Leonardo Hernández Quiñonez (CLHQ)
65
68
  © HormigasAIS • Lenguaje Binario HormigasAIS (LBH)
66
69
 
67
70
  [hormigasais.com](https://hormigasais.com) | [@Thrumanshow](https://github.com/Thrumanshow)
71
+
72
+ ## Curso Práctico
73
+
74
+ El directorio [`course/`](./course/) contiene un laboratorio educativo aislado para aprender administración Edge y protocolos LBH desde Android/Termux. Sus scripts utilizan fixtures y no requieren secretos reales. Consulta [`course/README.md`](./course/README.md) para comenzar.
75
+
76
+ ---
77
+
78
+ ## 📄 Componentes y Enlaces del Ecosistema
79
+
80
+ | Repo | Descripción |
81
+ | :--- | :--- |
82
+ | **LBH-Net** | Red soberana P2P :9199/:9200 |
83
+ | **Institucionalidad** | Foundation & Governance |
84
+
85
+ ---
86
+
87
+ ### Arquitectura y Ecosistema HormigasAIS
88
+ * **Red P2P Base:** [LBH-Net](https://github.com/HormigasAIS/LBH-Net) — Red soberana distribuida basada en Protocolo LBH (Termux / UDP :9199 / TCP :9200 / SQLite / Ed25519).
89
+ * **Vitrina Oficial:** [hormigasais.com](https://hormigasais.com)
90
+ * **Documentación Técnica:** [docs.hormigasais.com](https://docs.hormigasais.com)
91
+ * **Fundador:** CLHQ — Cristhiam Leonardo Hernández Quiñonez | El Salvador 2026 | DOI: 10.5281/zenodo.17767205
92
+
93
+ ## Licencia
94
+
95
+ HormigasAIS Source-Available License v1.0 (ver `LICENSE`). Puedes leer y estudiar el código, e instalar el paquete para generar y probar un nodo propio con fines personales no comerciales. El uso en producción, la redistribución y el uso comercial requieren autorización de CLHQ.
96
+
97
+ Las versiones 0.1.0 a 0.1.2 se publicaron declarando MIT; desde la 0.1.3 rige esta licencia.
@@ -0,0 +1,58 @@
1
+ import subprocess, json, sys, time, threading
2
+
3
+ VERSION = "0.3.0"
4
+
5
+ def reporter(trampa_ref, intervalo=30):
6
+ while True:
7
+ time.sleep(intervalo)
8
+ estado = "ACTIVO" if trampa_ref.activo else "LETARGO"
9
+ print(f"\n[REPORTE] Capturas={trampa_ref.total_capturas} | "
10
+ f"Estado={estado} | Temp={trampa_ref.temperatura}C")
11
+
12
+ def iniciar_colonia():
13
+ print(f"[HormigasAIS] Colonia Nodo Escuela v{VERSION}")
14
+ print("[BUS] Acoplando subprocesos y cargando contratos...")
15
+ print("-" * 70)
16
+
17
+ try:
18
+ from hormiga_trampa import HormigaTrampa
19
+ trampa = HormigaTrampa()
20
+ except ImportError:
21
+ print("[ERROR] Falta hormiga_trampa.py")
22
+ return
23
+
24
+ threading.Thread(
25
+ target=reporter, args=(trampa, 30), daemon=True
26
+ ).start()
27
+
28
+ proceso = subprocess.Popen(
29
+ ["python3", "hormiga_centinela.py"],
30
+ stdout=subprocess.PIPE,
31
+ stderr=subprocess.STDOUT,
32
+ text=True,
33
+ bufsize=1,
34
+ )
35
+
36
+ try:
37
+ for linea in iter(proceso.stdout.readline, ""):
38
+ linea = linea.strip()
39
+ if "FEROMONA_EMITIDA ==>" in linea:
40
+ partes = linea.split("==>")
41
+ if len(partes) > 1:
42
+ trampa.escuchar_bus(partes[1].strip())
43
+ elif "*" in linea:
44
+ sys.stdout.write("* ")
45
+ sys.stdout.flush()
46
+ trampa.escuchar_bus('{"type":"env_clear"}')
47
+ except KeyboardInterrupt:
48
+ print("\n\n[INTERRUPCION] Apagado manual detectado.")
49
+ except Exception as e:
50
+ print(f"\n[FALLO BUS] Anomalia: {e}")
51
+ finally:
52
+ print("\n[FAILSAFE] Protocolo de desconexion segura...")
53
+ proceso.terminate()
54
+ trampa.apagado_seguro()
55
+ print("[OK] Colonia cerrada de forma soberana.")
56
+
57
+ if __name__ == "__main__":
58
+ iniciar_colonia()
@@ -1,41 +1,68 @@
1
- #!/usr/bin/env python3
2
- """
3
- hormiga_centinela.py
4
- """
5
- import time
6
- import json
7
- import sys
8
- from pathlib import Path
9
-
10
- ROOT = Path(__file__).resolve().parent.parent
11
- sys.path.insert(0, str(ROOT))
12
-
13
- from utils.env_loader import load_termux_secrets
14
- load_termux_secrets()
15
-
16
- from protocols.encoder import make_header, encode_packet
17
- from protocols.constants import TYPE_CODES
18
- from protocols.signer import seal_action
19
-
20
- NODE_ID = "A16"
21
- OWNER = "Cristhiam Leonardo Hernández Quiñonez"
22
- INTERVAL = 30
23
-
24
- def emit_pulse():
25
- payload = json.dumps({
26
- "agent": "hormiga_centinela",
27
- "status": "ACTIVE",
28
- "node": NODE_ID,
29
- "ts": int(time.time())
30
- })
31
- header = make_header(NODE_ID[:3], "00")
32
- frame = encode_packet(header, TYPE_CODES["FUEL"], payload)
33
- seal = seal_action(payload, OWNER)
34
- print("[CENTINELA] FUEL pulse emitido")
35
- return frame
1
+ import os
2
+ import time, json, random, sys, hmac, hashlib
3
+
4
+ VERSION = "0.3.0"
5
+ NODE_ID = "centinela_escuela_01"
6
+ TARGET = "mosquito_pulse"
7
+ SECRET = os.environ.get("LAB_ESCUELA_SECRET", "hormigasais-colonia-escuela-demo").encode()
8
+
9
+ class UmbralAdaptativo:
10
+ def __init__(self, base=0.75, ventana=20):
11
+ self.historial = []
12
+ self.base = base
13
+ self.ventana = ventana
14
+
15
+ def actualizar(self, v):
16
+ self.historial.append(v)
17
+ if len(self.historial) > self.ventana:
18
+ self.historial.pop(0)
19
+
20
+ @property
21
+ def umbral(self):
22
+ if len(self.historial) < 5:
23
+ return self.base
24
+ media = sum(self.historial) / len(self.historial)
25
+ return round(min(media + 0.15, 0.92), 3)
26
+
27
+ def firmar(paquete_dict):
28
+ payload = json.dumps(paquete_dict, sort_keys=True).encode()
29
+ sig = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()[:16]
30
+ paquete_dict["sig"] = sig
31
+ return json.dumps(paquete_dict)
32
+
33
+ def generar_feromona(intensidad):
34
+ p = {
35
+ "timestamp": round(time.time(), 6),
36
+ "type": TARGET,
37
+ "origin": NODE_ID,
38
+ "status": "active",
39
+ "intensity": round(intensidad, 2),
40
+ "version": VERSION,
41
+ }
42
+ return firmar(p)
43
+
44
+ def ejecutar():
45
+ ua = UmbralAdaptativo()
46
+ print(f"[HormigasAIS] Centinela [{NODE_ID}] v{VERSION}")
47
+ print(f"Escaneando espectro infrarrojo... Ctrl+C para detener.")
48
+ print("-" * 70)
49
+ try:
50
+ while True:
51
+ lectura = random.uniform(0.1, 1.0)
52
+ ua.actualizar(lectura)
53
+ if lectura > ua.umbral:
54
+ print(f"[ALERTA] Gradiente={round(lectura,2)} Umbral={ua.umbral}")
55
+ paquete = generar_feromona(lectura)
56
+ print(f"[XOXO-BUS] FEROMONA_EMITIDA ==> {paquete}")
57
+ print("[BUS] Nodos trampa alertados.")
58
+ print("-" * 70)
59
+ time.sleep(5)
60
+ else:
61
+ sys.stdout.write("* ")
62
+ sys.stdout.flush()
63
+ time.sleep(1.5)
64
+ except KeyboardInterrupt:
65
+ print("\n[STOP] Centinela detenido. Modo letargo.")
36
66
 
37
67
  if __name__ == "__main__":
38
- print(f"[CENTINELA] Iniciando en nodo {NODE_ID}")
39
- while True:
40
- emit_pulse()
41
- time.sleep(INTERVAL)
68
+ ejecutar()
@@ -0,0 +1,72 @@
1
+ import os
2
+ import serial, json, sys, time, hmac, hashlib
3
+
4
+ VERSION = "1.0.0-hw"
5
+ NODE_ID = "centinela_hw_01"
6
+ SECRET = os.environ.get("LAB_ESCUELA_SECRET", "hormigasais-colonia-escuela-demo").encode()
7
+ PORT = "/dev/ttyUSB0" # Termux: ls /dev/tty* para confirmar
8
+ BAUD = 115200
9
+
10
+ def firmar(d):
11
+ payload = json.dumps(d, sort_keys=True).encode()
12
+ sig = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()[:16]
13
+ d["sig"] = sig
14
+ return json.dumps(d)
15
+
16
+ def abrir_serial():
17
+ intentos = 0
18
+ while intentos < 5:
19
+ try:
20
+ s = serial.Serial(PORT, BAUD, timeout=2)
21
+ print(f"[HW] Puerto {PORT} abierto a {BAUD} baud.")
22
+ return s
23
+ except serial.SerialException as e:
24
+ intentos += 1
25
+ print(f"[HW] Reintento {intentos}/5 — {e}")
26
+ time.sleep(3)
27
+ print(f"[ERROR] No se pudo abrir {PORT}. Verifica: pkg install python-pyserial")
28
+ sys.exit(1)
29
+
30
+ def ejecutar():
31
+ print(f"[HormigasAIS] Centinela Hardware [{NODE_ID}] v{VERSION}")
32
+ print(f"[HW] Escuchando ESP8266 en {PORT}...")
33
+ print("-" * 70)
34
+ ser = abrir_serial()
35
+ try:
36
+ while True:
37
+ linea = ser.readline().decode("utf-8", errors="ignore").strip()
38
+ if not linea:
39
+ sys.stdout.write("* ")
40
+ sys.stdout.flush()
41
+ continue
42
+ try:
43
+ d = json.loads(linea)
44
+ except json.JSONDecodeError:
45
+ continue
46
+
47
+ # Boot message del ESP
48
+ if "boot" in d:
49
+ print(f"\n[HW] ESP8266 conectado: {d}")
50
+ continue
51
+
52
+ # Evento de deteccion real
53
+ if d.get("type") == "mosquito_pulse":
54
+ d["origin"] = NODE_ID
55
+ d["version"] = VERSION
56
+ # Firmar antes de emitir al bus
57
+ paquete = firmar(d)
58
+ print(f"\n[ALERTA] Sensor PIR activado (I={d.get('intensity', 0):.2f})")
59
+ print(f"[XOXO-BUS] FEROMONA_EMITIDA ==> {paquete}")
60
+ print("[BUS] Nodos trampa alertados.")
61
+ print("-" * 70)
62
+ # Cooldown para no saturar
63
+ time.sleep(4)
64
+
65
+ except KeyboardInterrupt:
66
+ print("\n[STOP] Centinela hardware detenido.")
67
+ finally:
68
+ ser.close()
69
+ print("[HW] Puerto serial cerrado.")
70
+
71
+ if __name__ == "__main__":
72
+ ejecutar()
@@ -0,0 +1,95 @@
1
+ import os
2
+ import time, json, hmac, hashlib
3
+
4
+ VERSION = "0.3.0"
5
+ NODE_ID = "trampa_escuela_01"
6
+ TRIGGER_TYPE = "mosquito_pulse"
7
+ CICLOS_GRACIA = 4
8
+ SECRET = os.environ.get("LAB_ESCUELA_SECRET", "hormigasais-colonia-escuela-demo").encode()
9
+ LOG_FILE = "capturas.jsonl"
10
+
11
+ class HormigaTrampa:
12
+ def __init__(self):
13
+ self.activo = False
14
+ self.temperatura = 24.0
15
+ self.ciclos_sin_feromona = 0
16
+ self.total_capturas = 0
17
+ self._ultimo_ts = 0.0 # anti-duplicado
18
+
19
+ # ── Verificación HMAC ──────────────────────────────────────────────
20
+ def _verificar(self, d):
21
+ sig_rx = d.pop("sig", "")
22
+ payload = json.dumps(d, sort_keys=True).encode()
23
+ sig_ok = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()[:16]
24
+ d["sig"] = sig_rx # restaurar para no mutar el dict
25
+ return hmac.compare_digest(sig_rx, sig_ok)
26
+
27
+ # ── Registro soberano ──────────────────────────────────────────────
28
+ def _registrar(self, intensidad):
29
+ entrada = {
30
+ "ts": round(time.time(), 3),
31
+ "node": NODE_ID,
32
+ "intensidad": intensidad,
33
+ "succion": "MAX" if intensidad > 0.85 else "MOD",
34
+ }
35
+ with open(LOG_FILE, "a") as f:
36
+ f.write(json.dumps(entrada) + "\n")
37
+
38
+ # ── Bus principal ──────────────────────────────────────────────────
39
+ def escuchar_bus(self, paquete_crudo):
40
+ try:
41
+ d = json.loads(paquete_crudo)
42
+ except Exception:
43
+ return
44
+
45
+ if d.get("type") == TRIGGER_TYPE and d.get("status") == "active":
46
+ # Anti-duplicado: ignorar si mismo timestamp ya procesado
47
+ ts = d.get("timestamp", 0)
48
+ if ts == self._ultimo_ts:
49
+ return
50
+ self._ultimo_ts = ts
51
+
52
+ if not self._verificar(dict(d)):
53
+ print(f"[SEGURIDAD] Firma invalida — paquete descartado.")
54
+ return
55
+
56
+ self.ciclos_sin_feromona = 0
57
+ self.reaccionar(d.get("intensity", 0.5))
58
+ else:
59
+ self.evaluar_disipacion()
60
+
61
+ # ── Reacción ───────────────────────────────────────────────────────
62
+ def reaccionar(self, intensidad):
63
+ self.activo = True
64
+ self.temperatura = 36.5
65
+ self.total_capturas += 1
66
+ succion = "100% (MAXIMA)" if intensidad > 0.85 else "60% (MODERADA)"
67
+
68
+ print(f"\n[{NODE_ID}] Feromona detectada (I={intensidad} | Captura #{self.total_capturas})")
69
+ print(f"[ACTUADOR] Resistencia IR -> {self.temperatura}C")
70
+ print(f"[MECANICO] Extractor al {succion}")
71
+ print("-" * 65)
72
+ self._registrar(intensidad)
73
+
74
+ # ── Disipación ─────────────────────────────────────────────────────
75
+ def evaluar_disipacion(self):
76
+ if not self.activo:
77
+ return
78
+ self.ciclos_sin_feromona += 1
79
+ restantes = CICLOS_GRACIA - self.ciclos_sin_feromona
80
+ if restantes > 0:
81
+ print(f"\n[{NODE_ID}] Silencio. Persistencia biologica ({restantes} ciclos)...")
82
+ print(f"[ACTUADOR] Manteniendo gradiente a {self.temperatura}C")
83
+ print("-" * 65)
84
+ else:
85
+ print(f"\n[{NODE_ID}] Feromona evaporada.")
86
+ self.apagado_seguro()
87
+
88
+ # ── Apagado ────────────────────────────────────────────────────────
89
+ def apagado_seguro(self):
90
+ if self.activo:
91
+ self.temperatura = 24.0
92
+ self.activo = False
93
+ print("[BAJO CONSUMO] Extractor OFF. IR -> 24C. Modo letargo.")
94
+ print(f"[STATS] Total capturas esta sesion: {self.total_capturas}")
95
+ print("-" * 65)
@@ -0,0 +1,58 @@
1
+ # EXTENSION para hormiga_trampa.py — agregar metodo activar_gpio()
2
+ # Conectar segundo ESP8266 (o mismo via segundo canal) que controle:
3
+ # D1 -> Relay Resistencia IR
4
+ # D2 -> Relay Ventilador
5
+ # D3 -> LED UV (ya activo en firmware, este lo modula)
6
+
7
+ import serial, json, time
8
+
9
+ PUERTO_TRAMPA = "/dev/ttyUSB1" # Si hay segundo ESP; si es uno solo, mismo puerto
10
+ BAUD_TRAMPA = 115200
11
+
12
+ class GPIOBridge:
13
+ def __init__(self):
14
+ self.ser = None
15
+ self._conectar()
16
+
17
+ def _conectar(self):
18
+ try:
19
+ self.ser = serial.Serial(PUERTO_TRAMPA, BAUD_TRAMPA, timeout=1)
20
+ print(f"[HW-TRAMPA] GPIO Bridge activo en {PUERTO_TRAMPA}")
21
+ except Exception as e:
22
+ print(f"[HW-TRAMPA] Sin GPIO Bridge: {e} — modo simulacion activo")
23
+ self.ser = None
24
+
25
+ def _enviar(self, cmd):
26
+ if self.ser and self.ser.is_open:
27
+ try:
28
+ self.ser.write((json.dumps(cmd) + "\n").encode())
29
+ except Exception as e:
30
+ print(f"[HW-TRAMPA] Error GPIO: {e}")
31
+
32
+ def activar_trampa(self, intensidad):
33
+ potencia = 100 if intensidad > 0.85 else 60
34
+ self._enviar({
35
+ "cmd": "trampa_on",
36
+ "ir_on": True,
37
+ "fan_pct": potencia,
38
+ "uv_on": True
39
+ })
40
+
41
+ def apagar_trampa(self):
42
+ self._enviar({
43
+ "cmd": "trampa_off",
44
+ "ir_on": False,
45
+ "fan_pct": 0,
46
+ "uv_on": True # UV siempre activo para atraccion pasiva
47
+ })
48
+
49
+ def cerrar(self):
50
+ if self.ser:
51
+ self.apagar_trampa()
52
+ time.sleep(0.5)
53
+ self.ser.close()
54
+
55
+ # Instrucciones de integracion:
56
+ # 1. En HormigaTrampa.__init__() agregar: self.gpio = GPIOBridge()
57
+ # 2. En reaccionar() agregar: self.gpio.activar_trampa(intensidad)
58
+ # 3. En apagado_seguro() agregar: self.gpio.apagar_trampa()
package/bootstrap.sh ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # HormigasAIS Edge Starter — bootstrap.sh
3
+ # Prepara el entorno antes de ejecutar generator.py
4
+
5
+ set -Eeuo pipefail
6
+
7
+ echo "🐜 HormigasAIS Edge Starter — Bootstrap"
8
+ echo "========================================"
9
+
10
+ # 1. Validar Python3
11
+ if ! command -v python3 >/dev/null 2>&1; then
12
+ echo "❌ python3 no encontrado. Instálalo antes de continuar."
13
+ exit 1
14
+ fi
15
+ echo "✅ python3: $(python3 --version)"
16
+
17
+ # 2. Validar Node (opcional, solo si se usará generator.js)
18
+ if command -v node >/dev/null 2>&1; then
19
+ echo "✅ node: $(node --version)"
20
+ else
21
+ echo "⚠️ node no encontrado (opcional, solo necesario para generator.js)"
22
+ fi
23
+
24
+ # 3. Crear directorios base si no existen
25
+ mkdir -p logs generators .hormigasais
26
+ echo "✅ Directorios base verificados: logs/, generators/, .hormigasais/"
27
+
28
+ # 4. Instalar dependencias npm solo si existe package.json y node/npm disponibles
29
+ if [ -f package.json ] && command -v npm >/dev/null 2>&1; then
30
+ echo "📦 Instalando dependencias npm..."
31
+ npm install --omit=dev
32
+ else
33
+ echo "⚠️ Saltando npm install (sin package.json o sin npm)"
34
+ fi
35
+
36
+ echo "========================================"
37
+ echo "✅ Bootstrap completado. Siguiente paso:"
38
+ echo " python3 generator.py"
package/generator.js CHANGED
@@ -1,42 +1,36 @@
1
1
  #!/usr/bin/env node
2
- const fs = require('fs');
3
- const path = require('path');
4
- const { execSync } = require('child_process');
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
5
4
 
6
5
  const targetName = process.argv[2] || 'mi-colonia';
7
6
  const targetDir = path.join(process.cwd(), targetName);
8
7
 
9
- console.log(`🐜 [HormigasAIS] Desplegando nodo soberano en edge: ${targetName}...`);
8
+ console.log(`🐜 [HormigasAIS] Creando nodo: ${targetName}...`);
10
9
 
11
- if (!fs.existsSync(targetDir)) {
12
- fs.mkdirSync(targetDir, { recursive: true });
10
+ for (const dir of ['agents', 'protocols', 'scripts', '.hormigasais']) {
11
+ fs.mkdirSync(path.join(targetDir, dir), { recursive: true });
13
12
  }
14
13
 
15
- // Estructura de directorios
16
- const subdirs = ['agents', 'protocols', 'scripts', '.hormigasais'];
17
- subdirs.forEach(dir => {
18
- const p = path.join(targetDir, dir);
19
- if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true });
20
- });
21
-
22
- // Copiar o generar archivos base en el directorio destino
23
- const fecha = new Date().toISOString();
24
-
25
- const config = {
26
- "node_id": "NODE-" + Math.random().toString(36).substring(2, 8).toUpperCase(),
27
- "node_name": targetName,
28
- "protocol": "LBH_BINARY_V1",
29
- "version": "2.0",
30
- "owner": "Cristhiam Leonardo Hernández Quiñonez",
31
- "location": "San Miguel, El Salvador",
32
- "nodo_maestro": "A16-SanMiguel-SV",
33
- "created": fecha,
34
- "agents": ["hormiga_centinela", "hormiga_relevo"],
35
- "cert": "SELLADO-SOBERANO-LBH"
36
- };
37
-
38
- fs.writeFileSync(path.join(targetDir, '.hormigasais', 'node_config.json'), JSON.stringify(config, null, 2));
14
+ const configFile = path.join(targetDir, '.hormigasais', 'node_config.json');
15
+
16
+ if (fs.existsSync(configFile)) {
17
+ console.log(' node_config.json ya existe, no se sobrescribe.');
18
+ } else {
19
+ const config = {
20
+ schema: 'LBH_NODE_CONFIG_V1',
21
+ node_id: 'NODE-' + Math.random().toString(36).substring(2, 8).toUpperCase(),
22
+ node_name: targetName,
23
+ protocol: 'LBH_BINARY_V2',
24
+ version: '2.0.0',
25
+ owner: { name: 'YOUR-NAME', email: 'YOUR-EMAIL' },
26
+ location: { country: 'YOUR-COUNTRY', city: 'YOUR-CITY' },
27
+ created_at: new Date().toISOString(),
28
+ status: 'INITIALIZED',
29
+ agents: ['hormiga_centinela', 'hormiga_relevo'],
30
+ certificate: 'PENDING'
31
+ };
32
+ fs.writeFileSync(configFile, JSON.stringify(config, null, 2) + '\n');
33
+ }
39
34
 
40
- console.log(`✅ Nodo configurado en ${targetDir}`);
41
- console.log(`
42
- Siguiente paso:\n cd ${targetName}\n ./scripts/ignicion.sh`);
35
+ console.log(`✅ Nodo creado en ${targetDir}`);
36
+ console.log(`Siguiente paso: cd ${targetName} y completa .hormigasais/node_config.json`);
package/generator.py CHANGED
@@ -22,7 +22,6 @@ sys.path.insert(0, str(ROOT))
22
22
 
23
23
  MODULES = [
24
24
  "config_generator",
25
- "readme_generator",
26
25
  "protocol_generator",
27
26
  "contracts_generator",
28
27
  "agents_generator",
@@ -46,6 +45,9 @@ def main():
46
45
  print("🐜 HormigasAIS Generator")
47
46
  print("============================================")
48
47
 
48
+ fallos = []
49
+ faltantes = []
50
+
49
51
  for module_name in MODULES:
50
52
 
51
53
  print(f"[LOAD] {module_name}")
@@ -68,15 +70,27 @@ def main():
68
70
 
69
71
  except ModuleNotFoundError:
70
72
 
71
- print(f"[WAIT] {module_name} aún no existe")
73
+ print(f"[FALTA] {module_name} no existe — módulo obligatorio")
74
+ faltantes.append(module_name)
72
75
 
73
76
  except Exception as e:
74
77
 
75
78
  print(f"[FAIL] {module_name}")
76
79
  print(e)
80
+ fallos.append(module_name)
77
81
 
78
82
  print("")
79
- print("Proceso terminado.")
83
+
84
+ if faltantes or fallos:
85
+ print("❌ Proceso terminado CON ERRORES.")
86
+ if faltantes:
87
+ print(f" Módulos faltantes: {", ".join(faltantes)}")
88
+ if fallos:
89
+ print(f" Módulos con fallo: {", ".join(fallos)}")
90
+ print("============================================")
91
+ sys.exit(1)
92
+
93
+ print("✅ Proceso terminado correctamente.")
80
94
  print("============================================")
81
95
 
82
96
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hormigasais-edge-starter",
3
- "version": "0.1.2",
4
- "description": "Starter template para desplegar un nodo soberano HormigasAIS con agentes LBH. Incluye SDK de protocolos LBH, generadores autom\u00e1ticos y agentes edge.",
3
+ "version": "0.1.3",
4
+ "description": "Starter template para desplegar un nodo soberano HormigasAIS con agentes LBH. Incluye SDK de protocolos LBH, generadores automáticos y agentes edge.",
5
5
  "main": "./generator.js",
6
6
  "bin": {
7
7
  "create-hormigas-node": "generator.js"
@@ -19,8 +19,8 @@
19
19
  "termux",
20
20
  "offline-first"
21
21
  ],
22
- "author": "Cristhiam Leonardo Hern\u00e1ndez Qui\u00f1onez",
23
- "license": "MIT",
22
+ "author": "Cristhiam Leonardo Hernández Quiñonez",
23
+ "license": "SEE LICENSE IN LICENSE",
24
24
  "bugs": {
25
25
  "url": "https://github.com/Thrumanshow/hormigasais-edge-starter/issues"
26
26
  },
@@ -29,12 +29,8 @@
29
29
  "start": "python3 generator.py",
30
30
  "bootstrap": "bash bootstrap.sh",
31
31
  "verify": "python3 scripts/verify_sdk.py",
32
- "postinstall": "python3 generator.py --quick || echo 'HormigasAIS Edge Starter instalado'",
33
32
  "test": "python3 scripts/verify_sdk.py"
34
33
  },
35
- "dependencies": {
36
- "jspdf": "^2.5.1"
37
- },
38
34
  "engines": {
39
35
  "node": ">=14.0.0"
40
36
  },
@@ -48,9 +44,8 @@
48
44
  "scripts",
49
45
  "utils",
50
46
  "generators",
51
- ".hormigasais",
52
47
  "README.md",
53
48
  "package.json"
54
49
  ],
55
50
  "packageManager": "npm@11.15.0"
56
- }
51
+ }
@@ -1,27 +0,0 @@
1
- {
2
- "schema": "LBH_NODE_CONFIG_V1",
3
- "node_id": "A16-u0_a350",
4
- "node_name": "mi-colonia",
5
- "protocol": "LBH_BINARY_V2",
6
- "version": "2.0.0",
7
- "owner": {
8
- "name": "YOUR-NAME",
9
- "email": "YOUR-EMAIL"
10
- },
11
- "location": {
12
- "country": "SV",
13
- "city": "San Miguel"
14
- },
15
- "master_node": "A16-SanMiguel-SV",
16
- "created_at": "2026-07-24T02:27:25Z",
17
- "status": "INITIALIZED",
18
- "agents": [
19
- "hormiga_centinela",
20
- "hormiga_relevo"
21
- ],
22
- "security": {
23
- "hmac_required": true,
24
- "algorithm": "HMAC-SHA256"
25
- },
26
- "certificate": "PENDING"
27
- }