Files
dtic-DIIAA/adn/tools/run
T

206 lines
8.0 KiB
Ruby
Executable File

#!/usr/bin/env ruby
# frozen_string_literal: true
ENV['TZ'] = 'America/Argentina/Buenos_Aires'
# tools/adn.rb — Interfaz de línea de comandos unificada para el sistema ADN
# ============================================================================
# 🤖 DIRECTIVA IA: Esta herramienta es el punto de entrada principal para la
# automatización del ADN. Su objetivo es reducir progresivamente la dependencia
# de la IA mediante scripts Ruby autónomos.
# ============================================================================
# Uso: ./adn/tools/run <subcomando> [opciones] [argumentos]
#
# Subcomandos disponibles:
# validador [--watch] [--fix] Validar cumplimiento del ADN en bitácoras
# contexto <nodo> [--cmd] Obtener contexto topológico de un nodo
# nodos <acción> [opciones] Gestión de nodos (agrupar, listar, buscar)
# jornada <acción> [modo] [hora] Gestión de jornada (iniciar, cerrar, estado)
# salud [--dashboard] [--json] Ver salud general del sistema ADN
# generar bitacora [fecha] Generar nueva bitácora (fecha: YYYY-MM-DD)
# generar nodo <nombre> Generar ficha de nodo
# generar proyecto <código> Generar manifiesto de proyecto
# tailscale <acción> [opciones] Gestión de identidades Tailscale
# conocimiento asimilar <archivo> Asimilar documentación al ADN
# evolucion <propuesta.md> Gestionar propuesta de evolución del ADN
# msp <subcomando> Gestión de MSPs (auditar, registrar, listar)
# proy <subcomando> Generación y gestión de proyectos
# candados <acción> [opciones] Gestión de secretos y autorización SSH
# ayuda [subcomando] Mostrar ayuda detallada
#
# Documentación completa: docs/proy/p2604_mejoras_ADN/P2604_mejoras_ADN.md
require 'optparse'
require 'json'
require 'fileutils'
require 'date'
require 'time'
require_relative 'core/constants'
module ADN
# Aseguramos que los directorios existan
FileUtils.mkdir_p(LOGS_DIR) unless Dir.exist?(LOGS_DIR)
FileUtils.mkdir_p(BITACORAS_DIR) unless Dir.exist?(BITACORAS_DIR)
end
require_relative 'core/logger'
require_relative 'cli/triggers/eventos'
require_relative 'cli/triggers/motor'
require_relative 'cli/db'
require_relative 'cli/plan'
require_relative 'cli/backup'
require_relative 'cli/triggers'
require_relative 'cli/conocimiento'
require_relative 'cli/jornada'
require_relative 'cli/proceso'
require_relative 'cli/estados'
require_relative 'cli/tailscale'
require_relative 'cli/ssh'
require_relative 'cli/candados'
require_relative 'core/conciliador'
require_relative 'core/colores'
# ─── Logger estructurado ─────────────────────────────────────────────────
# El logger está implementado en adn/logger.rb
ADNLogger = ADN::Logger
require_relative 'cli/validador'
require_relative 'cli/contexto'
require_relative 'cli/generar'
require_relative 'cli/salud'
require_relative 'cli/ayuda'
require_relative 'cli/nodos'
require_relative 'cli/msp'
require_relative 'cli/wzombi'
require_relative 'proy/proy'
# ─── CLI Principal ──────────────────────────────────────────────────────
class ADNCLI
def initialize
@logger = ADNLogger.new
end
def ejecutar
if ARGV.empty?
SubcomandoAyuda.new([], @logger).ejecutar
exit 0
end
subcomando = ARGV[0]
# Saneamiento preventivo de pendientes (con cooldown de 5 minutos para performance)
ejecutar_saneamiento_preventivo(subcomando)
args = ARGV[1..-1] || []
case subcomando
when 'validador'
SubcomandoValidador.new(args, @logger).ejecutar
when 'contexto'
SubcomandoContexto.new(args, @logger).ejecutar
when 'salud'
ADN::SubcomandoSalud.new(args, @logger).ejecutar
when 'generar'
SubcomandoGenerar.new(args, @logger).ejecutar
when 'nodos'
ADN::SubcomandoNodos.new(args, @logger).ejecutar
when 'conocimiento'
ADN::SubcomandoConocimiento.new(args, @logger).ejecutar
when 'db'
ADN::SubcomandoDB.new(args, @logger).ejecutar
when 'backup', 'backups'
ADN::SubcomandoBackup.new(args, @logger).ejecutar
when 'triggers'
ADN::SubcomandoTriggers.new(args, @logger).ejecutar
when 'jornada'
ADN::SubcomandoJornada.new(args, @logger).ejecutar
when 'proceso'
ADN::SubcomandoProceso.new(args, @logger).ejecutar
when 'estados'
ADN::SubcomandoEstados.new(args, @logger).ejecutar
when 'network'
ADN::SubcomandoNetwork.new(args, @logger).ejecutar
when 'tailscale', 'ts'
ADN::SubcomandoTailscale.new(args, @logger).ejecutar
when 'inicio'
# Compatibilidad: redirigir a jornada iniciar
ADN::SubcomandoJornada.new(['iniciar'] + args, @logger).ejecutar
when 'cierre'
# Compatibilidad: redirigir a jornada cerrar
ADN::SubcomandoJornada.new(['cerrar'] + args, @logger).ejecutar
when 'ayuda', 'help', '-h', '--help'
ADN::SubcomandoAyuda.new(args, @logger).ejecutar
when 'evolucion'
@logger.info("Subcomando 'evolucion' será implementado en Fase 2")
puts "#{Color::YELLOW}⚠ El subcomando 'evolucion' será implementado en Fase 2#{Color::RESET}"
puts " Ver plan: docs/plan/260307-1400_Plan_Mejoras_ADN.md"
when 'plan'
ADN::SubcomandoPlan.new(args, @logger).ejecutar
when 'msp'
SubcomandoMsp.new(args, @logger).ejecutar
when 'proy'
ADN::SubcomandoProy.new(args, @logger).ejecutar
when 'ssh'
ADN::SubcomandoSSH.new(args, @logger).ejecutar
when 'candados', 'secrets'
ADN::SubcomandoCandados.new(args, @logger).ejecutar
when 'wzombi', 'w-zombi'
ADN::SubcomandoWZombi.new(args, @logger).ejecutar
else
@logger.error("Subcomando desconocido: #{subcomando}")
puts "#{Color::RED}✗ Subcomando desconocido: '#{subcomando}'#{Color::RESET}"
puts ""
SubcomandoAyuda.new([], @logger).ejecutar
exit 1
end
rescue Interrupt
puts "\n#{Color::YELLOW}⚠ Interrumpido por el usuario#{Color::RESET}"
@logger.info("Ejecución interrumpida por usuario")
exit 130
rescue OptionParser::InvalidOption => e
@logger.error("Opción inválida: #{e.message}")
puts "#{Color::RED}✗ Error: opción inválida #{e.message}#{Color::RESET}"
puts "Usa './adn/tools/run ayuda' para ver las opciones disponibles"
exit 1
rescue ADN::Error => e
@logger.error("Error de aplicación: #{e.message}")
puts "#{Color::RED}#{e.message}#{Color::RESET}"
exit 1
rescue => e
@logger.error("Error inesperado: #{e.message}", { backtrace: e.backtrace })
puts "#{Color::RED}✗ Error inesperado: #{e.message}#{Color::RESET}"
puts "#{Color::DIM}#{e.backtrace.first(3).join("\n")}#{Color::RESET}" if ENV['ADN_DEBUG']
puts "\n#{Color::YELLOW}💡 Ejecuta con ADN_DEBUG=1 para ver el trace completo#{Color::RESET}"
exit 1
end
private
def ejecutar_saneamiento_preventivo(subcomando)
# No sanear si es ayuda o db (para evitar bucles o lentitud en info rápida)
return if ['ayuda', 'help', 'db', 'salud', '-h', '--help'].include?(subcomando)
archivo_cooldown = "/tmp/adn_sanity_cooldown"
cooldown_segundo = 300 # 5 minutos
if File.exist?(archivo_cooldown)
last_run = File.mtime(archivo_cooldown)
return if (Time.now - last_run) < cooldown_segundo
end
# Ejecutar conciliación
begin
# Tocamos el archivo primero para evitar que otras instancias concurrentes lo lancen
FileUtils.touch(archivo_cooldown)
ADN::Conciliador.new(@logger).ejecutar(silencioso: true)
rescue => e
@logger.advertencia("No se pudo completar el saneamiento preventivo: #{e.message}")
end
end
end
# ─── Punto de entrada ───────────────────────────────────────────────────
if __FILE__ == $0
ADNCLI.new.ejecutar
end