#!/usr/bin/env bash # personalize.sh β€” personaliza los 3 archivos de un agente tras el scaffold # # Uso: # ./dev-scripts/agent/personalize.sh [flags] # # Flags: # --description "" descripcion del agente (obligatorio) # --provider proveedor LLM (default: auto-detect) # --model modelo LLM (default: segun provider) # --tone tono (default: friendly) # --prefix "" emoji prefix (default: πŸ€–) # --system-prompt "" system prompt inline # --system-prompt-file system prompt desde archivo # --tool-use habilitar tool_use en config # --language idioma (default: es) # # Genera/actualiza: # agents//config.yaml β€” description, provider, model, tone, prefix, tool-use # agents//agent.go β€” package name correcto y Register ID exacto # agents//prompts/system.md β€” system prompt completo con seccion de seguridad # # Ejemplo (uso standalone): # ./dev-scripts/agent/personalize.sh weather-bot \ # --description "Consulta el tiempo actual y predicciones" \ # --provider anthropic \ # --system-prompt "Eres Weather Bot, especialista en meteorologΓ­a." # # Ejemplo (uso desde create-full.sh): # create-full.sh lo invoca automaticamente si se pasan --description y/o --system-prompt. source "$(dirname "$0")/../_common.sh" load_env SCRIPT_DIR="$(dirname "$0")" need_arg "${1:-}" ID="$1" shift DIR="agents/$ID" [[ ! -d "$DIR" ]] && fail "No existe $DIR β€” ejecuta create-full.sh primero" # ── Defaults ───────────────────────────────────────────────────────────── DESCRIPTION="" PROVIDER="" MODEL="" TONE="friendly" PREFIX="πŸ€–" SYSTEM_PROMPT="" SYSTEM_PROMPT_FILE="" TOOL_USE=false LANGUAGE="es" # ── Parse flags ─────────────────────────────────────────────────────────── while [[ $# -gt 0 ]]; do case "$1" in --description) DESCRIPTION="${2:-}"; shift 2 ;; --description=*) DESCRIPTION="${1#--description=}"; shift ;; --provider) PROVIDER="${2:-}"; shift 2 ;; --provider=*) PROVIDER="${1#--provider=}"; shift ;; --model) MODEL="${2:-}"; shift 2 ;; --model=*) MODEL="${1#--model=}"; shift ;; --tone) TONE="${2:-friendly}"; shift 2 ;; --tone=*) TONE="${1#--tone=}"; shift ;; --prefix) PREFIX="${2:-πŸ€–}"; shift 2 ;; --prefix=*) PREFIX="${1#--prefix=}"; shift ;; --system-prompt) SYSTEM_PROMPT="${2:-}"; shift 2 ;; --system-prompt=*) SYSTEM_PROMPT="${1#--system-prompt=}"; shift ;; --system-prompt-file) SYSTEM_PROMPT_FILE="${2:-}"; shift 2 ;; --system-prompt-file=*) SYSTEM_PROMPT_FILE="${1#--system-prompt-file=}"; shift ;; --tool-use) TOOL_USE=true; shift ;; --language) LANGUAGE="${2:-es}"; shift 2 ;; --language=*) LANGUAGE="${1#--language=}"; shift ;; *) warn "Flag desconocido: $1 (ignorado)"; shift ;; esac done # ── Resolver provider/model ─────────────────────────────────────────────── if [[ -z "$PROVIDER" ]]; then read -r PROVIDER MODEL_DETECTED < <("$SCRIPT_DIR/detect-provider.sh" 2>/dev/null) if [[ -z "$MODEL" ]]; then MODEL="$MODEL_DETECTED" fi else if [[ -z "$MODEL" ]]; then case "$PROVIDER" in anthropic) MODEL="claude-sonnet-4-20250514" ;; claude-code) MODEL="sonnet" ;; *) MODEL="gpt-4o" ;; esac fi fi # Resolver api_key_env segun provider case "$PROVIDER" in anthropic) API_KEY_ENV="ANTHROPIC_API_KEY" ;; claude-code) API_KEY_ENV="" ;; *) API_KEY_ENV="OPENAI_API_KEY" ;; esac # Package name = ID sin guiones, sin sufijo -bot/_bot (ej: monitor-bot β†’ monitor) PACKAGE="$(echo "$ID" | tr '-' '_' | sed 's/_bot$//')" NORM="$(normalize_id "$ID")" DISPLAYNAME="$(python3 -c " import yaml, sys with open('$DIR/config.yaml') as f: cfg = yaml.safe_load(f) print(cfg.get('agent', {}).get('name', '$ID')) " 2>/dev/null || echo "$ID")" info "Personalizando agente: $ID" dim " provider: $PROVIDER / $MODEL" dim " tone: $TONE | prefix: $PREFIX | tool-use: $TOOL_USE" [[ -n "$DESCRIPTION" ]] && dim " description: $DESCRIPTION" echo "" # ═══════════════════════════════════════════════════════════════════════════ # Paso 1 β€” Actualizar config.yaml # ═══════════════════════════════════════════════════════════════════════════ info "Actualizando config.yaml..." TOOL_USE_BOOL="false" $TOOL_USE && TOOL_USE_BOOL="true" python3 - <"$DESCRIPTION"', r'^(\s+tone:\s*).*$': rf'\g<1>$TONE', r'^(\s+language:\s*).*$': rf'\g<1>$LANGUAGE', r'^(\s+prefix:\s*).*$': rf'\g<1>"$PREFIX"', r'^(\s+provider:\s*).*$': rf'\g<1>$PROVIDER', r'^(\s+model:\s*).*$': rf'\g<1>"$MODEL"', } lines = content.splitlines(keepends=True) new_lines = [] # State machine to apply updates in the right sections in_agent = False in_personality = False in_llm_primary = False in_tool_use = False desc_done = False tone_done = False lang_done = False prefix_done = False provider_done = False model_done = False api_key_done = False tool_use_done = False for i, line in enumerate(lines): stripped = line.lstrip() indent = len(line) - len(stripped) key = stripped.split(":")[0].rstrip() if ":" in stripped else "" # Detect section headers if not line.startswith(" ") and not line.startswith("\t"): in_agent = key == "agent" in_personality = key == "personality" in_llm_primary = False in_tool_use = False elif indent == 2: if in_llm_primary or in_tool_use: if key not in ("provider", "model", "api_key_env", "base_url", "max_tokens", "temperature", "claude_code", "enabled", "max_iterations", "parallel_calls"): in_llm_primary = False in_tool_use = False if key == "primary": in_llm_primary = True in_tool_use = False elif key == "tool_use": in_tool_use = True in_llm_primary = False # Apply substitutions if in_agent and indent == 2 and key == "description" and not desc_done and "$DESCRIPTION": new_lines.append(f' description: "$DESCRIPTION"\n') desc_done = True continue if in_personality and indent == 2: if key == "tone" and not tone_done: new_lines.append(f' tone: $TONE\n') tone_done = True continue if key == "language" and not lang_done: new_lines.append(f' language: $LANGUAGE\n') lang_done = True continue if key == "prefix" and not prefix_done: new_lines.append(f' prefix: "$PREFIX"\n') prefix_done = True continue if in_llm_primary and indent == 4: if key == "provider" and not provider_done: new_lines.append(f' provider: $PROVIDER\n') provider_done = True continue if key == "model" and not model_done: new_lines.append(f' model: "$MODEL"\n') model_done = True continue if key == "api_key_env" and not api_key_done and "$API_KEY_ENV": new_lines.append(f' api_key_env: $API_KEY_ENV\n') api_key_done = True continue if in_tool_use and indent == 4: if key == "enabled" and not tool_use_done: new_lines.append(f' enabled: {"true" if _tool_use_enabled else "false"}\n') tool_use_done = True continue new_lines.append(line) with open(config_path, "w") as f: f.writelines(new_lines) print("OK") PYTHON if [[ $? -ne 0 ]]; then fail "Error actualizando config.yaml" fi ok "config.yaml actualizado" # ═══════════════════════════════════════════════════════════════════════════ # Paso 2 β€” Regenerar agent.go # ═══════════════════════════════════════════════════════════════════════════ info "Regenerando agent.go..." cat > "$DIR/agent.go" < "$DIR/prompts/system.md" ok "prompts/system.md generado ($(wc -l < "$DIR/prompts/system.md") lΓ­neas)" # ═══════════════════════════════════════════════════════════════════════════ # Resumen # ═══════════════════════════════════════════════════════════════════════════ echo "" echo -e "${GRN}βœ“ PersonalizaciΓ³n completada para $ID${RST}" dim " agents/$ID/config.yaml" dim " agents/$ID/agent.go (package $PACKAGE)" dim " agents/$ID/prompts/system.md" echo "" echo -e "${YLW}Siguiente paso:${RST} go build -tags goolm ./..." echo ""