feat: import agents_and_robots platform as unibots (Matrix-out, unibus transport)
Reemplaza el scaffold del echobot por la plataforma completa de bots traida desde ~/DataProyects/Github/agents_and_robots tras la operacion Matrix-out: los bots ya no hablan por Matrix sino por el bus unibus (modelo todo-rooms + E2E via shell/transportunibus sobre github.com/enmanuel/unibus/pkg/client). - go.mod: replace de unibus -> ../unibus y de fn-registry -> ../../../.. (paths relativos reajustados a la nueva ubicacion dentro de fn_registry). - app.md: bump a 0.2.0, descripcion + arquitectura + comandos + gotchas reales. - modulo Go conservado como github.com/enmanuel/agents (sin reescribir imports). agents_and_robots queda archivado como museo de la era Matrix.
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
# dev-scripts
|
||||
|
||||
Scripts bash para operaciones del día a día con los bots Matrix.
|
||||
|
||||
Todos los scripts comparten funciones comunes definidas en `_common.sh` (colores, helpers de proceso, descubrimiento de agentes, carga de `.env`).
|
||||
|
||||
## Estructura
|
||||
|
||||
```
|
||||
dev-scripts/
|
||||
├── _common.sh funciones compartidas (sourced por todos los scripts)
|
||||
├── server/ gestión del launcher (ciclo de vida del servidor)
|
||||
└── agent/ gestión de agentes individuales (setup, registro, E2EE)
|
||||
```
|
||||
|
||||
## server/
|
||||
|
||||
Scripts para controlar el launcher unificado que ejecuta todos los agentes.
|
||||
|
||||
| Script | Descripción |
|
||||
|--------|-------------|
|
||||
| `start.sh` | Inicia el launcher (compila si es necesario) |
|
||||
| `stop.sh` | Detiene el launcher (SIGTERM, espera 5s, SIGKILL) |
|
||||
| `restart.sh` | Reinicia el launcher (stop + start) |
|
||||
| `ps.sh` | Muestra el proceso del launcher con detalle (PID, mem, CPU, uptime) |
|
||||
| `logs.sh [lines]` | Tail -f de los logs del launcher |
|
||||
| `dashboard.sh` | Abre la TUI interactiva de gestión |
|
||||
| `server.sh <cmd>` | CLI unificado que enruta a los scripts anteriores |
|
||||
|
||||
## agent/
|
||||
|
||||
Scripts para crear, registrar, verificar y gestionar agentes individuales.
|
||||
|
||||
| Script | Descripción |
|
||||
|--------|-------------|
|
||||
| `new-agent.sh <id> [name]` | Genera scaffold completo (config, agent.go, prompts) |
|
||||
| `register.sh <id> [name]` | Registra bot en Matrix via Synapse admin API |
|
||||
| `verify.sh [id]` | Verifica/regenera dispositivos E2EE (cross-signing) |
|
||||
| `avatar.sh <id> <img>` | Sube avatar y sincroniza displayname |
|
||||
| `reset-password.sh <id>` | Resetea password sin invalidar el token |
|
||||
| `remove.sh <id>` | Deshabilita un agente (enabled: false, no borra datos) |
|
||||
| `list.sh` | Muestra todos los agentes y su estado |
|
||||
Executable
+178
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env bash
|
||||
# _common.sh — sourced by all dev-scripts. Do not run directly.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Colores ────────────────────────────────────────────────────────────────
|
||||
RED='\033[0;31m'
|
||||
GRN='\033[0;32m'
|
||||
YLW='\033[0;33m'
|
||||
BLU='\033[0;34m'
|
||||
DIM='\033[2m'
|
||||
RST='\033[0m'
|
||||
|
||||
ok() { echo -e "${GRN}✓${RST} $*"; }
|
||||
info() { echo -e "${BLU}→${RST} $*"; }
|
||||
warn() { echo -e "${YLW}!${RST} $*"; }
|
||||
fail() { echo -e "${RED}✗${RST} $*" >&2; exit 1; }
|
||||
dim() { echo -e "${DIM}$*${RST}"; }
|
||||
|
||||
# ── Repo root ──────────────────────────────────────────────────────────────
|
||||
# Scripts can be called from any directory; we always operate from repo root.
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
# ── .env loader ───────────────────────────────────────────────────────────
|
||||
load_env() {
|
||||
local env_file="${1:-.env}"
|
||||
[[ -f "$env_file" ]] || fail ".env not found at $REPO_ROOT/$env_file — copy .env.example and fill it in"
|
||||
# Export only lines that are KEY=VALUE (skip comments and blanks)
|
||||
set -o allexport
|
||||
# shellcheck disable=SC1090
|
||||
source <(grep -E '^[A-Z_][A-Z0-9_]*=.+' "$env_file")
|
||||
set +o allexport
|
||||
}
|
||||
|
||||
# ── Go tooling ─────────────────────────────────────────────────────────────
|
||||
GO=${GO_BIN:-go}
|
||||
export PATH="$PATH:/usr/local/go/bin"
|
||||
command -v "$GO" &>/dev/null || fail "go not found — install Go or set GO_BIN"
|
||||
|
||||
# ── Process helpers ────────────────────────────────────────────────────────
|
||||
RUN_DIR="$REPO_ROOT/run"
|
||||
mkdir -p "$RUN_DIR"
|
||||
|
||||
pid_file() { echo "$RUN_DIR/$1.pid"; }
|
||||
log_file() { echo "$RUN_DIR/$1.log"; }
|
||||
|
||||
read_pid() {
|
||||
local f; f="$(pid_file "$1")"
|
||||
[[ -f "$f" ]] && cat "$f" || echo 0
|
||||
}
|
||||
|
||||
# Map agent ID to its config path by scanning agent directories.
|
||||
config_path_for() {
|
||||
local target_id="$1"
|
||||
for cfg in agents/*/config.yaml; do
|
||||
[[ -f "$cfg" ]] || continue
|
||||
local id
|
||||
id=$(grep -m1 '^ id:' "$cfg" | awk '{print $2}')
|
||||
if [[ "$id" == "$target_id" ]]; then
|
||||
echo "$cfg"
|
||||
return
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Find all PIDs of launcher processes for a given agent ID.
|
||||
# Searches for the actual config path in the process command line.
|
||||
# Returns newline-separated PIDs (may be empty).
|
||||
find_agent_pids() {
|
||||
local id="$1"
|
||||
local cfg; cfg="$(config_path_for "$id")"
|
||||
if [[ -z "$cfg" ]]; then
|
||||
return
|
||||
fi
|
||||
pgrep -f "launcher.*-c.*${cfg}" 2>/dev/null || true
|
||||
}
|
||||
|
||||
is_running() {
|
||||
local id="$1"
|
||||
|
||||
# First check PID file
|
||||
local pid; pid="$(read_pid "$id")"
|
||||
if [[ "$pid" -gt 0 ]] && kill -0 "$pid" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# PID file is stale or missing — search for actual processes
|
||||
local pids; pids="$(find_agent_pids "$id")"
|
||||
if [[ -n "$pids" ]]; then
|
||||
# Update PID file with the first found process
|
||||
local first_pid; first_pid="$(echo "$pids" | head -1)"
|
||||
echo "$first_pid" > "$(pid_file "$id")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Truly not running — clean up stale PID file
|
||||
[[ "$pid" -gt 0 ]] && rm -f "$(pid_file "$id")"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Count how many instances of an agent are running.
|
||||
count_instances() {
|
||||
local id="$1"
|
||||
local pids; pids="$(find_agent_pids "$id")"
|
||||
if [[ -z "$pids" ]]; then
|
||||
echo 0
|
||||
else
|
||||
echo "$pids" | wc -l
|
||||
fi
|
||||
}
|
||||
|
||||
agent_status() {
|
||||
local id="$1" enabled="$2"
|
||||
if [[ "$enabled" != "true" ]]; then
|
||||
echo "disabled"
|
||||
elif is_launcher_running; then
|
||||
echo "running"
|
||||
else
|
||||
echo "stopped"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Unified launcher helpers ───────────────────────────────────────────────
|
||||
LAUNCHER_ID="launcher"
|
||||
|
||||
launcher_pid_file() { echo "$RUN_DIR/$LAUNCHER_ID.pid"; }
|
||||
launcher_log_file() { echo "$RUN_DIR/$LAUNCHER_ID.log"; }
|
||||
|
||||
read_launcher_pid() {
|
||||
local f; f="$(launcher_pid_file)"
|
||||
[[ -f "$f" ]] && cat "$f" || echo 0
|
||||
}
|
||||
|
||||
is_launcher_running() {
|
||||
local pid; pid="$(read_launcher_pid)"
|
||||
if [[ "$pid" -gt 0 ]] && kill -0 "$pid" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
# Fallback: search for launcher process without -c flag
|
||||
local pids; pids="$(pgrep -f 'launcher.*--log-level' 2>/dev/null || true)"
|
||||
if [[ -n "$pids" ]]; then
|
||||
local first_pid; first_pid="$(echo "$pids" | head -1)"
|
||||
echo "$first_pid" > "$(launcher_pid_file)"
|
||||
return 0
|
||||
fi
|
||||
[[ "$pid" -gt 0 ]] && rm -f "$(launcher_pid_file)"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── Agent discovery ────────────────────────────────────────────────────────
|
||||
# Prints: id|version|enabled|description (one line per agent)
|
||||
list_agents_raw() {
|
||||
for cfg in agents/*/config.yaml; do
|
||||
[[ -f "$cfg" ]] || continue
|
||||
local id version enabled desc
|
||||
id=$(grep -m1 '^ id:' "$cfg" | awk '{print $2}')
|
||||
version=$(grep -m1 '^ version:' "$cfg" | awk '{print $2}' | tr -d '"')
|
||||
enabled=$(grep -m1 '^ enabled:' "$cfg" | awk '{print $2}')
|
||||
desc=$(grep -m1 '^ description:' "$cfg" | cut -d'"' -f2)
|
||||
[[ -n "$id" ]] && echo "${id}|${version}|${enabled}|${desc}|${cfg}"
|
||||
done
|
||||
}
|
||||
|
||||
# ── Naming convention ─────────────────────────────────────────────────────
|
||||
# Normalizes an agent ID to an env-var suffix.
|
||||
# Convention: uppercase, hyphens → underscores. No stripping of suffixes.
|
||||
# "assistant-bot" → "ASSISTANT_BOT"
|
||||
# "asistente-2" → "ASISTENTE_2"
|
||||
# "devops-bot" → "DEVOPS_BOT"
|
||||
normalize_id() {
|
||||
echo "$1" | tr '[:lower:]-' '[:upper:]_'
|
||||
}
|
||||
|
||||
# ── Usage helper ──────────────────────────────────────────────────────────
|
||||
need_arg() {
|
||||
[[ -n "${1:-}" ]] || { echo "Usage: $0 <agent-id>"; exit 1; }
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
# dev-scripts/agent
|
||||
|
||||
Scripts para crear, registrar, verificar y gestionar agentes individuales en el sistema Matrix.
|
||||
|
||||
## Scripts
|
||||
|
||||
### new-agent.sh
|
||||
|
||||
Genera el scaffold completo para un nuevo agente: `config.yaml`, `agent.go` (reglas puras), directorio de prompts y data. También registra automáticamente el import y la entrada en `rulesRegistry` de `cmd/launcher/main.go`.
|
||||
|
||||
```bash
|
||||
./dev-scripts/agent/new-agent.sh <agent-id> "Display Name"
|
||||
./dev-scripts/agent/new-agent.sh monitor-bot "Monitor Agent"
|
||||
```
|
||||
|
||||
### register.sh
|
||||
|
||||
Registra un nuevo bot en el servidor Matrix via Synapse admin API. Genera y guarda en `.env`: access token (`MATRIX_TOKEN_*`), password (`MATRIX_PASSWORD_*`) y pickle key (`PICKLE_KEY_*`).
|
||||
|
||||
Requiere `MATRIX_ADMIN_TOKEN` y `MATRIX_HOMESERVER` en `.env`.
|
||||
|
||||
```bash
|
||||
./dev-scripts/agent/register.sh <agent-id> "Display Name"
|
||||
./dev-scripts/agent/register.sh assistant-bot "Assistant"
|
||||
```
|
||||
|
||||
### verify.sh
|
||||
|
||||
Verifica o regenera los dispositivos E2EE de los agentes. Genera cross-signing keys, firma el device y guarda el recovery key en `.env`. Sin este paso, los mensajes del bot aparecen como "not verified by its owner".
|
||||
|
||||
```bash
|
||||
./dev-scripts/agent/verify.sh # verifica todos los habilitados con E2EE
|
||||
./dev-scripts/agent/verify.sh assistant-bot # verifica uno específico
|
||||
```
|
||||
|
||||
### avatar.sh
|
||||
|
||||
Sube una imagen como avatar del bot en Matrix y sincroniza el displayname desde el `config.yaml`.
|
||||
|
||||
```bash
|
||||
./dev-scripts/agent/avatar.sh <agent-id> <image-path>
|
||||
./dev-scripts/agent/avatar.sh assistant-bot static/assistant.jpg
|
||||
```
|
||||
|
||||
### reset-password.sh
|
||||
|
||||
Resetea la contraseña de un bot existente via Synapse admin API sin crear nueva sesión ni cambiar el device ID. El access token actual sigue siendo válido.
|
||||
|
||||
```bash
|
||||
./dev-scripts/agent/reset-password.sh <agent-id>
|
||||
./dev-scripts/agent/reset-password.sh assistant-bot
|
||||
```
|
||||
|
||||
### remove.sh
|
||||
|
||||
Deshabilita un agente marcándolo como `enabled: false` en su `config.yaml`. No borra datos — los preserva en `agents/<id>/data/`.
|
||||
|
||||
```bash
|
||||
./dev-scripts/agent/remove.sh <agent-id>
|
||||
```
|
||||
|
||||
### list.sh
|
||||
|
||||
Muestra todos los agentes registrados con su estado (running/stopped/disabled), versión y descripción en una tabla formateada.
|
||||
|
||||
```bash
|
||||
./dev-scripts/agent/list.sh
|
||||
```
|
||||
|
||||
## Flujo típico para un nuevo agente
|
||||
|
||||
```bash
|
||||
# 1. Crear scaffold
|
||||
./dev-scripts/agent/new-agent.sh mi-bot "Mi Bot"
|
||||
|
||||
# 2. Editar config, reglas y prompt
|
||||
# agents/mi-bot/config.yaml
|
||||
# agents/mi-bot/agent.go
|
||||
# agents/mi-bot/prompts/system.md
|
||||
|
||||
# 3. Registrar en Matrix
|
||||
./dev-scripts/agent/register.sh mi-bot "Mi Bot"
|
||||
|
||||
# 4. Verificar E2EE
|
||||
./dev-scripts/agent/verify.sh mi-bot
|
||||
|
||||
# 5. Arrancar
|
||||
./dev-scripts/server/start.sh
|
||||
```
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# avatar.sh — sube una imagen y la establece como avatar de un bot.
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/agent/avatar.sh <agent-id> <image-path>
|
||||
#
|
||||
# Ejemplos:
|
||||
# ./dev-scripts/agent/avatar.sh assistant-bot assets/assistant.png
|
||||
# ./dev-scripts/agent/avatar.sh devops-bot assets/devops.jpg
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
load_env
|
||||
|
||||
AGENT_ID="${1:-}"
|
||||
IMAGE_PATH="${2:-}"
|
||||
|
||||
[[ -n "$AGENT_ID" ]] || fail "Uso: $0 <agent-id> <image-path>"
|
||||
[[ -n "$IMAGE_PATH" ]] || fail "Uso: $0 <agent-id> <image-path>"
|
||||
[[ -f "$IMAGE_PATH" ]] || fail "Imagen no encontrada: $IMAGE_PATH"
|
||||
|
||||
# Resuelve el binario de agentctl: compiled > go run
|
||||
if [[ -f "$REPO_ROOT/bin/agentctl" ]]; then
|
||||
CTL="$REPO_ROOT/bin/agentctl"
|
||||
else
|
||||
info "bin/agentctl no encontrado, usando go run ./cmd/agentctl"
|
||||
CTL="$GO run ./cmd/agentctl"
|
||||
fi
|
||||
|
||||
info "Subiendo avatar para $AGENT_ID desde $IMAGE_PATH..."
|
||||
$CTL avatar "$AGENT_ID" "$IMAGE_PATH"
|
||||
|
||||
ok "Avatar de $AGENT_ID actualizado."
|
||||
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
# create-full.sh — pipeline completo para crear un agente funcional
|
||||
#
|
||||
# Ejecuta en orden: scaffold → build → register → verify E2EE
|
||||
# NO arranca el agente — primero personalizar agent.go, config.yaml y prompts/system.md
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/agent/create-full.sh <agent-id> "Display Name"
|
||||
#
|
||||
# Ejemplo:
|
||||
# ./dev-scripts/agent/create-full.sh monitor-bot "Monitor Agent"
|
||||
#
|
||||
# Requisitos en .env:
|
||||
# MATRIX_ADMIN_TOKEN, MATRIX_HOMESERVER, MATRIX_SERVER_NAME
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
load_env
|
||||
|
||||
need_arg "${1:-}"
|
||||
|
||||
ID="$1"
|
||||
DISPLAYNAME="${2:-$ID}"
|
||||
NORM="$(normalize_id "$ID")"
|
||||
SCRIPT_DIR="$(dirname "$0")"
|
||||
|
||||
echo ""
|
||||
echo -e "${BLU}═══════════════════════════════════════════════════════${RST}"
|
||||
echo -e "${BLU} Creando agente: ${GRN}$ID${BLU} ($DISPLAYNAME)${RST}"
|
||||
echo -e "${BLU}═══════════════════════════════════════════════════════${RST}"
|
||||
echo ""
|
||||
|
||||
# ── Paso 1: Scaffold ─────────────────────────────────────────────────────
|
||||
info "Paso 1/4 — Scaffold (agent.go, config.yaml, prompts, launcher)"
|
||||
echo ""
|
||||
|
||||
"$SCRIPT_DIR/new-agent.sh" "$ID" "$DISPLAYNAME"
|
||||
|
||||
echo ""
|
||||
|
||||
# ── Paso 2: Verificar compilación ─────────────────────────────────────────
|
||||
info "Paso 2/4 — Verificando compilación..."
|
||||
|
||||
if "$GO" build -tags goolm ./... 2>&1; then
|
||||
ok "Compilación exitosa"
|
||||
else
|
||||
fail "Error de compilación — revisa agents/$ID/agent.go y cmd/launcher/main.go"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ── Paso 3: Registrar en Matrix ──────────────────────────────────────────
|
||||
info "Paso 3/4 — Registrando en Matrix..."
|
||||
echo ""
|
||||
|
||||
# Reload .env in case new-agent.sh or previous steps changed it
|
||||
load_env
|
||||
|
||||
"$SCRIPT_DIR/register.sh" "$ID" "$DISPLAYNAME"
|
||||
|
||||
echo ""
|
||||
|
||||
# ── Paso 4: Verificar E2EE ───────────────────────────────────────────────
|
||||
info "Paso 4/4 — Verificación E2EE (cross-signing + recovery key)..."
|
||||
echo ""
|
||||
|
||||
# Reload .env to pick up token, password, pickle key from register.sh
|
||||
load_env
|
||||
|
||||
"$SCRIPT_DIR/verify.sh" "$ID"
|
||||
|
||||
echo ""
|
||||
|
||||
# ── Resumen ──────────────────────────────────────────────────────────────
|
||||
echo -e "${GRN}═══════════════════════════════════════════════════════${RST}"
|
||||
echo -e "${GRN} ✓ Agente $ID creado exitosamente${RST}"
|
||||
echo -e "${GRN}═══════════════════════════════════════════════════════${RST}"
|
||||
echo ""
|
||||
echo -e " ${BLU}Archivos creados:${RST}"
|
||||
echo -e " agents/$ID/agent.go"
|
||||
echo -e " agents/$ID/config.yaml"
|
||||
echo -e " agents/$ID/prompts/system.md"
|
||||
echo ""
|
||||
echo -e " ${BLU}Variables en .env:${RST}"
|
||||
echo -e " MATRIX_TOKEN_${NORM}"
|
||||
echo -e " MATRIX_PASSWORD_${NORM}"
|
||||
echo -e " PICKLE_KEY_${NORM}"
|
||||
echo -e " SSSS_RECOVERY_KEY_${NORM}"
|
||||
echo ""
|
||||
echo -e " ${BLU}Launcher actualizado:${RST}"
|
||||
echo -e " cmd/launcher/main.go (import + rulesRegistry)"
|
||||
echo ""
|
||||
echo -e "${YLW}Siguiente paso:${RST}"
|
||||
echo ""
|
||||
echo -e " 1. Personalizar los archivos del agente:"
|
||||
echo -e " ${DIM}agents/$ID/agent.go${RST} — reglas de decisión"
|
||||
echo -e " ${DIM}agents/$ID/config.yaml${RST} — LLM, tools, personalidad"
|
||||
echo -e " ${DIM}agents/$ID/prompts/system.md${RST} — system prompt"
|
||||
echo ""
|
||||
echo -e " 2. Arrancar:"
|
||||
echo -e " ${DIM}./dev-scripts/server/start.sh${RST}"
|
||||
echo ""
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# list.sh — muestra todos los agentes y su estado actual
|
||||
# Uso: ./dev-scripts/agent/list.sh
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
|
||||
printf "%-22s %-12s %-8s %s\n" "ID" "STATUS" "VERSION" "DESCRIPTION"
|
||||
printf '%s\n' "$(printf '─%.0s' {1..70})"
|
||||
|
||||
while IFS='|' read -r id version enabled desc _cfg; do
|
||||
status=$(agent_status "$id" "$enabled")
|
||||
|
||||
case "$status" in
|
||||
running) label="${GRN}● running${RST}" ;;
|
||||
stopped) label="${DIM}○ stopped${RST}" ;;
|
||||
disabled) label="${YLW} disabled${RST}" ;;
|
||||
*) label="$status" ;;
|
||||
esac
|
||||
|
||||
# Truncate description
|
||||
[[ ${#desc} -gt 38 ]] && desc="${desc:0:37}…"
|
||||
|
||||
printf "%-22s " "$id"
|
||||
printf "${label}"
|
||||
printf " %-8s %s\n" "$version" "$desc"
|
||||
|
||||
done < <(list_agents_raw)
|
||||
Executable
+363
@@ -0,0 +1,363 @@
|
||||
#!/usr/bin/env bash
|
||||
# new-agent.sh — genera el scaffold de un nuevo agente
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/agent/new-agent.sh <agent-id> [displayname]
|
||||
#
|
||||
# Ejemplo:
|
||||
# ./dev-scripts/agent/new-agent.sh monitor-bot "Monitor Agent"
|
||||
#
|
||||
# Crea:
|
||||
# agents/<agent-id>/config.yaml (copiado desde agents/_template/)
|
||||
# agents/<agent-id>/agent.go (copiado desde agents/_template/)
|
||||
# agents/<agent-id>/prompts/ (copiado desde agents/_template/prompts/)
|
||||
# agents/<agent-id>/data/ (directorio de datos, en .gitignore)
|
||||
#
|
||||
# También te recuerda los dos pasos manuales que quedan.
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
load_env
|
||||
|
||||
need_arg "${1:-}"
|
||||
|
||||
ID="$1"
|
||||
DISPLAYNAME="${2:-$ID}"
|
||||
PACKAGE="$(echo "$ID" | tr '-' '_' | sed 's/_bot//')" # "monitor-bot" → "monitor"
|
||||
NORM="$(normalize_id "$ID")" # "monitor-bot" → "MONITOR_BOT"
|
||||
DIR="agents/$ID"
|
||||
TEMPLATE="agents/_template"
|
||||
|
||||
[[ -d "$DIR" ]] && fail "Ya existe agents/$ID — ¿ya fue creado?"
|
||||
[[ ! -d "$TEMPLATE" ]] && fail "No existe el directorio _template en agents/_template/"
|
||||
|
||||
info "Creando scaffold para $ID desde _template..."
|
||||
|
||||
mkdir -p "$DIR/prompts" "$DIR/data"
|
||||
|
||||
# ── Copiar config.yaml desde template y personalizar ─────────────────────
|
||||
cp "$TEMPLATE/config.yaml" "$DIR/config.yaml"
|
||||
sed -i "s/_template/$ID/g" "$DIR/config.yaml"
|
||||
sed -i "s/Template Agent/$DISPLAYNAME/g" "$DIR/config.yaml"
|
||||
sed -i "s/template: true/template: false/g" "$DIR/config.yaml"
|
||||
sed -i "s/enabled: true/enabled: true/g" "$DIR/config.yaml"
|
||||
sed -i "s/MATRIX_TOKEN_TEMPLATE/MATRIX_TOKEN_${NORM}/g" "$DIR/config.yaml"
|
||||
sed -i "s/PICKLE_KEY_TEMPLATE/PICKLE_KEY_${NORM}/g" "$DIR/config.yaml"
|
||||
sed -i "s/@template:matrix.example.com/@$ID:\${MATRIX_SERVER_NAME}/g" "$DIR/config.yaml"
|
||||
sed -i "s|https://matrix.example.com|\${MATRIX_HOMESERVER}|g" "$DIR/config.yaml"
|
||||
|
||||
ok "config.yaml creado desde template"
|
||||
|
||||
# DEPRECATED: generacion inline — ahora copiamos desde _template
|
||||
: <<'YAML'
|
||||
# ============================================
|
||||
# IDENTIDAD
|
||||
# ============================================
|
||||
agent:
|
||||
id: $ID
|
||||
name: "$DISPLAYNAME"
|
||||
version: "1.0.0"
|
||||
enabled: true
|
||||
description: "Descripción del agente $DISPLAYNAME"
|
||||
tags: [$(echo "$ID" | tr '-' ',')]
|
||||
|
||||
# ============================================
|
||||
# PERSONALIDAD Y COMPORTAMIENTO
|
||||
# ============================================
|
||||
personality:
|
||||
tone: friendly
|
||||
verbosity: concise
|
||||
language: es
|
||||
languages_supported: [es, en]
|
||||
emoji_style: minimal
|
||||
prefix: "🤖"
|
||||
error_style: helpful
|
||||
|
||||
templates:
|
||||
greeting: "Hola, soy $DISPLAYNAME. ¿En qué puedo ayudarte?"
|
||||
unknown_command: "No reconozco ese comando. Escríbeme directamente."
|
||||
permission_denied: "No tengo permiso para hacer eso."
|
||||
error: "Algo salió mal: {{.Error}}"
|
||||
success: "{{.Summary}}"
|
||||
busy: "Procesando, dame un momento..."
|
||||
|
||||
behavior:
|
||||
proactive: false
|
||||
ask_confirmation: false
|
||||
show_reasoning: false
|
||||
thread_replies: true
|
||||
typing_indicator: true
|
||||
acknowledge_receipt: false
|
||||
|
||||
# ============================================
|
||||
# LLM
|
||||
# ============================================
|
||||
llm:
|
||||
primary:
|
||||
provider: openai
|
||||
model: gpt-4o
|
||||
api_key_env: OPENAI_API_KEY
|
||||
base_url: ""
|
||||
max_tokens: 4096
|
||||
temperature: 0.7
|
||||
|
||||
fallback:
|
||||
provider: ""
|
||||
model: ""
|
||||
api_key_env: ""
|
||||
base_url: ""
|
||||
max_tokens: 0
|
||||
temperature: 0
|
||||
|
||||
reasoning:
|
||||
system_prompt_file: "prompts/system.md"
|
||||
context_window: 16384
|
||||
memory_messages: 20
|
||||
|
||||
tool_use:
|
||||
enabled: false
|
||||
max_iterations: 3
|
||||
parallel_calls: false
|
||||
|
||||
rate_limit:
|
||||
requests_per_minute: 30
|
||||
tokens_per_minute: 100000
|
||||
concurrent_requests: 3
|
||||
|
||||
# ============================================
|
||||
# TOOLS — ajustar según necesidades del agente
|
||||
# ============================================
|
||||
tools:
|
||||
ssh:
|
||||
enabled: false
|
||||
allowed_targets: []
|
||||
forbidden_commands: []
|
||||
timeout: 0s
|
||||
max_concurrent: 0
|
||||
require_confirmation: []
|
||||
http:
|
||||
enabled: false
|
||||
allowed_domains: []
|
||||
timeout: 0s
|
||||
max_retries: 0
|
||||
scripts:
|
||||
enabled: false
|
||||
scripts_dir: ""
|
||||
allowed: []
|
||||
timeout: 0s
|
||||
sandbox: false
|
||||
file_ops:
|
||||
enabled: false
|
||||
allowed_paths: []
|
||||
read_only: true
|
||||
mcp:
|
||||
enabled: false
|
||||
servers: []
|
||||
expose:
|
||||
port: 0
|
||||
tools: []
|
||||
|
||||
# ============================================
|
||||
# MATRIX
|
||||
# ============================================
|
||||
matrix:
|
||||
homeserver: "${MATRIX_HOMESERVER}"
|
||||
user_id: "@$ID:${MATRIX_SERVER_NAME}"
|
||||
access_token_env: MATRIX_TOKEN_${NORM}
|
||||
device_id: ""
|
||||
|
||||
encryption:
|
||||
enabled: true
|
||||
store_path: "./agents/${ID}/data/crypto/"
|
||||
pickle_key_env: PICKLE_KEY_${NORM}
|
||||
trust_mode: tofu
|
||||
recovery_key_env: SSSS_RECOVERY_KEY_${NORM}
|
||||
|
||||
rooms:
|
||||
listen: []
|
||||
respond: []
|
||||
admin: []
|
||||
|
||||
filters:
|
||||
command_prefix: "!"
|
||||
mention_respond: true
|
||||
dm_respond: true
|
||||
ignore_bots: true
|
||||
ignore_users: []
|
||||
min_power_level: 0
|
||||
|
||||
threads:
|
||||
enabled: true # responder en threads cuando el mensaje viene de un thread
|
||||
auto_thread: false # true para crear thread automático por cada conversación nueva
|
||||
|
||||
# ============================================
|
||||
# INTER-AGENTES
|
||||
# ============================================
|
||||
agents:
|
||||
peers: []
|
||||
delegation:
|
||||
enabled: false
|
||||
can_delegate_to: []
|
||||
can_receive_from: []
|
||||
max_delegation_depth: 1
|
||||
timeout: 30s
|
||||
protocol:
|
||||
format: json
|
||||
channel: matrix
|
||||
heartbeat_interval: 60s
|
||||
|
||||
# ============================================
|
||||
# SSH
|
||||
# ============================================
|
||||
ssh:
|
||||
defaults:
|
||||
user: ""
|
||||
port: 22
|
||||
key_file_env: ""
|
||||
known_hosts: ""
|
||||
keepalive_interval: 0s
|
||||
timeout: 0s
|
||||
targets: {}
|
||||
|
||||
# ============================================
|
||||
# SEGURIDAD
|
||||
# ============================================
|
||||
security:
|
||||
roles:
|
||||
admin:
|
||||
users: ["@admin:\${MATRIX_SERVER_NAME}"]
|
||||
actions: ["*"]
|
||||
user:
|
||||
users: ["*"]
|
||||
actions: ["help"]
|
||||
audit:
|
||||
enabled: false
|
||||
log_file: "./data/audit.log"
|
||||
log_to_room: ""
|
||||
include: []
|
||||
secrets:
|
||||
provider: env
|
||||
|
||||
# ============================================
|
||||
# SCHEDULING
|
||||
# ============================================
|
||||
schedules: []
|
||||
|
||||
# ============================================
|
||||
# OBSERVABILIDAD
|
||||
# ============================================
|
||||
observability:
|
||||
logging:
|
||||
level: info
|
||||
format: json
|
||||
output: stdout
|
||||
file: "./data/$ID.log"
|
||||
metrics:
|
||||
enabled: false
|
||||
port: 0
|
||||
path: /metrics
|
||||
export: prometheus
|
||||
health:
|
||||
enabled: true
|
||||
port: 0
|
||||
path: /healthz
|
||||
tracing:
|
||||
enabled: false
|
||||
provider: ""
|
||||
endpoint: ""
|
||||
|
||||
# ============================================
|
||||
# RESILIENCIA
|
||||
# ============================================
|
||||
resilience:
|
||||
circuit_breaker:
|
||||
failure_threshold: 5
|
||||
timeout: 30s
|
||||
half_open_max: 2
|
||||
retry:
|
||||
max_attempts: 2
|
||||
backoff: exponential
|
||||
initial_delay: 1s
|
||||
max_delay: 10s
|
||||
shutdown:
|
||||
timeout: 10s
|
||||
drain_messages: true
|
||||
save_state: false
|
||||
state_file: ""
|
||||
queue:
|
||||
enabled: true
|
||||
max_size: 50
|
||||
priority_users: ["@admin:\${MATRIX_SERVER_NAME}"]
|
||||
|
||||
# ============================================
|
||||
# ALMACENAMIENTO
|
||||
# ============================================
|
||||
storage:
|
||||
state:
|
||||
backend: sqlite
|
||||
path: "./data/$ID.db"
|
||||
cache:
|
||||
enabled: true
|
||||
backend: memory
|
||||
ttl: 5m
|
||||
max_entries: 200
|
||||
history:
|
||||
backend: sqlite
|
||||
path: "./data/history.db"
|
||||
retention: 168h
|
||||
YAML
|
||||
|
||||
# ── Copiar agent.go desde template y personalizar ────────────────────────
|
||||
cp "$TEMPLATE/agent.go" "$DIR/agent.go"
|
||||
sed -i "s/_template/$PACKAGE/g" "$DIR/agent.go"
|
||||
sed -i "s/Package _template/Package $PACKAGE/g" "$DIR/agent.go"
|
||||
sed -i "s/AGENT_ID_PLACEHOLDER/$ID/g" "$DIR/agent.go"
|
||||
ok "agent.go creado desde template"
|
||||
|
||||
# ── Copiar prompts/system.md desde template y personalizar ───────────────
|
||||
cp "$TEMPLATE/prompts/system.md" "$DIR/prompts/system.md"
|
||||
sed -i "s/Template Agent/$DISPLAYNAME/g" "$DIR/prompts/system.md"
|
||||
ok "prompts/system.md creado desde template"
|
||||
|
||||
ok "Scaffold creado en $DIR/"
|
||||
echo ""
|
||||
|
||||
# ── Actualizar cmd/launcher/main.go — añadir blank import ────────────────
|
||||
LAUNCHER="cmd/launcher/main.go"
|
||||
BLANK_IMPORT="_ \"github.com/enmanuel/agents/agents/$ID\""
|
||||
|
||||
if grep -q "agents/$ID\"" "$LAUNCHER" 2>/dev/null; then
|
||||
warn "$ID ya tiene blank import en $LAUNCHER — saltando"
|
||||
else
|
||||
TAB=$'\t'
|
||||
IMPORT_LINE="${TAB}${BLANK_IMPORT}"
|
||||
|
||||
# Insertar blank import después del último blank import de agents/
|
||||
if awk -v new_import="$IMPORT_LINE" '
|
||||
{
|
||||
lines[NR] = $0
|
||||
if ($0 ~ /_ "github\.com\/enmanuel\/agents\/agents\//)
|
||||
last_import = NR
|
||||
}
|
||||
END {
|
||||
if (!last_import) { for (i=1;i<=NR;i++) print lines[i]; exit 1 }
|
||||
for (i = 1; i <= NR; i++) {
|
||||
print lines[i]
|
||||
if (i == last_import) print new_import
|
||||
}
|
||||
}
|
||||
' "$LAUNCHER" > /tmp/_launcher_tmp; then
|
||||
mv /tmp/_launcher_tmp "$LAUNCHER"
|
||||
ok "Blank import añadido en $LAUNCHER"
|
||||
else
|
||||
warn "No se pudo insertar el blank import — añádelo manualmente:"
|
||||
echo -e " ${GRN}${IMPORT_LINE}${RST}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${YLW}Quedan 3 pasos:${RST}"
|
||||
echo ""
|
||||
echo -e " ${DIM}1. ./dev-scripts/agent/register.sh $ID \"$DISPLAYNAME\"${RST} # registra en Matrix + genera token, password, pickle key"
|
||||
echo -e " ${DIM}2. ./dev-scripts/agent/verify.sh $ID${RST} # genera cross-signing keys + verifica device"
|
||||
echo -e " ${DIM}3. ./dev-scripts/server/start.sh $ID${RST} # arranca el agente"
|
||||
echo ""
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
# register.sh — registra un nuevo bot en el servidor Matrix via Synapse admin API
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/agent/register.sh <username> [displayname]
|
||||
#
|
||||
# Ejemplos:
|
||||
# ./dev-scripts/agent/register.sh assistant-bot "Assistant"
|
||||
# ./dev-scripts/agent/register.sh devops-bot "DevOps Agent"
|
||||
#
|
||||
# Genera y guarda en .env:
|
||||
# MATRIX_TOKEN_<NORM>=... (access token)
|
||||
# MATRIX_PASSWORD_<NORM>=... (password para UIA)
|
||||
# PICKLE_KEY_<NORM>=... (E2EE crypto store key)
|
||||
#
|
||||
# Requiere en .env:
|
||||
# MATRIX_ADMIN_TOKEN=syt_...
|
||||
# MATRIX_HOMESERVER=https://...
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
load_env
|
||||
|
||||
need_arg "${1:-}"
|
||||
|
||||
USERNAME="$1"
|
||||
DISPLAYNAME="${2:-$USERNAME}"
|
||||
NORM="$(normalize_id "$USERNAME")"
|
||||
ENV_VAR="MATRIX_TOKEN_${NORM}"
|
||||
|
||||
[[ -n "${MATRIX_ADMIN_TOKEN:-}" ]] || fail "MATRIX_ADMIN_TOKEN no está en .env"
|
||||
[[ -n "${MATRIX_HOMESERVER:-}" ]] || fail "MATRIX_HOMESERVER no está en .env"
|
||||
|
||||
info "Registrando @${USERNAME}:${MATRIX_SERVER_NAME:-$MATRIX_HOMESERVER}..."
|
||||
dim " Env var prefix: ${NORM}"
|
||||
echo ""
|
||||
|
||||
# Ejecutar cmd/register y capturar su output completo
|
||||
OUTPUT=$("$GO" run ./cmd/register \
|
||||
--homeserver "$MATRIX_HOMESERVER" \
|
||||
--username "$USERNAME" \
|
||||
--displayname "$DISPLAYNAME" \
|
||||
--env-var "$ENV_VAR" 2>&1) || fail "cmd/register falló:\n$OUTPUT"
|
||||
|
||||
echo "$OUTPUT"
|
||||
echo ""
|
||||
|
||||
# ── Parsear y guardar cada variable en .env ──────────────────────────────
|
||||
|
||||
save_env_var() {
|
||||
local key="$1" value="$2"
|
||||
[[ -n "$value" ]] || return
|
||||
|
||||
# Quote values with spaces
|
||||
if [[ "$value" == *" "* ]]; then
|
||||
value="\"${value}\""
|
||||
fi
|
||||
|
||||
if grep -q "^${key}=" .env; then
|
||||
awk -v key="$key" -v val="$value" \
|
||||
'index($0, key "=") == 1 { print key "=" val; next } { print }' \
|
||||
.env > /tmp/_env_tmp && mv /tmp/_env_tmp .env
|
||||
ok "$key actualizado en .env"
|
||||
else
|
||||
printf '%s=%s\n' "$key" "$value" >> .env
|
||||
ok "$key añadido a .env"
|
||||
fi
|
||||
}
|
||||
|
||||
# Extract parseable lines from output
|
||||
TOKEN=$(echo "$OUTPUT" | grep "^${ENV_VAR}=" | cut -d= -f2-)
|
||||
PASSWORD=$(echo "$OUTPUT" | grep "^MATRIX_PASSWORD_${NORM}=" | cut -d= -f2-)
|
||||
PICKLE_KEY=$(echo "$OUTPUT" | grep "^PICKLE_KEY_${NORM}=" | cut -d= -f2-)
|
||||
|
||||
[[ -n "$TOKEN" ]] || fail "No se encontró '${ENV_VAR}=' en el output"
|
||||
|
||||
save_env_var "$ENV_VAR" "$TOKEN"
|
||||
save_env_var "MATRIX_PASSWORD_${NORM}" "$PASSWORD"
|
||||
save_env_var "PICKLE_KEY_${NORM}" "$PICKLE_KEY"
|
||||
|
||||
echo ""
|
||||
echo -e "${YLW}Siguientes pasos:${RST}"
|
||||
echo ""
|
||||
echo -e " ${DIM}1. ./dev-scripts/agent/verify.sh $USERNAME${RST} # genera cross-signing keys E2EE"
|
||||
echo -e " ${DIM}2. ./dev-scripts/server/start.sh $USERNAME${RST} # arranca el agente"
|
||||
echo ""
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
# remove.sh — deshabilita un agente (enabled: false). No borra datos.
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/agent/remove.sh assistant-bot
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
|
||||
need_arg "${1:-}"
|
||||
TARGET="$1"
|
||||
|
||||
found=false
|
||||
while IFS='|' read -r id _version _enabled _desc cfg; do
|
||||
[[ "$id" != "$TARGET" ]] && continue
|
||||
found=true
|
||||
|
||||
# Marcar como disabled en el config
|
||||
if grep -q 'enabled: true' "$cfg"; then
|
||||
sed -i 's/enabled: true/enabled: false/' "$cfg"
|
||||
ok "$id marcado como disabled en $cfg"
|
||||
info "Reinicia el launcher para aplicar: ./dev-scripts/server/server.sh restart"
|
||||
else
|
||||
warn "$id ya estaba marcado como disabled"
|
||||
fi
|
||||
|
||||
dim " Datos preservados en agents/$id/data/"
|
||||
|
||||
done < <(list_agents_raw)
|
||||
|
||||
"$found" || fail "Agente '$TARGET' no encontrado"
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# reset-password.sh — resetea la contraseña de un bot existente y la escribe en .env
|
||||
#
|
||||
# A diferencia de register.sh, este script NO crea una nueva sesión ni cambia el device ID.
|
||||
# El token de acceso actual sigue siendo válido.
|
||||
# Útil para añadir MATRIX_PASSWORD_X a .env en bots ya registrados.
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/agent/reset-password.sh <agent-id>
|
||||
#
|
||||
# Ejemplo:
|
||||
# ./dev-scripts/agent/reset-password.sh assistant-bot
|
||||
#
|
||||
# Requiere en .env:
|
||||
# MATRIX_ADMIN_TOKEN=syt_...
|
||||
# MATRIX_HOMESERVER=https://...
|
||||
# MATRIX_SERVER_NAME=...
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
load_env
|
||||
|
||||
need_arg "${1:-}"
|
||||
|
||||
ID="$1"
|
||||
USERNAME="$ID"
|
||||
PASSWORD_ENV_VAR="MATRIX_PASSWORD_$(echo "$ID" | tr '[:lower:]-' '[:upper:]_' | sed 's/_BOT$//')"
|
||||
|
||||
[[ -n "${MATRIX_ADMIN_TOKEN:-}" ]] || fail "MATRIX_ADMIN_TOKEN no está en .env"
|
||||
[[ -n "${MATRIX_HOMESERVER:-}" ]] || fail "MATRIX_HOMESERVER no está en .env"
|
||||
[[ -n "${MATRIX_SERVER_NAME:-}" ]] || fail "MATRIX_SERVER_NAME no está en .env"
|
||||
|
||||
USER_ID="@${USERNAME}:${MATRIX_SERVER_NAME}"
|
||||
|
||||
# Generar nueva contraseña aleatoria
|
||||
NEW_PASSWORD=$(head -c 24 /dev/urandom | od -A n -t x1 | tr -d ' \n')
|
||||
|
||||
info "Reseteando contraseña de ${USER_ID}..."
|
||||
info "(El token de acceso actual NO cambia — solo la contraseña)"
|
||||
|
||||
# Synapse admin API: reset password sin cerrar sesiones existentes
|
||||
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
||||
"${MATRIX_HOMESERVER}/_synapse/admin/v1/reset_password/${USER_ID}" \
|
||||
-H "Authorization: Bearer ${MATRIX_ADMIN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"new_password\": \"${NEW_PASSWORD}\", \"logout_devices\": false}")
|
||||
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
||||
BODY=$(echo "$RESPONSE" | head -1)
|
||||
|
||||
if [[ "$HTTP_CODE" != "200" ]]; then
|
||||
fail "Admin API devolvió HTTP $HTTP_CODE: $BODY"
|
||||
fi
|
||||
|
||||
# Escribir en .env
|
||||
if grep -q "^${PASSWORD_ENV_VAR}=" .env; then
|
||||
awk -v key="$PASSWORD_ENV_VAR" -v val="$NEW_PASSWORD" \
|
||||
'index($0, key "=") == 1 { print key "=" val; next } { print }' \
|
||||
.env > /tmp/_env_tmp && mv /tmp/_env_tmp .env
|
||||
ok "$PASSWORD_ENV_VAR actualizado en .env"
|
||||
else
|
||||
printf '\n%s=%s\n' "$PASSWORD_ENV_VAR" "$NEW_PASSWORD" >> .env
|
||||
ok "$PASSWORD_ENV_VAR añadido a .env"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
ok "Contraseña reseteada para ${USER_ID}"
|
||||
dim " El bot usará esta contraseña para cross-signing bootstrap en el próximo arranque."
|
||||
dim " Reinícialo con: ./dev-scripts/server/stop.sh $ID && ./dev-scripts/server/start.sh $ID"
|
||||
Executable
+167
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env bash
|
||||
# verify.sh — (re)verifica dispositivos E2EE de agentes Matrix
|
||||
#
|
||||
# Genera/sube cross-signing keys y firma el device de cada agente.
|
||||
# Usa el MISMO crypto store que el agente para que las keys queden disponibles.
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/agent/verify.sh # verifica todos los habilitados con E2EE
|
||||
# ./dev-scripts/agent/verify.sh assistant-bot # verifica uno específico
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
load_env
|
||||
|
||||
TARGET="${1:-}"
|
||||
|
||||
# ── YAML helpers (simple grep-based, no deps) ────────────────────────────
|
||||
|
||||
yaml_val() {
|
||||
# Extract a simple YAML value: yaml_val file "key"
|
||||
# Handles both quoted and unquoted values.
|
||||
local file="$1" key="$2"
|
||||
grep -m1 "^\s*${key}:" "$file" 2>/dev/null \
|
||||
| sed 's/^[^:]*:\s*//' \
|
||||
| tr -d '"' \
|
||||
| tr -d "'" \
|
||||
| xargs
|
||||
}
|
||||
|
||||
# ── Verify a single agent ────────────────────────────────────────────────
|
||||
|
||||
verify_agent() {
|
||||
local cfg="$1"
|
||||
local agent_id; agent_id="$(yaml_val "$cfg" "id")"
|
||||
local agent_dir; agent_dir="$(dirname "$cfg")"
|
||||
|
||||
# Check E2EE is enabled
|
||||
local enc_enabled; enc_enabled="$(yaml_val "$cfg" "enabled")"
|
||||
# The first "enabled" is agent.enabled; we need encryption.enabled specifically
|
||||
enc_enabled="$(grep -A5 'encryption:' "$cfg" | grep -m1 'enabled:' | awk '{print $2}')"
|
||||
if [[ "$enc_enabled" != "true" ]]; then
|
||||
dim " $agent_id — E2EE deshabilitado, saltando"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Extract config values
|
||||
local user_id; user_id="$(yaml_val "$cfg" "user_id")"
|
||||
local username; username="$(echo "$user_id" | sed 's/@\([^:]*\):.*/\1/')"
|
||||
local token_env; token_env="$(yaml_val "$cfg" "access_token_env")"
|
||||
local pickle_env; pickle_env="$(yaml_val "$cfg" "pickle_key_env")"
|
||||
local recovery_env; recovery_env="$(yaml_val "$cfg" "recovery_key_env")"
|
||||
local store_path; store_path="$(grep -A5 'encryption:' "$cfg" | grep -m1 'store_path:' | sed 's/^[^:]*:\s*//' | tr -d '"' | xargs)"
|
||||
|
||||
local token="${!token_env:-}"
|
||||
local pickle_key="${!pickle_env:-}"
|
||||
|
||||
# Find password — convention: MATRIX_PASSWORD_<NORMALIZED>
|
||||
local norm; norm="$(echo "$username" | tr '-' '_' | tr '[:lower:]' '[:upper:]')"
|
||||
local pass_env="MATRIX_PASSWORD_${norm}"
|
||||
local password="${!pass_env:-}"
|
||||
|
||||
# Validate required values
|
||||
if [[ -z "$token" ]]; then
|
||||
fail " $agent_id — $token_env no está en .env"
|
||||
return 1
|
||||
fi
|
||||
if [[ -z "$password" ]]; then
|
||||
warn " $agent_id — $pass_env no está en .env, intentando sin password..."
|
||||
fi
|
||||
|
||||
info "$agent_id — verificando device..."
|
||||
dim " user: $username"
|
||||
dim " store: $store_path"
|
||||
dim " pickle_env: $pickle_env"
|
||||
dim " token_env: $token_env"
|
||||
|
||||
# Stop agent if running (crypto store can't be shared)
|
||||
local was_running=false
|
||||
if is_running "$agent_id"; then
|
||||
was_running=true
|
||||
info " Deteniendo $agent_id antes de verificar..."
|
||||
"$REPO_ROOT/dev-scripts/server/stop.sh" "$agent_id"
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# Build verify command
|
||||
local verify_bin="$REPO_ROOT/bin/verify"
|
||||
if [[ ! -x "$verify_bin" ]] || [[ "$(find ./cmd/verify -newer "$verify_bin" 2>/dev/null | head -1)" ]]; then
|
||||
info " Compilando cmd/verify..."
|
||||
mkdir -p "$(dirname "$verify_bin")"
|
||||
"$GO" build -tags goolm -o "$verify_bin" ./cmd/verify || {
|
||||
fail " No se pudo compilar cmd/verify"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Run verification
|
||||
local verify_args=(
|
||||
--homeserver "$MATRIX_HOMESERVER"
|
||||
--username "$username"
|
||||
--token "$token"
|
||||
--store "$store_path"
|
||||
)
|
||||
if [[ -n "$password" ]]; then
|
||||
verify_args+=(--password "$password")
|
||||
fi
|
||||
if [[ -n "$pickle_key" ]]; then
|
||||
verify_args+=(--pickle-key "$pickle_key")
|
||||
fi
|
||||
|
||||
local output
|
||||
if output=$("$verify_bin" "${verify_args[@]}" 2>&1); then
|
||||
ok "$agent_id — verificación exitosa"
|
||||
|
||||
# Extract recovery key from output if present
|
||||
local new_rk
|
||||
new_rk="$(echo "$output" | grep "^SSSS_RECOVERY_KEY_" | cut -d= -f2-)"
|
||||
if [[ -n "$new_rk" && -n "$recovery_env" ]]; then
|
||||
# Update .env with new recovery key (quoted — keys contain spaces)
|
||||
local quoted_rk="\"${new_rk}\""
|
||||
if grep -q "^${recovery_env}=" "$REPO_ROOT/.env"; then
|
||||
sed -i "s|^${recovery_env}=.*|${recovery_env}=${quoted_rk}|" "$REPO_ROOT/.env"
|
||||
ok " Recovery key actualizada en .env ($recovery_env)"
|
||||
else
|
||||
echo "${recovery_env}=${quoted_rk}" >> "$REPO_ROOT/.env"
|
||||
ok " Recovery key añadida a .env ($recovery_env)"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
warn "$agent_id — verify output:"
|
||||
echo "$output"
|
||||
# If it says keys already exist, that's usually fine
|
||||
if echo "$output" | grep -q "signed with cross-signing key"; then
|
||||
ok "$agent_id — device firmado con keys existentes"
|
||||
else
|
||||
warn "$agent_id — puede necesitar atención manual"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$output" | sed 's/^/ /'
|
||||
|
||||
# Restart agent if it was running
|
||||
if [[ "$was_running" == "true" ]]; then
|
||||
info " Reiniciando $agent_id..."
|
||||
"$REPO_ROOT/dev-scripts/server/start.sh" "$agent_id"
|
||||
fi
|
||||
|
||||
echo
|
||||
}
|
||||
|
||||
# ── Main ──────────────────────────────────────────────────────────────────
|
||||
|
||||
echo
|
||||
info "Verificación E2EE de agentes Matrix"
|
||||
echo
|
||||
|
||||
if [[ -n "$TARGET" ]]; then
|
||||
cfg="$(config_path_for "$TARGET")"
|
||||
[[ -n "$cfg" ]] || fail "Agente '$TARGET' no encontrado"
|
||||
verify_agent "$cfg"
|
||||
else
|
||||
while IFS='|' read -r id version enabled desc cfg; do
|
||||
[[ "$enabled" == "true" ]] || continue
|
||||
verify_agent "$cfg"
|
||||
done < <(list_agents_raw)
|
||||
fi
|
||||
|
||||
ok "Verificación completada"
|
||||
@@ -0,0 +1,45 @@
|
||||
# dev-scripts/cron/ — Gestión de automatizaciones cron
|
||||
|
||||
Scripts para crear, listar y aplicar automatizaciones del catálogo `crons/`.
|
||||
|
||||
## Scripts
|
||||
|
||||
### `new.sh` — Scaffolder interactivo
|
||||
|
||||
Crea una nueva automatización en `crons/<nombre>/`:
|
||||
|
||||
```bash
|
||||
./dev-scripts/cron/new.sh
|
||||
```
|
||||
|
||||
Pregunta: nombre, descripción, tipo de acción (`send_message` o `llm_prompt`) y cron expression.
|
||||
Crea `schedule.yaml` y el archivo de prompt/mensaje vacío.
|
||||
Imprime el bloque YAML listo para añadir a `config.yaml`.
|
||||
|
||||
### `list.sh` — Listar automatizaciones
|
||||
|
||||
Lista todas las automatizaciones del catálogo con nombre, tipo, cron y descripción:
|
||||
|
||||
```bash
|
||||
./dev-scripts/cron/list.sh
|
||||
```
|
||||
|
||||
### `apply.sh` — Aplicar a un agente
|
||||
|
||||
Añade una automatización al `config.yaml` de un agente:
|
||||
|
||||
```bash
|
||||
./dev-scripts/cron/apply.sh <nombre> <agent-id>
|
||||
|
||||
# Ejemplo:
|
||||
./dev-scripts/cron/apply.sh good-morning assistant-bot
|
||||
```
|
||||
|
||||
Usa `yq` si está disponible para parchear el YAML directamente.
|
||||
Si `yq` no está instalado, imprime el bloque YAML para copiar a mano.
|
||||
|
||||
Recuerda editar `output_room` en `config.yaml` con la sala real del agente.
|
||||
|
||||
## Catálogo
|
||||
|
||||
Las automatizaciones viven en `crons/`. Ver `crons/README.md` para la documentación completa.
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
# apply.sh <name> <agent-id>
|
||||
# Añade la automatización <name> al config.yaml del agente <agent-id>.
|
||||
# Usa yq si está disponible; en caso contrario imprime el bloque YAML para copiar a mano.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
|
||||
if [[ $# -ne 2 ]]; then
|
||||
echo "Uso: $0 <nombre-automatizacion> <agent-id>" >&2
|
||||
echo "Ejemplo: $0 good-morning assistant-bot" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NAME="$1"
|
||||
AGENT_ID="$2"
|
||||
|
||||
SCHEDULE_FILE="$REPO_ROOT/crons/$NAME/schedule.yaml"
|
||||
AGENT_CONFIG="$REPO_ROOT/agents/$AGENT_ID/config.yaml"
|
||||
|
||||
if [[ ! -f "$SCHEDULE_FILE" ]]; then
|
||||
echo "Error: no existe crons/$NAME/schedule.yaml" >&2
|
||||
echo "Usa ./dev-scripts/cron/list.sh para ver las automatizaciones disponibles." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$AGENT_CONFIG" ]]; then
|
||||
echo "Error: no existe agents/$AGENT_ID/config.yaml" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse schedule.yaml fields
|
||||
kind=""
|
||||
template=""
|
||||
cron_expr=""
|
||||
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
" kind:"*) kind="${line#*kind:}"; kind="${kind// /}" ;;
|
||||
" template:"*) template="${line#*template:}"; template="${template# }" ;;
|
||||
default_cron:*) cron_expr="${line#default_cron:}"; cron_expr="${cron_expr# }"; cron_expr="${cron_expr//\"/}" ;;
|
||||
esac
|
||||
done < "$SCHEDULE_FILE"
|
||||
|
||||
if [[ -z "$kind" || -z "$template" || -z "$cron_expr" ]]; then
|
||||
echo "Error: schedule.yaml incompleto (falta kind, template o default_cron)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build YAML block
|
||||
YAML_BLOCK=" - name: $NAME
|
||||
cron: \"$cron_expr\"
|
||||
output_room: \"\" # TODO: reemplaza con la sala real del agente
|
||||
action:
|
||||
kind: $kind
|
||||
template: \"$template\""
|
||||
|
||||
# Try yq first
|
||||
if command -v yq &>/dev/null; then
|
||||
# Check if schedules key already has this entry
|
||||
existing=$(yq ".schedules // [] | .[] | select(.name == \"$NAME\") | .name" "$AGENT_CONFIG" 2>/dev/null || true)
|
||||
if [[ -n "$existing" ]]; then
|
||||
echo "Advertencia: el agente $AGENT_ID ya tiene un schedule llamado '$NAME'. No se añade de nuevo."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Append using yq
|
||||
yq -i ".schedules += [{\"name\": \"$NAME\", \"cron\": \"$cron_expr\", \"output_room\": \"\", \"action\": {\"kind\": \"$kind\", \"template\": \"$template\"}}]" "$AGENT_CONFIG"
|
||||
echo "✓ Añadido schedule '$NAME' a agents/$AGENT_ID/config.yaml"
|
||||
echo "→ Edita output_room en agents/$AGENT_ID/config.yaml para apuntar a la sala correcta."
|
||||
else
|
||||
echo "yq no está disponible. Añade manualmente el siguiente bloque a agents/$AGENT_ID/config.yaml:"
|
||||
echo ""
|
||||
echo "schedules:"
|
||||
echo "$YAML_BLOCK"
|
||||
echo ""
|
||||
echo "→ Edita output_room para apuntar a la sala correcta del agente."
|
||||
fi
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
# list.sh — Lista todas las automatizaciones del catálogo crons/ con nombre, tipo y descripción.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
CRONS_DIR="$REPO_ROOT/crons"
|
||||
|
||||
if [[ ! -d "$CRONS_DIR" ]]; then
|
||||
echo "No se encontró el directorio crons/." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Collect entries: name, kind, default_cron, description
|
||||
entries=()
|
||||
while IFS= read -r -d '' schedule_file; do
|
||||
name=""
|
||||
description=""
|
||||
kind=""
|
||||
cron_expr=""
|
||||
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
name:*) name="${line#name:}"; name="${name// /}" ;;
|
||||
description:*) description="${line#description:}"; description="${description# }"; description="${description#\"}"; description="${description%\"}" ;;
|
||||
" kind:"*) kind="${line#*kind:}"; kind="${kind// /}" ;;
|
||||
default_cron:*) cron_expr="${line#default_cron:}"; cron_expr="${cron_expr# }"; cron_expr="${cron_expr//\"/}" ;;
|
||||
esac
|
||||
done < "$schedule_file"
|
||||
|
||||
if [[ -n "$name" ]]; then
|
||||
entries+=("$name|$kind|$cron_expr|$description")
|
||||
fi
|
||||
done < <(find "$CRONS_DIR" -name "schedule.yaml" -print0 | sort -z)
|
||||
|
||||
if [[ ${#entries[@]} -eq 0 ]]; then
|
||||
echo "No hay automatizaciones en crons/."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Print header
|
||||
printf "%-22s %-15s %-15s %s\n" "NOMBRE" "TIPO" "CRON" "DESCRIPCIÓN"
|
||||
printf "%-22s %-15s %-15s %s\n" "------" "----" "----" "-----------"
|
||||
|
||||
for entry in "${entries[@]}"; do
|
||||
IFS='|' read -r name kind cron_expr description <<< "$entry"
|
||||
printf "%-22s %-15s %-15s %s\n" "$name" "$kind" "$cron_expr" "$description"
|
||||
done
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env bash
|
||||
# new.sh — Scaffolder interactivo para automatizaciones cron
|
||||
# Crea crons/<name>/schedule.yaml y el archivo de prompt/mensaje vacío.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
|
||||
echo "=== Nueva automatización cron ==="
|
||||
echo ""
|
||||
|
||||
# Nombre
|
||||
read -rp "Nombre de la automatización (ej: weekly-report): " NAME
|
||||
NAME="${NAME// /-}"
|
||||
NAME="${NAME,,}" # lowercase
|
||||
if [[ -z "$NAME" ]]; then
|
||||
echo "Error: el nombre no puede estar vacío." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -d "$REPO_ROOT/crons/$NAME" ]]; then
|
||||
echo "Error: ya existe crons/$NAME/" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Descripción
|
||||
read -rp "Descripción breve: " DESCRIPTION
|
||||
if [[ -z "$DESCRIPTION" ]]; then
|
||||
echo "Error: la descripción no puede estar vacía." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Tipo de acción
|
||||
echo ""
|
||||
echo "Tipo de acción:"
|
||||
echo " 1) send_message — envía un mensaje estático o plantilla"
|
||||
echo " 2) llm_prompt — llama al LLM con un prompt y envía la respuesta"
|
||||
read -rp "Selecciona [1/2]: " ACTION_TYPE_NUM
|
||||
case "$ACTION_TYPE_NUM" in
|
||||
1) ACTION_KIND="send_message"; PROMPT_FILE="message.md" ;;
|
||||
2) ACTION_KIND="llm_prompt"; PROMPT_FILE="prompt.md" ;;
|
||||
*)
|
||||
echo "Error: selección inválida. Usa 1 o 2." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Cron expression
|
||||
DEFAULT_CRON="0 9 * * *"
|
||||
read -rp "Cron expression [default: $DEFAULT_CRON]: " CRON_EXPR
|
||||
CRON_EXPR="${CRON_EXPR:-$DEFAULT_CRON}"
|
||||
|
||||
# Crear estructura
|
||||
CRON_DIR="$REPO_ROOT/crons/$NAME"
|
||||
PROMPTS_DIR="$CRON_DIR/prompts"
|
||||
mkdir -p "$PROMPTS_DIR"
|
||||
|
||||
# schedule.yaml
|
||||
cat > "$CRON_DIR/schedule.yaml" <<EOF
|
||||
# Automatización: $NAME
|
||||
name: $NAME
|
||||
description: "$DESCRIPTION"
|
||||
|
||||
# Cron por defecto
|
||||
default_cron: "$CRON_EXPR"
|
||||
|
||||
# Acción
|
||||
action:
|
||||
kind: $ACTION_KIND
|
||||
# Relativo a la raíz del proyecto
|
||||
template: crons/$NAME/prompts/$PROMPT_FILE
|
||||
|
||||
# Sala de salida por defecto (vacío = el agente debe configurar output_room)
|
||||
default_output_room: ""
|
||||
EOF
|
||||
|
||||
# Archivo de prompt/mensaje
|
||||
touch "$PROMPTS_DIR/$PROMPT_FILE"
|
||||
|
||||
echo ""
|
||||
echo "✓ Creado: crons/$NAME/schedule.yaml"
|
||||
echo "✓ Creado: crons/$NAME/prompts/$PROMPT_FILE"
|
||||
echo ""
|
||||
echo "Edita crons/$NAME/prompts/$PROMPT_FILE con el contenido deseado."
|
||||
echo ""
|
||||
echo "Añade esto a agents/<id>/config.yaml:"
|
||||
echo ""
|
||||
echo " schedules:"
|
||||
echo " - name: $NAME"
|
||||
echo " cron: \"$CRON_EXPR\""
|
||||
echo " output_room: \"!TUROOM:matrix-af2f3d.organic-machine.com\""
|
||||
echo " action:"
|
||||
echo " kind: $ACTION_KIND"
|
||||
echo " template: \"crons/$NAME/prompts/$PROMPT_FILE\""
|
||||
echo ""
|
||||
echo "O usa: ./dev-scripts/cron/apply.sh $NAME <agent-id>"
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
# install.sh — instalar dependencias para E2E tests
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
E2E_DIR="$REPO_ROOT/e2e"
|
||||
|
||||
echo "=== Instalacion de E2E tests ==="
|
||||
|
||||
# 1. Verificar Node.js
|
||||
if ! command -v node &>/dev/null; then
|
||||
echo "ERROR: Node.js no encontrado."
|
||||
echo "Instalar Node.js v18+ con:"
|
||||
echo " curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -"
|
||||
echo " sudo apt-get install -y nodejs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NODE_VERSION=$(node -v | sed 's/v//' | cut -d. -f1)
|
||||
if [ "$NODE_VERSION" -lt 18 ]; then
|
||||
echo "ERROR: Se requiere Node.js v18+, encontrado v$(node -v)"
|
||||
exit 1
|
||||
fi
|
||||
echo "Node.js $(node -v) OK"
|
||||
|
||||
# 2. Instalar dependencias del proyecto
|
||||
echo "Instalando dependencias npm..."
|
||||
cd "$E2E_DIR"
|
||||
npm ci
|
||||
|
||||
# 3. Instalar Chromium para Playwright
|
||||
echo "Instalando Chromium para Playwright..."
|
||||
npx playwright install chromium
|
||||
|
||||
# 4. Instalar dependencias del sistema para Playwright
|
||||
echo "Instalando dependencias del sistema (requiere sudo)..."
|
||||
sudo npx playwright install-deps chromium
|
||||
|
||||
echo ""
|
||||
echo "=== Instalacion completa ==="
|
||||
echo "Siguiente paso: copiar e2e/.env.example a e2e/.env y configurar credenciales"
|
||||
Executable
+134
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env bash
|
||||
# run.sh — ejecutar E2E tests con Playwright
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/e2e/run.sh # headless (default)
|
||||
# ./dev-scripts/e2e/run.sh --headed # con browser visible (requiere DISPLAY)
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
E2E_DIR="$REPO_ROOT/e2e"
|
||||
ELEMENT_SCRIPT="$E2E_DIR/scripts/setup-element.sh"
|
||||
PS_SCRIPT="$REPO_ROOT/dev-scripts/server/ps.sh"
|
||||
|
||||
HEADED=false
|
||||
EXTRA_ARGS=()
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--headed)
|
||||
HEADED=true
|
||||
;;
|
||||
*)
|
||||
EXTRA_ARGS+=("$arg")
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# --- Verificaciones previas ---
|
||||
|
||||
# 1. Verificar dependencias instaladas
|
||||
if [ ! -d "$E2E_DIR/node_modules" ]; then
|
||||
echo "ERROR: node_modules no encontrado. Ejecutar primero:"
|
||||
echo " ./dev-scripts/e2e/install.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. Verificar .env
|
||||
if [ ! -f "$E2E_DIR/.env" ]; then
|
||||
echo "ERROR: e2e/.env no encontrado. Crear desde el template:"
|
||||
echo " cp e2e/.env.example e2e/.env"
|
||||
echo " # editar e2e/.env con credenciales"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 3. Verificar que los agentes estan corriendo
|
||||
echo "=== Verificando agentes ==="
|
||||
if [ -x "$PS_SCRIPT" ]; then
|
||||
if ! "$PS_SCRIPT" 2>/dev/null | grep -q "running"; then
|
||||
echo "WARN: el launcher no parece estar corriendo."
|
||||
echo " Iniciar con: ./dev-scripts/server/start.sh"
|
||||
echo " Continuando de todas formas..."
|
||||
else
|
||||
echo "Launcher corriendo OK"
|
||||
fi
|
||||
else
|
||||
echo "WARN: no se encontro ps.sh, no se puede verificar el estado de los agentes"
|
||||
fi
|
||||
|
||||
# --- Element Web ---
|
||||
|
||||
echo ""
|
||||
echo "=== Element Web ==="
|
||||
ELEMENT_STARTED_BY_US=false
|
||||
|
||||
if [ -x "$ELEMENT_SCRIPT" ]; then
|
||||
if "$ELEMENT_SCRIPT" status 2>/dev/null | grep -q "corriendo\|running\|listening"; then
|
||||
echo "Element Web ya esta corriendo"
|
||||
else
|
||||
echo "Levantando Element Web..."
|
||||
"$ELEMENT_SCRIPT" start
|
||||
ELEMENT_STARTED_BY_US=true
|
||||
# Esperar a que el servidor este listo
|
||||
sleep 2
|
||||
fi
|
||||
else
|
||||
echo "WARN: setup-element.sh no encontrado. Asegurarse de que Element Web esta corriendo."
|
||||
fi
|
||||
|
||||
# --- Ejecutar tests ---
|
||||
|
||||
echo ""
|
||||
echo "=== Ejecutando E2E tests ==="
|
||||
|
||||
PLAYWRIGHT_ARGS=()
|
||||
if [ "$HEADED" = true ]; then
|
||||
if [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]; then
|
||||
echo "WARN: --headed solicitado pero no se detecta DISPLAY. Ejecutando headless."
|
||||
else
|
||||
PLAYWRIGHT_ARGS+=("--headed")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Agregar argumentos extra del usuario
|
||||
if [ ${#EXTRA_ARGS[@]} -gt 0 ]; then
|
||||
PLAYWRIGHT_ARGS+=("${EXTRA_ARGS[@]}")
|
||||
fi
|
||||
|
||||
EXIT_CODE=0
|
||||
cd "$E2E_DIR"
|
||||
npx playwright test "${PLAYWRIGHT_ARGS[@]}" || EXIT_CODE=$?
|
||||
|
||||
# Generar reporte HTML si hay fallos
|
||||
if [ "$EXIT_CODE" -ne 0 ]; then
|
||||
echo ""
|
||||
echo "=== Generando reporte HTML ==="
|
||||
npx playwright show-report --host 0.0.0.0 --port 0 2>/dev/null &
|
||||
REPORT_PID=$!
|
||||
sleep 1
|
||||
kill "$REPORT_PID" 2>/dev/null || true
|
||||
echo "Reporte disponible en: $E2E_DIR/playwright-report/"
|
||||
echo " Para verlo: cd e2e && npx playwright show-report"
|
||||
fi
|
||||
|
||||
# --- Teardown ---
|
||||
|
||||
if [ "$ELEMENT_STARTED_BY_US" = true ]; then
|
||||
echo ""
|
||||
echo "=== Deteniendo Element Web ==="
|
||||
"$ELEMENT_SCRIPT" stop 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# --- Resultado ---
|
||||
|
||||
echo ""
|
||||
if [ "$EXIT_CODE" -eq 0 ]; then
|
||||
echo "=== Todos los tests pasaron ==="
|
||||
else
|
||||
echo "=== Algunos tests fallaron (exit code: $EXIT_CODE) ==="
|
||||
echo "Ver screenshots en: $E2E_DIR/test-results/"
|
||||
fi
|
||||
|
||||
exit "$EXIT_CODE"
|
||||
@@ -0,0 +1,71 @@
|
||||
# dev-scripts/server
|
||||
|
||||
Scripts para gestionar el ciclo de vida del launcher unificado que ejecuta todos los agentes habilitados.
|
||||
|
||||
## Scripts
|
||||
|
||||
### start.sh
|
||||
|
||||
Inicia el launcher unificado. Compila el binario y ejecuta los tests si es necesario antes de arrancar. Reporta el número de agentes habilitados.
|
||||
|
||||
```bash
|
||||
./dev-scripts/server/start.sh
|
||||
```
|
||||
|
||||
### stop.sh
|
||||
|
||||
Detiene el launcher de forma ordenada. Envía SIGTERM, espera 5 segundos, y si no termina usa SIGKILL.
|
||||
|
||||
```bash
|
||||
./dev-scripts/server/stop.sh
|
||||
```
|
||||
|
||||
### restart.sh
|
||||
|
||||
Reinicia el launcher (ejecuta stop.sh seguido de start.sh).
|
||||
|
||||
```bash
|
||||
./dev-scripts/server/restart.sh
|
||||
```
|
||||
|
||||
### ps.sh
|
||||
|
||||
Muestra el estado del proceso del launcher con métricas detalladas: PID, uptime, uso de memoria, CPU y tamaño de logs.
|
||||
|
||||
```bash
|
||||
./dev-scripts/server/ps.sh
|
||||
```
|
||||
|
||||
### logs.sh
|
||||
|
||||
Sigue los logs del launcher en tiempo real (tail -f). Acepta un argumento opcional para el número de líneas iniciales.
|
||||
|
||||
```bash
|
||||
./dev-scripts/server/logs.sh # últimas líneas por defecto
|
||||
./dev-scripts/server/logs.sh 50 # últimas 50 líneas
|
||||
```
|
||||
|
||||
### dashboard.sh
|
||||
|
||||
Abre la TUI interactiva (bubbletea) para gestión visual de bots. Permite ver estado, iniciar/detener agentes y ver logs desde una interfaz de terminal.
|
||||
|
||||
```bash
|
||||
./dev-scripts/server/dashboard.sh
|
||||
```
|
||||
|
||||
### server.sh
|
||||
|
||||
CLI unificado que enruta comandos a los scripts individuales. Útil como punto de entrada único.
|
||||
|
||||
```bash
|
||||
./dev-scripts/server/server.sh start # → start.sh
|
||||
./dev-scripts/server/server.sh stop # → stop.sh
|
||||
./dev-scripts/server/server.sh restart # → restart.sh
|
||||
./dev-scripts/server/server.sh status # resumen general del servidor
|
||||
./dev-scripts/server/server.sh ps # → ps.sh
|
||||
./dev-scripts/server/server.sh logs # → logs.sh
|
||||
./dev-scripts/server/server.sh kill # SIGKILL forzado (emergencia)
|
||||
./dev-scripts/server/server.sh enable <id> # habilita un agente
|
||||
./dev-scripts/server/server.sh disable <id> # deshabilita un agente
|
||||
./dev-scripts/server/server.sh dashboard # → dashboard.sh
|
||||
```
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
echo "Building dashboard..."
|
||||
/usr/local/go/bin/go build -tags goolm -o bin/dashboard ./cmd/dashboard/
|
||||
echo "Done → bin/dashboard"
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# dashboard.sh — lanza el TUI interactivo de gestión de bots
|
||||
# Uso: ./dev-scripts/server/dashboard.sh
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
|
||||
exec "$GO" run ./cmd/dashboard "$@"
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
# logs.sh — sigue los logs de agentes (logs/<agent>/YYYY-MM-DD.jsonl)
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/server/logs.sh # tail -f de todos los agentes (hoy)
|
||||
# ./dev-scripts/server/logs.sh assistant-bot # tail -f de un agente específico
|
||||
# ./dev-scripts/server/logs.sh assistant-bot 100 # últimas 100 líneas
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
|
||||
LOG_DIR="logs"
|
||||
AGENT_ID="${1:-}"
|
||||
LINES="${2:-50}"
|
||||
|
||||
if [[ ! -d "$LOG_DIR" ]]; then
|
||||
fail "No hay logs todavía — inicia el launcher primero"
|
||||
fi
|
||||
|
||||
TODAY="$(date -u +%Y-%m-%d)"
|
||||
|
||||
if [[ -n "$AGENT_ID" ]]; then
|
||||
# Logs de un agente específico
|
||||
LOG_FILE="$LOG_DIR/$AGENT_ID/$TODAY.jsonl"
|
||||
if [[ ! -f "$LOG_FILE" ]]; then
|
||||
# Try to find the latest log file for this agent
|
||||
LATEST="$(ls -t "$LOG_DIR/$AGENT_ID/"*.jsonl 2>/dev/null | head -1)"
|
||||
if [[ -z "$LATEST" ]]; then
|
||||
fail "No hay logs para $AGENT_ID"
|
||||
fi
|
||||
LOG_FILE="$LATEST"
|
||||
fi
|
||||
info "Siguiendo logs: $LOG_FILE"
|
||||
dim " Ctrl+C para salir"
|
||||
echo ""
|
||||
tail -n "$LINES" -f "$LOG_FILE"
|
||||
else
|
||||
# Logs de todos los agentes (archivos de hoy)
|
||||
FILES=$(find "$LOG_DIR" -name "$TODAY.jsonl" 2>/dev/null)
|
||||
if [[ -z "$FILES" ]]; then
|
||||
# Fallback: latest file from each agent
|
||||
FILES=""
|
||||
for d in "$LOG_DIR"/*/; do
|
||||
LATEST="$(ls -t "$d"*.jsonl 2>/dev/null | head -1)"
|
||||
[[ -n "$LATEST" ]] && FILES="$FILES $LATEST"
|
||||
done
|
||||
fi
|
||||
if [[ -z "$FILES" ]]; then
|
||||
fail "No hay logs todavía — inicia el launcher primero"
|
||||
fi
|
||||
info "Siguiendo logs de todos los agentes (hoy: $TODAY)"
|
||||
dim " Ctrl+C para salir"
|
||||
echo ""
|
||||
# shellcheck disable=SC2086
|
||||
tail -n "$LINES" -f $FILES
|
||||
fi
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
# ps.sh — muestra el estado del launcher unificado y agentes
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/server/ps.sh
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
|
||||
if ! is_launcher_running; then
|
||||
echo ""
|
||||
dim " Launcher no está corriendo."
|
||||
echo ""
|
||||
echo " Agentes:"
|
||||
while IFS='|' read -r id _v enabled _d _c; do
|
||||
if [[ "$enabled" == "true" ]]; then
|
||||
echo -e " ${GRN}●${RST} $id (enabled)"
|
||||
else
|
||||
echo -e " ${DIM}○ $id (disabled)${RST}"
|
||||
fi
|
||||
done < <(list_agents_raw)
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pid="$(read_launcher_pid)"
|
||||
|
||||
# Uptime
|
||||
if [[ -f /proc/$pid/stat ]]; then
|
||||
start_ticks=$(awk '{print $22}' /proc/$pid/stat 2>/dev/null || echo 0)
|
||||
clk_tck=$(getconf CLK_TCK)
|
||||
boot_time=$(awk '/btime/{print $2}' /proc/stat)
|
||||
proc_start=$((boot_time + start_ticks / clk_tck))
|
||||
now=$(date +%s)
|
||||
elapsed=$((now - proc_start))
|
||||
days=$((elapsed / 86400))
|
||||
hours=$(( (elapsed % 86400) / 3600 ))
|
||||
mins=$(( (elapsed % 3600) / 60 ))
|
||||
if [[ $days -gt 0 ]]; then
|
||||
uptime="${days}d ${hours}h"
|
||||
elif [[ $hours -gt 0 ]]; then
|
||||
uptime="${hours}h ${mins}m"
|
||||
else
|
||||
uptime="${mins}m"
|
||||
fi
|
||||
else
|
||||
uptime="n/a"
|
||||
fi
|
||||
|
||||
# Memory and CPU
|
||||
read -r mem_kb cpu_pct < <(ps -p "$pid" -o rss=,pcpu= 2>/dev/null || echo "0 0")
|
||||
if [[ "$mem_kb" -gt 1048576 ]]; then
|
||||
mem="$(( mem_kb / 1048576 )) GB"
|
||||
elif [[ "$mem_kb" -gt 1024 ]]; then
|
||||
mem="$(( mem_kb / 1024 )) MB"
|
||||
else
|
||||
mem="${mem_kb} KB"
|
||||
fi
|
||||
|
||||
# Log size
|
||||
log="$(launcher_log_file)"
|
||||
if [[ -f "$log" ]]; then
|
||||
log_bytes=$(stat -c%s "$log" 2>/dev/null || echo 0)
|
||||
if [[ "$log_bytes" -gt 1048576 ]]; then
|
||||
log_size="$(( log_bytes / 1048576 )) MB"
|
||||
elif [[ "$log_bytes" -gt 1024 ]]; then
|
||||
log_size="$(( log_bytes / 1024 )) KB"
|
||||
else
|
||||
log_size="${log_bytes} B"
|
||||
fi
|
||||
else
|
||||
log_size="-"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e " ${GRN}● Launcher running${RST} PID $pid"
|
||||
echo -e " uptime: $uptime mem: $mem cpu: ${cpu_pct}% log: $log_size"
|
||||
echo ""
|
||||
echo " Agentes:"
|
||||
|
||||
while IFS='|' read -r id _v enabled _d _c; do
|
||||
if [[ "$enabled" == "true" ]]; then
|
||||
echo -e " ${GRN}●${RST} $id (enabled, running in launcher)"
|
||||
else
|
||||
echo -e " ${DIM}○ $id (disabled)${RST}"
|
||||
fi
|
||||
done < <(list_agents_raw)
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# restart.sh — reinicia el launcher unificado
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/server/restart.sh
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
|
||||
info "Deteniendo launcher..."
|
||||
"$REPO_ROOT/dev-scripts/server/stop.sh"
|
||||
|
||||
echo ""
|
||||
info "Iniciando launcher..."
|
||||
"$REPO_ROOT/dev-scripts/server/start.sh"
|
||||
Executable
+139
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env bash
|
||||
# server.sh — gestión unificada del servidor de bots
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/server/server.sh start # iniciar el launcher
|
||||
# ./dev-scripts/server/server.sh stop # detener el launcher
|
||||
# ./dev-scripts/server/server.sh restart # reiniciar el launcher
|
||||
# ./dev-scripts/server/server.sh status # resumen general del servidor
|
||||
# ./dev-scripts/server/server.sh ps # proceso con detalle
|
||||
# ./dev-scripts/server/server.sh logs [lines] # tail -f de logs
|
||||
# ./dev-scripts/server/server.sh kill # SIGKILL forzado (emergencia)
|
||||
# ./dev-scripts/server/server.sh enable <id> # habilitar un agente
|
||||
# ./dev-scripts/server/server.sh disable <id> # deshabilitar un agente
|
||||
# ./dev-scripts/server/server.sh dashboard # TUI interactivo
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
|
||||
CMD="${1:-status}"
|
||||
shift || true
|
||||
ARG="${1:-}"
|
||||
|
||||
toggle_agent_enabled() {
|
||||
local id="$1" value="$2"
|
||||
for cfg in agents/*/config.yaml; do
|
||||
[[ -f "$cfg" ]] || continue
|
||||
local cid
|
||||
cid=$(grep -m1 '^ id:' "$cfg" | awk '{print $2}')
|
||||
if [[ "$cid" == "$id" ]]; then
|
||||
sed -i "s/^\\( enabled:\\).*/\\1 $value/" "$cfg"
|
||||
ok "$id enabled: $value"
|
||||
info "Reinicia el launcher para aplicar: ./dev-scripts/server/server.sh restart"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fail "Agente '$id' no encontrado"
|
||||
}
|
||||
|
||||
case "$CMD" in
|
||||
start)
|
||||
exec "$REPO_ROOT/dev-scripts/server/start.sh"
|
||||
;;
|
||||
|
||||
stop)
|
||||
exec "$REPO_ROOT/dev-scripts/server/stop.sh"
|
||||
;;
|
||||
|
||||
restart)
|
||||
exec "$REPO_ROOT/dev-scripts/server/restart.sh"
|
||||
;;
|
||||
|
||||
ps)
|
||||
exec "$REPO_ROOT/dev-scripts/server/ps.sh"
|
||||
;;
|
||||
|
||||
logs)
|
||||
exec "$REPO_ROOT/dev-scripts/server/logs.sh" ${ARG:+"$ARG"}
|
||||
;;
|
||||
|
||||
dashboard|tui)
|
||||
exec "$REPO_ROOT/dev-scripts/server/dashboard.sh"
|
||||
;;
|
||||
|
||||
enable)
|
||||
[[ -n "$ARG" ]] || fail "Uso: $0 enable <agent-id>"
|
||||
toggle_agent_enabled "$ARG" "true"
|
||||
;;
|
||||
|
||||
disable)
|
||||
[[ -n "$ARG" ]] || fail "Uso: $0 disable <agent-id>"
|
||||
toggle_agent_enabled "$ARG" "false"
|
||||
;;
|
||||
|
||||
kill)
|
||||
if ! is_launcher_running; then
|
||||
dim " El launcher no está corriendo."
|
||||
exit 0
|
||||
fi
|
||||
pid="$(read_launcher_pid)"
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
rm -f "$(launcher_pid_file)"
|
||||
ok "Launcher killed (PID $pid)"
|
||||
;;
|
||||
|
||||
status)
|
||||
echo ""
|
||||
echo -e " ${BLU}Bot Server Status${RST}"
|
||||
printf '%s\n' " $(printf '─%.0s' {1..40})"
|
||||
|
||||
if is_launcher_running; then
|
||||
pid="$(read_launcher_pid)"
|
||||
echo -e " ${GRN}● Launcher running${RST} PID $pid"
|
||||
else
|
||||
echo -e " ${DIM}○ Launcher stopped${RST}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
enabled=0
|
||||
disabled=0
|
||||
total=0
|
||||
while IFS='|' read -r id _v en _d _c; do
|
||||
((total++)) || true
|
||||
if [[ "$en" == "true" ]]; then
|
||||
((enabled++)) || true
|
||||
else
|
||||
((disabled++)) || true
|
||||
fi
|
||||
done < <(list_agents_raw)
|
||||
|
||||
echo -e " Agentes totales: $total"
|
||||
echo -e " ${GRN}● Enabled:${RST} $enabled"
|
||||
echo -e " ${DIM}○ Disabled:${RST} $disabled"
|
||||
echo ""
|
||||
|
||||
"$REPO_ROOT/dev-scripts/agent/list.sh"
|
||||
|
||||
if is_launcher_running; then
|
||||
echo ""
|
||||
"$REPO_ROOT/dev-scripts/server/ps.sh"
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Uso: $0 {start|stop|restart|status|ps|logs|kill|enable|disable|dashboard}"
|
||||
echo ""
|
||||
echo "Comandos:"
|
||||
echo " start Iniciar el launcher unificado"
|
||||
echo " stop Detener el launcher"
|
||||
echo " restart Reiniciar el launcher"
|
||||
echo " status Resumen general del servidor"
|
||||
echo " ps Proceso del launcher con detalle (PID, mem, CPU)"
|
||||
echo " logs [lines] Tail -f de logs del launcher"
|
||||
echo " kill SIGKILL forzado (solo emergencias)"
|
||||
echo " enable <id> Habilitar un agente (requiere restart)"
|
||||
echo " disable <id> Deshabilitar un agente (requiere restart)"
|
||||
echo " dashboard TUI interactivo de gestión"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
# start.sh — inicia el launcher unificado (todos los agentes habilitados)
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/server/start.sh # inicia el launcher unificado
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
load_env
|
||||
|
||||
if is_launcher_running; then
|
||||
pid="$(read_launcher_pid)"
|
||||
fail "El launcher ya está corriendo (PID $pid). Usa restart.sh para reiniciar."
|
||||
fi
|
||||
|
||||
BIN="$REPO_ROOT/bin/launcher"
|
||||
LOG="$(launcher_log_file)"
|
||||
PID_F="$(launcher_pid_file)"
|
||||
|
||||
# Always rebuild — Go is fast and avoids stale binaries from changes in pkg/, agents/, etc.
|
||||
info "Ejecutando tests..."
|
||||
"$GO" test -tags goolm ./... || fail "Tests fallaron — corrige antes de compilar"
|
||||
|
||||
info "Compilando launcher..."
|
||||
mkdir -p "$(dirname "$BIN")"
|
||||
"$GO" build -tags goolm -o "$BIN" ./cmd/launcher || fail "Error de compilación"
|
||||
|
||||
info "Iniciando launcher unificado..."
|
||||
|
||||
nohup "$BIN" --log-level "${LOG_LEVEL:-info}" \
|
||||
>> "$LOG" 2>&1 &
|
||||
|
||||
pid=$!
|
||||
echo "$pid" > "$PID_F"
|
||||
|
||||
sleep 1
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
# Count enabled agents
|
||||
enabled=0
|
||||
total=0
|
||||
while IFS='|' read -r _id _v en _d _c; do
|
||||
((total++)) || true
|
||||
[[ "$en" == "true" ]] && ((enabled++)) || true
|
||||
done < <(list_agents_raw)
|
||||
|
||||
ok "Launcher PID $pid ($enabled/$total agentes habilitados) → logs: $LOG"
|
||||
else
|
||||
rm -f "$PID_F"
|
||||
fail "Launcher arrancó pero murió — revisa: tail -f $LOG"
|
||||
fi
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# stop.sh — detiene el launcher unificado
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/server/stop.sh
|
||||
|
||||
source "$(dirname "$0")/../_common.sh"
|
||||
|
||||
if ! is_launcher_running; then
|
||||
dim " El launcher no está corriendo."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pid="$(read_launcher_pid)"
|
||||
info "Deteniendo launcher (PID $pid)..."
|
||||
|
||||
kill -TERM "$pid" 2>/dev/null || true
|
||||
|
||||
# Wait up to 5s for graceful shutdown
|
||||
for _ in {1..10}; do
|
||||
kill -0 "$pid" 2>/dev/null || break
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
# SIGKILL if still alive
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
warn "Launcher no respondió a SIGTERM, enviando SIGKILL..."
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -f "$(launcher_pid_file)"
|
||||
ok "Launcher detenido"
|
||||
Reference in New Issue
Block a user