Skip to content

Instantly share code, notes, and snippets.

@kigster
Last active July 24, 2025 01:02
Show Gist options
  • Save kigster/4d74df02358c86c0b9eecf1120fb6450 to your computer and use it in GitHub Desktop.
Save kigster/4d74df02358c86c0b9eecf1120fb6450 to your computer and use it in GitHub Desktop.
BASH script that downloads and installs Claude Mac App & the CLI client via npm
#!/usr/bin/env bash
# shellcheck disable=SC2154,SC1091,SC1090
# vim: ft=bash
#
# AUTHOR:
# © 2025 Konstantin Gredeskoul, All rights reserved.
# LICENSE:
# MIT
# USAGE:
# Download the script to a local file called "install-claude-app-cli" and then:
# bash install-claude-app-cli
declare dmg url mnt
export dmg=Claude.dmg
export url="https://storage.googleapis.com/osprey-downloads-c02f6a0d-347c-492b-a752-3e0651722e97/nest/$dmg"
export mnt="/Volumes/Claude"
# shellcheck disable=SC2064
trap "rm -f ${dmg}" EXIT
if [[ ! -d "${HOME}/.bashmatic" ]] ; then
bash -c "$(curl -fsSL https://bashmatic.re1.re); bashmatic-install -q"
fi
[[ -f "${HOME}/.bashmatic/init" ]] && source "${HOME}/.bashmatic/init" >/dev/null
function app-size() {
local app_path="$1"
du -sh "$app_path" | awk '{printf "%s", $1}'
}
function wait-for-command() {
local command="$1"
local timeout="$2"
local interval="$3"
local elapsed=0.0
while ! eval "$command" >/dev/null 2>&1; do
sleep "$interval"
elapsed=$(echo "$elapsed + $interval" | bc)
if [[ $(echo "$elapsed >= $timeout" | bc) -eq 1 ]]; then
error "Reached timeout ($timeout seconds) waiting for $command to complete..."
return 1
fi
done
}
function install-deps() {
h2 "Checking for Brew, and will install it if not there."
command -v brew >/dev/null || /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
run "brew bundle"
command -v volta && volta setup
for file in $(util.shell-init-files) ; do
[[ -f ${file} ]] && {
# shellcheck disable=SC1090
source "${file}"
break
}
done
run "volta install node"
run "volta pin node"
run "volta install yarn"
run "volta pin yarn"
}
if [[ -d /Applications/Claude.app ]] && command -v claude >/dev/null; then
# shellcheck disable=SC2154
h1 "Claude.app is already installed. To re-install please run:" \
"${txtylw}rm -rf /Applications/Claude.app"
exit 0
fi
install-deps
[[ -s $dmg ]] && {
warning "Re-downloading $dmg even though a local copy exists..."
run.set-next show-output-on
run "rm -fv Claude.dmg*"
}
run.set-all abort-on-error show-output-off
run.set-next show-output-on
if [[ -d $mnt ]]; then
warning "Claude mount point $mnt already exists, unmounting it..."
run "diskutil unmount $mnt"
fi
run "wget --show-progress $url"
if [[ -s $dmg ]] ; then
h5 "Opening $dmg... Please wait..."
open $dmg >/dev/null 2>&1 &
progress.bar.auto-run 1.5
wait-for-command "test -d $mnt && test -d $mnt/Claude.app" 5.0 0.2 || {
error "Timeout waiting for $dmg to be mounted..."
exit 4
}
else
error "Can't find $dmg after downloading it." "Source URL was [$url]"
exit 2
fi
declare app_size current_size
if [[ -d "${mnt}" && -d "${mnt}/Claude.app" ]]; then
app_size=$(app-size "${mnt}/Claude.app")
info "Total size of Claude.app is: ${bldylw}${app_size}"
success "Claude DMG was successfully mounted in $mnt..."
else
error "Failed mounting $dmg, no directory $mnt were found."
exit 3
fi
h2bg "Installing Claude.app to /Applications...."
run "cp -rp $mnt/Claude.app /Applications"
h4 "Unmounting $dmg..."
run "diskutil unmount $mnt"
[[ -d /Applications/Claude.app ]] && command -v claude >/dev/null && {
current_size=$(app-size /Applications/Claude.app)
success "Claude.app is installed in /Applications"
echo
h3bg "Total size of the installed /Applications/Claude.app is: ${bldylw}${current_size}"
echo
sleep 1
h4bg "To start the Desktop Mac App, run ${txtgrn}${bakylw} open /Applications/Claude.app " \
"To run the CLI session in the terminal, run ${txtgrn}${bakylw} claude --help "
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment