#!/bin/sh # Installs the eml CLI: curl -fsSL https://eml.dev/install.sh | sh # # EML_VERSION=0.1.0 install that version instead of the latest # EML_INSTALL_DIR=... where to put the binary (default: ~/.local/bin) # EML_NO_MODIFY_PATH=1 leave shell startup files alone # NO_COLOR=1 plain output set -eu base="https://eml.dev/cli" dir="${EML_INSTALL_DIR:-$HOME/.local/bin}" data="${XDG_DATA_HOME:-$HOME/.local/share}/eml" # --- Styling ----------------------------------------------------------------- # Colors and animation only on a terminal. stdout is still the terminal under # `curl | sh`; CI and pipes get plain text. if [ -t 1 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-dumb}" != dumb ]; then fancy=1 else fancy= fi esc="$(printf '\033')" if [ -n "$fancy" ]; then reset="$esc[0m" bold="$esc[1m" dim="$esc[2m" case "${COLORTERM:-}" in truecolor | 24bit) c1="$esc[38;2;217;70;239m" c2="$esc[38;2;236;72;153m" c3="$esc[38;2;239;68;68m" c4="$esc[38;2;249;115;22m" c5="$esc[38;2;251;191;36m" ;; *) c1="$esc[38;5;171m" c2="$esc[38;5;205m" c3="$esc[38;5;203m" c4="$esc[38;5;209m" c5="$esc[38;5;214m" ;; esac green="$esc[32m" red="$esc[31m" accent="$c1" else reset= bold= dim= c1= c2= c3= c4= c5= green= red= accent= fi pause() { [ -n "$fancy" ] && sleep "$1" 2>/dev/null || true; } banner() { printf '\n' for row in \ "$c1 ███████ ███ ███ ██" \ "$c2 ██ ████ ████ ██" \ "$c3 █████ ██ ████ ██ ██" \ "$c4 ██ ██ ██ ██ ██" \ "$c5 ███████ ██ ██ ███████"; do printf ' %s%s\n' "$row" "$reset" pause 0.05 done printf '\n %sTransactional email for people and agents. Hosted in Germany.%s\n\n' "$dim" "$reset" pause 0.15 } ok() { printf ' %s✓%s %s\n' "$green" "$reset" "$*"; } note() { printf ' %s·%s %s\n' "$dim" "$reset" "$*"; } fail() { printf ' %s✗ %s%s\n' "$red" "$*" "$reset" >&2; exit 1; } # step "Doing" "Done" cmd...: a spinner beside "Doing…" while cmd runs, then ✓ Done. step() { doing="$1" done="$2"; shift 2 if [ -z "$fancy" ]; then "$@" || return 1 ok "$done" return fi "$@" & pid=$! printf '%s[?25l' "$esc" i=0 while kill -0 "$pid" 2>/dev/null; do case $i in 0) f='⠋' ;; 1) f='⠙' ;; 2) f='⠹' ;; 3) f='⠸' ;; 4) f='⠼' ;; 5) f='⠴' ;; 6) f='⠦' ;; 7) f='⠧' ;; 8) f='⠇' ;; *) f='⠏' ;; esac printf '\r %s%s%s %s…' "$accent" "$f" "$reset" "$doing" i=$(((i + 1) % 10)) sleep 0.08 2>/dev/null || sleep 1 done printf '\r%s[2K%s[?25h' "$esc" "$esc" wait "$pid" || return 1 ok "$done" } show_cursor() { [ -n "$fancy" ] && printf '%s[?25h' "$esc" || true; } trap 'show_cursor; exit 130' INT TERM tilde() { case "$1" in "$HOME"/*) printf '~%s' "${1#"$HOME"}" ;; *) printf '%s' "$1" ;; esac; } # The same path as a shell expression for startup files: $HOME/... when possible. home_expr() { case "$1" in "$HOME"/*) printf '$HOME%s' "${1#"$HOME"}" ;; *) printf '%s' "$1" ;; esac; } # --- Platform ---------------------------------------------------------------- banner case "$(uname -s)" in Linux) os=unknown-linux-musl os_name=Linux ;; Darwin) os=apple-darwin os_name=macOS ;; *) fail "Unsupported OS $(uname -s). Build from source with cargo instead." ;; esac case "$(uname -m)" in x86_64 | amd64) arch=x86_64 ;; arm64 | aarch64) arch=aarch64 ;; *) fail "Unsupported CPU $(uname -m)." ;; esac target="$arch-$os" # The shell `curl | sh` was typed into (this script's parent), else the login # shell. $SHELL alone is wrong for people who run fish over a bash login shell. parent="$(ps -o comm= -p "$PPID" 2>/dev/null || true)" parent="$(basename "${parent#-}")" case "$parent" in bash | zsh | fish) shell="$parent" ;; *) shell="$(basename "${SHELL:-sh}")" ;; esac case "$shell" in bash | zsh | fish) ;; *) shell= ;; esac ok "Detected $os_name $arch${shell:+, $shell}" command -v curl >/dev/null || fail "curl is required." if command -v sha256sum >/dev/null; then sha="sha256sum"; else sha="shasum -a 256"; fi # --- Download and verify ----------------------------------------------------- version="${EML_VERSION:-$(curl -fsSL "$base/latest/VERSION")}" || fail "Could not reach $base." version="${version#v}" file="eml-$target.tar.gz" tmp="$(mktemp -d)" trap 'rm -rf "$tmp"; show_cursor' EXIT step "Downloading eml $version" "Downloaded eml $version" curl -fsL -o "$tmp/$file" "$base/v$version/$file" \ || fail "No build of eml $version for $target." curl -fsSL -o "$tmp/SHA256SUMS" "$base/v$version/SHA256SUMS" || fail "Could not download checksums." (cd "$tmp" && grep " $file\$" SHA256SUMS | $sha -c - >/dev/null 2>&1) || fail "Checksum mismatch for $file." ok "Verified checksum" tar -xzf "$tmp/$file" -C "$tmp" mkdir -p "$dir" install -m 0755 "$tmp/eml" "$dir/eml" ok "Installed $(tilde "$dir/eml")" # --- Shell setup ------------------------------------------------------------- # Appends a marked block to a startup file once. add_block() { rc="$1" body="$2" if [ -f "$rc" ] && grep -q '# added by the eml installer' "$rc"; then return 1 fi mkdir -p "$(dirname "$rc")" printf '\n# added by the eml installer\n%s\n' "$body" >>"$rc" } on_path= case ":$PATH:" in *":$dir:"*) on_path=1 ;; esac path_dir="$(tilde "$dir")" path_expr="$(home_expr "$dir")" data_expr="$(home_expr "$data")" if [ -n "$shell" ] && [ -z "${EML_NO_MODIFY_PATH:-}" ]; then case "$shell" in bash) mkdir -p "$data" "$dir/eml" completions bash >"$data/eml.bash" rc="$HOME/.bashrc" [ "$os" = apple-darwin ] && rc="$HOME/.bash_profile" block="[ -f \"$data_expr/eml.bash\" ] && . \"$data_expr/eml.bash\"" [ -z "$on_path" ] && block="export PATH=\"$path_expr:\$PATH\" $block" ;; zsh) mkdir -p "$data" "$dir/eml" completions zsh >"$data/_eml" rc="${ZDOTDIR:-$HOME}/.zshrc" # Sourcing needs compinit to have run, which it has in any zshrc with completions. block="(( \$+functions[compdef] )) && source \"$data_expr/_eml\"" [ -z "$on_path" ] && block="export PATH=\"$path_expr:\$PATH\" $block" ;; fish) fish_dir="${XDG_CONFIG_HOME:-$HOME/.config}/fish" mkdir -p "$fish_dir/completions" "$dir/eml" completions fish >"$fish_dir/completions/eml.fish" rc="$fish_dir/conf.d/eml.fish" block="fish_add_path -g \"$path_expr\"" ;; esac if add_block "$rc" "$block"; then ok "Set up $shell ${on_path:+completions}${on_path:-PATH and completions} in $(tilde "$rc")" else ok "Updated $shell completions" fi [ -z "$on_path" ] && restart=1 || restart= elif [ -z "$on_path" ]; then note "Add $path_dir to your PATH: export PATH=\"$path_expr:\$PATH\"" restart= fi # --- Done -------------------------------------------------------------------- printf '\n %s%seml %s is ready.%s' "$bold" "$accent" "$version" "$reset" [ -n "${restart:-}" ] && printf ' %sOpen a new terminal, or run: exec %s%s' "$dim" "$shell" "$reset" printf '\n\n' pause 0.1 printf ' %seml login%s %sconnect an API key%s\n' "$bold" "$reset" "$dim" "$reset" printf ' %seml doctor%s %scheck domains, DNS and SMTP%s\n' "$bold" "$reset" "$dim" "$reset" printf ' %seml guide%s %sthe guide for coding agents%s\n\n' "$bold" "$reset" "$dim" "$reset"