#!/usr/bin/env bash set -e set -u CONFIG_DIR="$HOME/.hammerspoon" SPOONS_DIR="$CONFIG_DIR/Spoons" LICENSE_DIR="$HOME/.config/BetterDictation" LICENSE_FILE="$LICENSE_DIR/license.key" REPO_DIR="$SPOONS_DIR/BetterDictation.spoon" TMP_DIR=$(mktemp -d) SSH_KEY="$TMP_DIR/update_key" DOMAIN="https://betterdictation.com" # ## HELPER FUNCTIONS # ask() { print_question "$1" read -r } print_question() { printf "$(tput setaf 3) [?] $1$(tput sgr0)" } get_answer() { printf "%s" "$REPLY" } dotheader() { echo dotsay "@b@green[[$1]]" echo } dotsay() { local result=$(_colorized $@) echo "$result" } saysuccess() { printf " " dotsay "✅ @blue[[$1]]" } sayinfo() { printf " " dotsay "+ @yellow[[$1]]" } indent() { sed 's/^/ /' } _colorized() { echo "$@" | sed -E \ -e 's/((@(red|green|yellow|blue|magenta|cyan|white|reset|b|u))+)[[]{2}(.*)[]]{2}/\1\4@reset/g' \ -e "s/@red/$(tput setaf 1)/g" \ -e "s/@green/$(tput setaf 2)/g" \ -e "s/@yellow/$(tput setaf 3)/g" \ -e "s/@blue/$(tput setaf 4)/g" \ -e "s/@magenta/$(tput setaf 5)/g" \ -e "s/@cyan/$(tput setaf 6)/g" \ -e "s/@white/$(tput setaf 7)/g" \ -e "s/@reset/$(tput sgr0)/g" \ -e "s/@b/$(tput bold)/g" \ -e "s/@u/$(tput sgr 0 1)/g" } function command_exists() { local name=$1 command -v "$name" >/dev/null 2>&1 } ## END HELPER FUNCTIONS # Check if required commands exist check_dependencies() { local commands=("curl" "git" "afplay") for cmd in "${commands[@]}"; do if ! command_exists "$cmd"; then dotsay "@red[[$cmd is required but not installed. Please install it before continuing.]]" exit 1 fi done if ! command_exists "brew"; then dotsay "@red[[brew required but not installed. Please install them before continuing.]]" echo dotsay "Installation instructions can be found at https://brew.sh" | indent echo exit 1 fi if ! command_exists "ffmpeg"; then dotsay "@red[[ffmpeg required but not installed. Please install them before continuing by running,]]" echo dotsay "brew install ffmpeg" | indent echo exit 1 fi } check_os_architecture() { local os=$(uname -s) local arch=$(uname -m) if [[ "$os" == "Darwin" && "$arch" == "arm64" ]]; then saysuccess "Running on MacOS with Apple Silicon" else dotsay "@red[[BetterDictation is only for Macs running Apple Silicon.]]" exit 1 fi } ensure_license_directory_exists() { if [ ! -d "$LICENSE_DIR" ]; then mkdir -p "$LICENSE_DIR" fi } get_license_key() { # Check if the license file exists if [ -f "$LICENSE_FILE" ]; then # Print the license key (optional) dotsay "License key found: $(cat $LICENSE_FILE)" else # Ask the user for the license key if the file doesn't exist ask "Please enter your license key for Better Dictation: " echo "$(get_answer)" > "$LICENSE_FILE" fi } get_ssh_key() { local license_key local response_code license_key=$(cat $LICENSE_FILE) # Send the curl request and capture the HTTP response code response_code=$(curl -XPOST -s -o "$SSH_KEY" -H "Content-Type: application/json" -w "%{http_code}" -d "{ \"license_key\" : \"$license_key\" }" "$DOMAIN/api/get_update_key") # Check the HTTP response code if [ "$response_code" -ne 200 ]; then dotsay "@red[[Failed to retrieve SSH key from API. Server returned $response_code.]]" exit 1 else chmod 600 "$SSH_KEY" fi } clone_repo() { if [ ! -d "$SPOONS_DIR" ]; then mkdir -p "$SPOONS_DIR" fi rm -rf "$REPO_DIR" if ! GIT_SSH_COMMAND="ssh -i $SSH_KEY" git clone -q git@github.com:thmsmlr/BetterDictation.spoon.git "$REPO_DIR"; then dotsay "@red[[Failed to download software]]" exit 1 else saysuccess "Software successfully downloaded" fi } run_update_script() { if [ -f "$REPO_DIR/update" ]; then chmod +x "$REPO_DIR/update" "$REPO_DIR/update" else dotsay "@red[[Update script not found.]]" exit 1 fi } function install_hammerspoon() { if [ -d "/Applications/Hammerspoon.app" ]; then saysuccess "Hammerspoon is already installed!" else sayinfo "Hammerspoon not found, installing..." brew install --cask hammerspoon | indent fi } function bootstrap_hammerspoon_directory() { if [ ! -d "$CONFIG_DIR" ]; then sayinfo "Creating $CONFIG_DIR directory" mkdir -p $CONFIG_DIR else saysuccess "$CONFIG_DIR exists!" fi } function bootstrap_spoons_dir() { if [ ! -d "$SPOONS_DIR" ]; then sayinfo "Creating $SPOONS_DIR" mkdir -p $SPOONS_DIR sayinfo "Adding a .gitignore file to $SPOONS_DIR for your dotfiles" touch $SPOONS_DIR/.gitignore cat << EOF | tee -a "$SPOONS_DIR/.gitignore" > /dev/null * !.gitignore !SpoonInstall.spoon EOF else saysuccess "$SPOONS_DIR already exists" fi } function bootstrap_spoon_install() { local url="https://github.com/Hammerspoon/Spoons/raw/master/Spoons/SpoonInstall.spoon.zip" local destination=/tmp/SpoonInstall.spoon.zip local spoons_dir=$HOME/.hammerspoon/Spoons if [ ! -d "$SPOONS_DIR/SpoonInstall.spoon" ]; then sayinfo "Installing SpoonInstall.spoon in $SPOONS_DIR" curl -L -o "$destination" "$url" | indent unzip -d $SPOONS_DIR "$destination" | indent else saysuccess "SpoonInstall.spoon already setup" fi } function bootstrap_spoon() { local dir="$SPOONS_DIR/BetterDictation.spoon" local modeldir="$SPOONS_DIR/BetterDictation.spoon/models" if [ -d "$dir" ]; then saysuccess "BetterDictation.spoon is already installed!" else sayinfo "Cloning BetterDictation.spoon from Git" echo clone_repo echo fi sayinfo "Checking out main branch" echo local current_dir=$(pwd) cd $dir && (GIT_SSH_COMMAND="ssh -i $SSH_KEY" git checkout main 2>/dev/null | indent) echo sayinfo "Pulling latest changes" echo cd $dir && (GIT_SSH_COMMAND="ssh -i $SSH_KEY" git pull origin main 2>/dev/null | indent) echo cd $current_dir } function setup_spoon() { local dir="$SPOONS_DIR/BetterDictation.spoon" if [ -f "$dir/setup" ]; then sayinfo "Setting up BetterDictation.spoon" chmod +x "$dir/setup" cd $dir && "./setup" else dotsay "@red[[Setup script not found.]]" exit 1 fi } function bootstrap_init_lua() { local init_file="$CONFIG_DIR/init.lua" if [ ! -f "$init_file" ]; then sayinfo "Setting up $init_file" echo 'hs.loadSpoon("SpoonInstall")' > "$init_file" else saysuccess "$init_file exists!" fi if ! grep -q "BetterDictation" "$init_file"; then sayinfo "Patching $init_file with BetterDictation init code" cat << EOF | tee -a "$init_file" > /dev/null -------------------------------- -- BETTER DICTATION CONFIG -------------------------------- hs.ipc.cliInstall() local dictation = hs.loadSpoon("BetterDictation") -- If you want to customize the whispercpp executable location -- dictation:setwhispercppExecutable("/bin/whispercpp") -- If you want to customize the path to the specific whispercpp model -- dictation:setwhispercppModel("/Users/thomas/code/whipsercpp/models/ggml-small.en.bin") -- If you want we can dim audio by 30% while running dictation -- dictation:setDimAudioBy(0.5) hs.hotkey.bind({"cmd", "ctrl"}, "v", dictation.start) -------------------------------- -- END BETTER DICTATION CONFIG -------------------------------- EOF else saysuccess "BetterDictation init code already exists in $init_file" fi } function restart_hammerspoon() { killall Hammerspoon >/dev/null 2>&1 || true open /Applications/Hammerspoon.app } function open_microphone_preferences() { echo dotsay "In order for BetterDictation to work, we'll need to give Hammerspoon access to your microphone." | indent dotsay "Shortly, we'll open the microphone privacy pane in System Preferences. Please add Hammerspoon to the list of apps that can access your microphone." | indent echo # Count down 3 seconds with the message for i in {3..1}; do echo -ne "Opening System Preferences in $i...\r" | indent sleep 1 done # Clear the last line before proceeding echo -e "\033[K" open "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone" # Ask user if they've enabled ask "Once you've enabled Hammerspoon in the microphone privacy settings, press any key to continue" echo "$(get_answer)" > /dev/null echo -e "\033[K" saysuccess "Microphone enabled!" } function open_accessibility_preferences() { echo dotsay "Next up, the accessibility settings" | indent dotsay "Shortly, we'll open the accessibility privacy pane in System Preferences. Please add Hammerspoon to the list of apps that can access your accessibility settings." | indent echo # Count down 3 seconds with the message for i in {3..1}; do echo -ne "Opening System Preferences in $i...\r" | indent sleep 1 done # Clear the last line before proceeding echo -e "\033[K" open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" # Ask user if they've enabled ask "Once you've enabled Hammerspoon in the accessibility settings, press any key to continue" echo "$(get_answer)" > /dev/null echo -e "\033[K" saysuccess "Accessibility enabled!" } dotheader "Better Dictation Installer" dotheader "Checking for dependencies..." check_os_architecture check_dependencies dotheader "Verifying License key..." ensure_license_directory_exists get_license_key get_ssh_key dotheader "Checking for Hammerspoon..." install_hammerspoon dotheader "Setting up Hammerspoon config directory..." bootstrap_hammerspoon_directory bootstrap_spoons_dir dotheader "Adding SpoonInstall.spoon if necessary for package management" bootstrap_spoon_install dotheader "Downloading BetterDictation.spoon" bootstrap_spoon setup_spoon dotheader "Setting up ~/.hammerspoon/init.lua" bootstrap_init_lua dotheader "Restarting Hammerspoon" restart_hammerspoon dotheader "Ensuring application has correct permissions" # sayinfo "Microphone Permissions" # open_microphone_preferences # echo sayinfo "Enable Accessibility" open_accessibility_preferences echo saysuccess "Install complete" echo dotsay "💁‍♂️ @b@yellow[[Important post-install info]]" echo dotsay "- To change your configuration settings, edit @b@blue[[~/.hammerspoon/init.lua]]" echo dotsay "Opening $DOMAIN for usage instructions" open "$DOMAIN/install#usage"