refactor: reorganizar dev-scripts en subdirectorios server/ y agent/
Se separan los scripts de gestión en dos categorías claras: - dev-scripts/server/ — operaciones del launcher (start, stop, restart, ps, logs, dashboard) - dev-scripts/agent/ — operaciones de agentes (new, register, verify, avatar, remove, list) Se añade create-full.sh como script unificado que ejecuta scaffold + build + register + verify. Se incluyen READMEs en cada subdirectorio documentando los scripts disponibles. Los scripts originales en la raíz de dev-scripts/ se eliminan. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# avatar.sh — sube una imagen y la establece como avatar de un bot.
|
||||
# También sincroniza el displayname desde el config (agent.name).
|
||||
#
|
||||
# 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"
|
||||
|
||||
info "Sincronizando displayname desde config..."
|
||||
$CTL displayname "$AGENT_ID"
|
||||
|
||||
ok "Perfil 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
+396
@@ -0,0 +1,396 @@
|
||||
#!/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 (basado en el assistant como plantilla)
|
||||
# agents/<agent-id>/agent.go (reglas puras vacías, listo para extender)
|
||||
# agents/<agent-id>/prompts/ (directorio para system prompt)
|
||||
# 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"
|
||||
|
||||
[[ -d "$DIR" ]] && fail "Ya existe agents/$ID — ¿ya fue creado?"
|
||||
|
||||
info "Creando scaffold para $ID..."
|
||||
|
||||
mkdir -p "$DIR/prompts" "$DIR/data"
|
||||
|
||||
# ── config.yaml ────────────────────────────────────────────────────────────
|
||||
cat > "$DIR/config.yaml" <<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
|
||||
|
||||
# ============================================
|
||||
# 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
|
||||
|
||||
# ── agent.go ───────────────────────────────────────────────────────────────
|
||||
cat > "$DIR/agent.go" <<GO
|
||||
// Package $PACKAGE defines the pure rules for the $DISPLAYNAME.
|
||||
package $PACKAGE
|
||||
|
||||
import "github.com/enmanuel/agents/pkg/decision"
|
||||
|
||||
// Rules returns the decision rules for the $ID.
|
||||
func Rules() []decision.Rule {
|
||||
return []decision.Rule{
|
||||
{
|
||||
Name: "help",
|
||||
Match: decision.MatchCommand("help"),
|
||||
Actions: []decision.Action{{
|
||||
Kind: decision.ActionKindReply,
|
||||
Reply: &decision.ReplyAction{
|
||||
Content: "Soy $DISPLAYNAME. Escríbeme lo que necesitas.",
|
||||
},
|
||||
}},
|
||||
},
|
||||
// Catch-all: DMs y menciones van al LLM
|
||||
{
|
||||
Name: "llm-fallback",
|
||||
Match: func(ctx decision.MessageContext) bool {
|
||||
return ctx.IsDirectMsg || ctx.IsMention
|
||||
},
|
||||
Actions: []decision.Action{{
|
||||
Kind: decision.ActionKindLLM,
|
||||
LLM: &decision.LLMAction{},
|
||||
}},
|
||||
},
|
||||
}
|
||||
}
|
||||
GO
|
||||
|
||||
# ── system prompt ──────────────────────────────────────────────────────────
|
||||
cat > "$DIR/prompts/system.md" <<MD
|
||||
# $DISPLAYNAME — System Prompt
|
||||
|
||||
Eres $DISPLAYNAME. Describe aquí el rol, capacidades y restricciones del agente.
|
||||
|
||||
## Rol
|
||||
...
|
||||
|
||||
## Capacidades
|
||||
...
|
||||
|
||||
## Restricciones
|
||||
...
|
||||
MD
|
||||
|
||||
ok "Scaffold creado en $DIR/"
|
||||
echo ""
|
||||
|
||||
# ── Actualizar cmd/launcher/main.go ───────────────────────────────────────
|
||||
LAUNCHER="cmd/launcher/main.go"
|
||||
|
||||
if grep -q "\"$ID\":" "$LAUNCHER" 2>/dev/null; then
|
||||
warn "$ID ya está en rulesRegistry de $LAUNCHER — saltando"
|
||||
else
|
||||
TAB=$'\t'
|
||||
IMPORT_LINE="${TAB}${PACKAGE}agent \"github.com/enmanuel/agents/agents/$ID\""
|
||||
REGISTRY_LINE="${TAB}\"$ID\": ${PACKAGE}agent.Rules,"
|
||||
|
||||
# Insertar import después del último import agents/agents/*
|
||||
if awk -v new_import="$IMPORT_LINE" '
|
||||
{
|
||||
lines[NR] = $0
|
||||
if ($0 ~ /[a-z_]+agent "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 "Import añadido en $LAUNCHER"
|
||||
else
|
||||
warn "No se pudo insertar el import automáticamente — añádelo manualmente:"
|
||||
echo -e " ${GRN}${IMPORT_LINE}${RST}"
|
||||
fi
|
||||
|
||||
# Insertar entry en rulesRegistry antes del cierre }
|
||||
if awk -v new_entry="$REGISTRY_LINE" '
|
||||
/^var rulesRegistry/ { in_reg = 1 }
|
||||
in_reg && /^\}/ { print new_entry; in_reg = 0 }
|
||||
{ print }
|
||||
' "$LAUNCHER" > /tmp/_launcher_tmp; then
|
||||
mv /tmp/_launcher_tmp "$LAUNCHER"
|
||||
ok "Registry entry añadida en $LAUNCHER"
|
||||
else
|
||||
warn "No se pudo insertar el registry entry — añádelo manualmente:"
|
||||
echo -e " ${GRN}${REGISTRY_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"
|
||||
Reference in New Issue
Block a user