#!/usr/bin/env bash set -euo pipefail BINARY_NAME="email-mcp" MODULE_PATH="email-mcp" DEFAULT_PROFILE="default" PROFILE_ENV="EMAIL_MCP_PROFILE" if [ -t 2 ] && [ -z "${NO_COLOR:-}" ]; then C_RESET="$(printf '\033[0m')" C_BOLD="$(printf '\033[1m')" C_DIM="$(printf '\033[2m')" C_RED="$(printf '\033[31m')" C_GREEN="$(printf '\033[32m')" C_YELLOW="$(printf '\033[33m')" C_BLUE="$(printf '\033[34m')" C_MAGENTA="$(printf '\033[35m')" C_CYAN="$(printf '\033[36m')" else C_RESET="" C_BOLD="" C_DIM="" C_RED="" C_GREEN="" C_YELLOW="" C_BLUE="" C_MAGENTA="" C_CYAN="" fi ui_line() { printf "%b%s%b\n" "$C_DIM" "------------------------------------------------------------" "$C_RESET" >&2 } ui_title() { printf "\n%b%s%b\n" "$C_BOLD$C_CYAN" "$1" "$C_RESET" >&2 } ui_info() { printf "%b[info]%b %s\n" "$C_BLUE" "$C_RESET" "$1" >&2 } ui_success() { printf "%b[ok]%b %s\n" "$C_GREEN" "$C_RESET" "$1" >&2 } ui_warn() { printf "%b[warn]%b %s\n" "$C_YELLOW" "$C_RESET" "$1" >&2 } ui_error() { printf "%b[error]%b %s\n" "$C_RED" "$C_RESET" "$1" >&2 } prompt() { local label="$1" local default_value="$2" local answer="" if [ -n "$default_value" ]; then printf "%b%s%b [%s]: " "$C_BOLD" "$label" "$C_RESET" "$default_value" >&2 else printf "%b%s%b: " "$C_BOLD" "$label" "$C_RESET" >&2 fi if [ -t 2 ] && [ -r /dev/tty ]; then if ! IFS= read -r answer < /dev/tty 2>/dev/null; then IFS= read -r answer || answer="" fi else IFS= read -r answer || answer="" fi if [ -z "$answer" ]; then printf "%s" "$default_value" return fi printf "%s" "$answer" } sanitize_server_name() { local raw="$1" local sanitized sanitized="$(printf "%s" "$raw" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_-]/-/g; s/--*/-/g; s/^-*//; s/-*$//')" if [ -z "$sanitized" ]; then sanitized="$BINARY_NAME" fi printf "%s" "$sanitized" } toml_escape() { printf "%s" "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' } go_bin_dir() { local gobin gobin="$(go env GOBIN 2>/dev/null || true)" if [ -n "$gobin" ]; then printf "%s\n" "$gobin" return fi go env GOPATH 2>/dev/null | awk '{print $1 "/bin"}' } resolve_binary_path() { if command -v "$BINARY_NAME" >/dev/null 2>&1; then command -v "$BINARY_NAME" return fi if command -v go >/dev/null 2>&1; then local bin_dir bin_dir="$(go_bin_dir)" if [ -n "$bin_dir" ] && [ -x "$bin_dir/$BINARY_NAME" ]; then printf "%s\n" "$bin_dir/$BINARY_NAME" return fi fi printf "%s\n" "$HOME/.local/bin/$BINARY_NAME" } ensure_cli() { local cli_name="$1" if command -v "$cli_name" >/dev/null 2>&1; then return fi ui_error "Commande introuvable: $cli_name" exit 1 } install_binary() { if command -v "$BINARY_NAME" >/dev/null 2>&1; then ui_success "Binaire detecte: $(command -v "$BINARY_NAME")" local reinstall reinstall="$(prompt "Reinstaller via go install ? (y/N)" "N")" case "$reinstall" in y|Y|yes|YES) ;; *) return ;; esac fi if ! command -v go >/dev/null 2>&1; then ui_error "Go n'est pas installe. Installe Go ou choisis une configuration manuelle." exit 1 fi ui_info "Installation du binaire via go install..." go install "${MODULE_PATH}/cmd/${BINARY_NAME}@latest" local bin_dir bin_dir="$(go_bin_dir)" if [ -n "$bin_dir" ]; then ui_success "Binaire installe dans $bin_dir" ui_info "Ajoute ce dossier au PATH si necessaire." fi } run_setup_wizard() { install_binary local profile profile="$(prompt "Profil a configurer (${PROFILE_ENV})" "$DEFAULT_PROFILE")" local binary_path binary_path="$(resolve_binary_path)" ui_info "Lancement de $BINARY_NAME setup" if [ -r /dev/tty ] && [ -w /dev/tty ]; then env "${PROFILE_ENV}=${profile}" "$binary_path" setup < /dev/tty > /dev/tty else env "${PROFILE_ENV}=${profile}" "$binary_path" setup fi ui_success "Setup termine pour le profil \"$profile\"." } collect_server_inputs() { local default_name default_name="$(sanitize_server_name "$BINARY_NAME")" SERVER_NAME="$(prompt "Nom du serveur MCP" "$default_name")" SERVER_NAME="$(sanitize_server_name "$SERVER_NAME")" PROFILE_VALUE="$(prompt "Valeur de ${PROFILE_ENV}" "$DEFAULT_PROFILE")" local default_command default_command="$(resolve_binary_path)" COMMAND_PATH="$(prompt "Chemin du binaire serveur MCP" "$default_command")" } choose_scope() { local selected while true; do ui_title "Scope de configuration" printf " 1) global (user)\n" >&2 printf " 2) project (projet courant)\n" >&2 selected="$(prompt "Choix" "1")" case "$selected" in 1) printf "global" return ;; 2) printf "project" return ;; *) ui_warn "Choix invalide: $selected" ;; esac done } apply_claude_mcp() { ensure_cli "claude" collect_server_inputs local scope_choice scope_choice="$(choose_scope)" local claude_scope if [ "$scope_choice" = "global" ]; then claude_scope="user" else claude_scope="project" fi ui_info "Application de la configuration Claude ($claude_scope)..." claude mcp remove --scope "$claude_scope" "$SERVER_NAME" >/dev/null 2>&1 || true claude mcp add \ --transport stdio \ --scope "$claude_scope" \ -e "${PROFILE_ENV}=${PROFILE_VALUE}" \ "$SERVER_NAME" -- "$COMMAND_PATH" mcp ui_success "Serveur \"$SERVER_NAME\" configure dans Claude ($claude_scope)." } rewrite_codex_project_config() { local project_dir="$1" local config_file="$project_dir/.codex/config.toml" local section_prefix="[mcp_servers.${SERVER_NAME}" mkdir -p "$project_dir/.codex" touch "$config_file" local tmp_file tmp_file="$(mktemp)" awk -v prefix="$section_prefix" ' function is_target(line, p, next_char) { if (index(line, p) != 1) { return 0 } next_char = substr(line, length(p) + 1, 1) return next_char == "]" || next_char == "." } /^\[.*\]$/ { if (is_target($0, prefix)) { skip = 1 next } if (skip == 1) { skip = 0 } } { if (skip != 1) { print $0 } } ' "$config_file" > "$tmp_file" mv "$tmp_file" "$config_file" { printf "\n[mcp_servers.%s]\n" "$SERVER_NAME" printf "command = \"%s\"\n" "$(toml_escape "$COMMAND_PATH")" printf "args = [\"mcp\"]\n\n" printf "[mcp_servers.%s.env]\n" "$SERVER_NAME" printf "%s = \"%s\"\n" "$PROFILE_ENV" "$(toml_escape "$PROFILE_VALUE")" } >> "$config_file" } apply_codex_mcp() { ensure_cli "codex" collect_server_inputs local scope_choice scope_choice="$(choose_scope)" if [ "$scope_choice" = "global" ]; then ui_info "Application via codex mcp add (scope global)..." codex mcp remove "$SERVER_NAME" >/dev/null 2>&1 || true codex mcp add \ "$SERVER_NAME" \ --env "${PROFILE_ENV}=${PROFILE_VALUE}" \ -- "$COMMAND_PATH" mcp ui_success "Serveur \"$SERVER_NAME\" configure dans le scope global Codex." return fi local default_project_dir default_project_dir="$(pwd)" local project_dir project_dir="$(prompt "Dossier projet cible pour .codex/config.toml" "$default_project_dir")" rewrite_codex_project_config "$project_dir" ui_success "Configuration projet ecrite dans $project_dir/.codex/config.toml" } print_mcp_json() { collect_server_inputs cat <&2 printf "%bFramework module:%b %s\n" "$C_DIM" "$C_RESET" "$MODULE_PATH" >&2 ui_line printf "Choisis une action:\n" >&2 printf " 1) Installer/mettre a jour le binaire + setup\n" >&2 printf " 2) Configurer Claude Code (apply direct)\n" >&2 printf " 3) Configurer Codex (apply direct)\n" >&2 printf " 4) Generer JSON MCP manuel\n" >&2 printf " 5) Quitter\n" >&2 } main() { while true; do print_header local choice choice="$(prompt "Choix" "1")" printf "\n" >&2 case "$choice" in 1) run_setup_wizard return ;; 2) apply_claude_mcp return ;; 3) apply_codex_mcp return ;; 4) ui_info "JSON MCP genere sur stdout." print_mcp_json return ;; 5) ui_warn "Annule." return ;; *) ui_warn "Choix invalide: $choice" ;; esac done } main "$@"