diff --git a/README.md b/README.md index ff3a28e..a189298 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,20 @@ Pour l’update, la validation du manifeste accepte : ## Installation +### Installateur interactif + +Le repo inclut un assistant interactif `install.sh` : + +```sh +./install.sh +``` + +Tu peux aussi l’exécuter directement depuis la branche `main` : + +```sh +curl -fsSL https://gitea.lclr.dev/AI/email-mcp/raw/branch/main/install.sh | bash +``` + ### Claude Code CLI Ajoute le serveur MCP en pointant vers le binaire et la sous-commande `mcp` : diff --git a/go.mod b/go.mod index a9b7cc2..07dc546 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module email-mcp go 1.25.0 require ( - gitea.lclr.dev/AI/mcp-framework v1.3.2 + gitea.lclr.dev/AI/mcp-framework v1.4.0-rc1 github.com/emersion/go-imap/v2 v2.0.0-beta.8 github.com/emersion/go-message v0.18.2 github.com/godbus/dbus/v5 v5.2.2 diff --git a/go.sum b/go.sum index 1d58b66..dc8711c 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ gitea.lclr.dev/AI/mcp-framework v1.3.1 h1:GxT5bV22+hbLLUz2IMCscb3qLdkqX5u9d1dbHn gitea.lclr.dev/AI/mcp-framework v1.3.1/go.mod h1:kUVMrL3/UBYgjOsW7sJCs3V0pO0qoJJMpIpueoTsoA4= gitea.lclr.dev/AI/mcp-framework v1.3.2 h1:BCfr77B9cmbSytC4ORPfCGjtgldUotbmzvdBQhKHF9o= gitea.lclr.dev/AI/mcp-framework v1.3.2/go.mod h1:kUVMrL3/UBYgjOsW7sJCs3V0pO0qoJJMpIpueoTsoA4= +gitea.lclr.dev/AI/mcp-framework v1.4.0-rc1 h1:zwggfzUBlhgQEh999YUfEe3pk8VXoL+8Ht+8tIoVfNY= +gitea.lclr.dev/AI/mcp-framework v1.4.0-rc1/go.mod h1:kUVMrL3/UBYgjOsW7sJCs3V0pO0qoJJMpIpueoTsoA4= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..3dc7699 --- /dev/null +++ b/install.sh @@ -0,0 +1,282 @@ +#!/usr/bin/env bash +set -euo pipefail + +BINARY_NAME="email-mcp" +DEFAULT_PROFILE="default" +PROFILE_ENV="EMAIL_MCP_PROFILE" +RELEASE_BASE_URL="https://gitea.lclr.dev" +RELEASE_REPOSITORY="AI/email-mcp" + +prompt() { + local label="$1" + local default_value="$2" + local answer="" + + if [ -n "$default_value" ]; then + printf "%s [%s]: " "$label" "$default_value" + else + printf "%s: " "$label" + fi + + if [ -r /dev/tty ]; then + IFS= read -r answer < /dev/tty || answer="" + else + IFS= read -r answer || answer="" + fi + + if [ -z "$answer" ]; then + printf "%s" "$default_value" + return + fi + + printf "%s" "$answer" +} + +detect_os() { + case "$(uname -s | tr '[:upper:]' '[:lower:]')" in + linux) printf "linux" ;; + darwin) printf "darwin" ;; + *) + printf "OS non supporté: %s\n" "$(uname -s)" >&2 + exit 1 + ;; + esac +} + +detect_arch() { + case "$(uname -m)" in + x86_64|amd64) printf "amd64" ;; + aarch64|arm64) printf "arm64" ;; + *) + printf "Architecture non supportée: %s\n" "$(uname -m)" >&2 + exit 1 + ;; + esac +} + +release_api_url() { + printf "%s/api/v1/repos/%s/releases/latest\n" "${RELEASE_BASE_URL%/}" "$RELEASE_REPOSITORY" +} + +extract_asset_url() { + local json="$1" + local asset_name="$2" + + printf "%s\n" "$json" \ + | grep -o '"browser_download_url":"[^"]*"' \ + | sed 's/"browser_download_url":"//;s/"$//' \ + | sed 's#\\/#/#g' \ + | grep "/${asset_name}$" \ + | head -n 1 +} + +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" +} + +verify_checksum() { + local binary_path="$1" + local checksum_path="$2" + + if command -v sha256sum >/dev/null 2>&1; then + ( cd "$(dirname "$binary_path")" && sha256sum -c "$(basename "$checksum_path")" ) + return + fi + + if command -v shasum >/dev/null 2>&1; then + local expected + local actual + expected="$(awk '{print $1}' "$checksum_path")" + actual="$(shasum -a 256 "$binary_path" | awk '{print $1}')" + if [ "$expected" != "$actual" ]; then + printf "Checksum invalide pour %s\n" "$binary_path" >&2 + exit 1 + fi + return + fi + + printf "Aucun utilitaire de checksum trouvé (sha256sum/shasum), vérification ignorée.\n" >&2 +} + +download_latest_release_binary() { + if ! command -v curl >/dev/null 2>&1; then + printf "curl est requis pour télécharger la release.\n" >&2 + exit 1 + fi + + local os_name + local arch_name + local ext="" + os_name="$(detect_os)" + arch_name="$(detect_arch)" + if [ "$os_name" = "windows" ]; then + ext=".exe" + fi + + local asset_name + local checksum_name + asset_name="${BINARY_NAME}-${os_name}-${arch_name}${ext}" + checksum_name="${asset_name}.sha256" + + printf "Récupération de la dernière release...\n" + local release_json + release_json="$(curl -fsSL "$(release_api_url)")" + + local asset_url + asset_url="$(extract_asset_url "$release_json" "$asset_name")" + if [ -z "$asset_url" ]; then + printf "Asset introuvable dans la dernière release: %s\n" "$asset_name" >&2 + exit 1 + fi + + local checksum_url + checksum_url="$(extract_asset_url "$release_json" "$checksum_name" || true)" + + local tmp_dir + tmp_dir="$(mktemp -d)" + trap 'rm -rf "$tmp_dir"' EXIT INT TERM + + curl -fsSL "$asset_url" -o "$tmp_dir/$asset_name" + chmod +x "$tmp_dir/$asset_name" + + if [ -n "$checksum_url" ]; then + curl -fsSL "$checksum_url" -o "$tmp_dir/$checksum_name" + verify_checksum "$tmp_dir/$asset_name" "$tmp_dir/$checksum_name" + fi + + local target_dir + target_dir="$(prompt "Répertoire d'installation" "$HOME/.local/bin")" + mkdir -p "$target_dir" + install -m 0755 "$tmp_dir/$asset_name" "$target_dir/$BINARY_NAME" + + printf "Binaire installé: %s/%s\n" "$target_dir" "$BINARY_NAME" + printf "Ajoute ce dossier au PATH si nécessaire.\n" +} + +install_binary() { + if command -v "$BINARY_NAME" >/dev/null 2>&1; then + printf "Binaire détecté: %s\n" "$(command -v "$BINARY_NAME")" + local reinstall + reinstall="$(prompt "Forcer une réinstallation depuis la dernière release ? (y/N)" "N")" + case "$reinstall" in + y|Y|yes|YES) + download_latest_release_binary + ;; + *) + return + ;; + esac + return + fi + + download_latest_release_binary +} + +run_setup_wizard() { + install_binary + + local profile + profile="$(prompt "Profil à configurer (${PROFILE_ENV})" "$DEFAULT_PROFILE")" + local binary_path + binary_path="$(resolve_binary_path)" + + printf "Lancement de %s setup...\n\n" "$BINARY_NAME" + 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 +} + +print_mcp_json() { + local profile + profile="$(prompt "Profil à exposer dans la config MCP (${PROFILE_ENV})" "$DEFAULT_PROFILE")" + local default_command + default_command="$(resolve_binary_path)" + local command_path + command_path="$(prompt "Commande du serveur MCP" "$default_command")" + + cat <&2 + ;; + esac + done +} + +main "$@"