install 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. APP=opencode
  4. MUTED='\033[0;2m'
  5. RED='\033[0;31m'
  6. ORANGE='\033[38;2;255;140;0m'
  7. NC='\033[0m' # No Color
  8. requested_version=${VERSION:-}
  9. raw_os=$(uname -s)
  10. os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
  11. # Normalize various Unix-like identifiers
  12. case "$raw_os" in
  13. Darwin*) os="darwin" ;;
  14. Linux*) os="linux" ;;
  15. MINGW*|MSYS*|CYGWIN*) os="windows" ;;
  16. esac
  17. arch=$(uname -m)
  18. if [[ "$arch" == "aarch64" ]]; then
  19. arch="arm64"
  20. elif [[ "$arch" == "x86_64" ]]; then
  21. arch="x64"
  22. fi
  23. filename="$APP-$os-$arch.zip"
  24. case "$filename" in
  25. *"-linux-"*)
  26. [[ "$arch" == "x64" || "$arch" == "arm64" ]] || exit 1
  27. ;;
  28. *"-darwin-"*)
  29. [[ "$arch" == "x64" || "$arch" == "arm64" ]] || exit 1
  30. ;;
  31. *"-windows-"*)
  32. [[ "$arch" == "x64" ]] || exit 1
  33. ;;
  34. *)
  35. echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}"
  36. exit 1
  37. ;;
  38. esac
  39. if ! command -v unzip >/dev/null 2>&1; then
  40. echo -e "${RED}Error: 'unzip' is required but not installed.${NC}"
  41. exit 1
  42. fi
  43. INSTALL_DIR=$HOME/.opencode/bin
  44. mkdir -p "$INSTALL_DIR"
  45. if [ -z "$requested_version" ]; then
  46. url="https://github.com/sst/opencode/releases/latest/download/$filename"
  47. specific_version=$(curl -s https://api.github.com/repos/sst/opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
  48. if [[ $? -ne 0 || -z "$specific_version" ]]; then
  49. echo -e "${RED}Failed to fetch version information${NC}"
  50. exit 1
  51. fi
  52. else
  53. url="https://github.com/sst/opencode/releases/download/v${requested_version}/$filename"
  54. specific_version=$requested_version
  55. fi
  56. print_message() {
  57. local level=$1
  58. local message=$2
  59. local color=""
  60. case $level in
  61. info) color="${NC}" ;;
  62. warning) color="${NC}" ;;
  63. error) color="${RED}" ;;
  64. esac
  65. echo -e "${color}${message}${NC}"
  66. }
  67. check_version() {
  68. if command -v opencode >/dev/null 2>&1; then
  69. opencode_path=$(which opencode)
  70. ## TODO: check if version is installed
  71. # installed_version=$(opencode version)
  72. installed_version="0.0.1"
  73. installed_version=$(echo $installed_version | awk '{print $2}')
  74. if [[ "$installed_version" != "$specific_version" ]]; then
  75. print_message info "${MUTED}Installed version: ${NC}$installed_version."
  76. else
  77. print_message info "${MUTED}Version ${NC}$specific_version${MUTED} already installed"
  78. exit 0
  79. fi
  80. fi
  81. }
  82. unbuffered_sed() {
  83. if echo | sed -u -e "" >/dev/null 2>&1; then
  84. sed -nu "$@"
  85. elif echo | sed -l -e "" >/dev/null 2>&1; then
  86. sed -nl "$@"
  87. else
  88. local pad="$(printf "\n%512s" "")"
  89. sed -ne "s/$/\\${pad}/" "$@"
  90. fi
  91. }
  92. print_progress() {
  93. local bytes="$1"
  94. local length="$2"
  95. [ "$length" -gt 0 ] || return 0
  96. local width=50
  97. local percent=$(( bytes * 100 / length ))
  98. [ "$percent" -gt 100 ] && percent=100
  99. local on=$(( percent * width / 100 ))
  100. local off=$(( width - on ))
  101. local filled=$(printf "%*s" "$on" "")
  102. filled=${filled// /■}
  103. local empty=$(printf "%*s" "$off" "")
  104. empty=${empty// /・}
  105. printf "\r${ORANGE}%s%s %3d%%${NC}" "$filled" "$empty" "$percent" >&4
  106. }
  107. download_with_progress() {
  108. local url="$1"
  109. local output="$2"
  110. if [ -t 2 ]; then
  111. exec 4>&2
  112. else
  113. exec 4>/dev/null
  114. fi
  115. local tmp_dir=${TMPDIR:-/tmp}
  116. local basename="${tmp_dir}/opencode_install_$$"
  117. local tracefile="${basename}.trace"
  118. rm -f "$tracefile"
  119. mkfifo "$tracefile"
  120. # Hide cursor
  121. printf "\033[?25l" >&4
  122. trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN
  123. (
  124. curl --trace-ascii "$tracefile" -s -L -o "$output" "$url"
  125. ) &
  126. local curl_pid=$!
  127. unbuffered_sed \
  128. -e 'y/ACDEGHLNORTV/acdeghlnortv/' \
  129. -e '/^0000: content-length:/p' \
  130. -e '/^<= recv data/p' \
  131. "$tracefile" | \
  132. {
  133. local length=0
  134. local bytes=0
  135. while IFS=" " read -r -a line; do
  136. [ "${#line[@]}" -lt 2 ] && continue
  137. local tag="${line[0]} ${line[1]}"
  138. if [ "$tag" = "0000: content-length:" ]; then
  139. length="${line[2]}"
  140. length=$(echo "$length" | tr -d '\r')
  141. bytes=0
  142. elif [ "$tag" = "<= recv" ]; then
  143. local size="${line[3]}"
  144. bytes=$(( bytes + size ))
  145. if [ "$length" -gt 0 ]; then
  146. print_progress "$bytes" "$length"
  147. fi
  148. fi
  149. done
  150. }
  151. wait $curl_pid
  152. local ret=$?
  153. echo "" >&4
  154. return $ret
  155. }
  156. download_and_install() {
  157. print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version"
  158. mkdir -p opencodetmp && cd opencodetmp
  159. if [[ "$os" == "windows" ]] || ! download_with_progress "$url" "$filename"; then
  160. # Fallback to standard curl on Windows or if custom progress fails
  161. curl -# -L -o "$filename" "$url"
  162. fi
  163. unzip -q "$filename"
  164. mv opencode "$INSTALL_DIR"
  165. chmod 755 "${INSTALL_DIR}/opencode"
  166. cd .. && rm -rf opencodetmp
  167. }
  168. check_version
  169. download_and_install
  170. add_to_path() {
  171. local config_file=$1
  172. local command=$2
  173. if grep -Fxq "$command" "$config_file"; then
  174. print_message info "Command already exists in $config_file, skipping write."
  175. elif [[ -w $config_file ]]; then
  176. echo -e "\n# opencode" >> "$config_file"
  177. echo "$command" >> "$config_file"
  178. print_message info "${MUTED}Successfully added ${NC}opencode ${MUTED}to \$PATH in ${NC}$config_file"
  179. else
  180. print_message warning "Manually add the directory to $config_file (or similar):"
  181. print_message info " $command"
  182. fi
  183. }
  184. XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
  185. current_shell=$(basename "$SHELL")
  186. case $current_shell in
  187. fish)
  188. config_files="$HOME/.config/fish/config.fish"
  189. ;;
  190. zsh)
  191. config_files="$HOME/.zshrc $HOME/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
  192. ;;
  193. bash)
  194. config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
  195. ;;
  196. ash)
  197. config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
  198. ;;
  199. sh)
  200. config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
  201. ;;
  202. *)
  203. # Default case if none of the above matches
  204. config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
  205. ;;
  206. esac
  207. config_file=""
  208. for file in $config_files; do
  209. if [[ -f $file ]]; then
  210. config_file=$file
  211. break
  212. fi
  213. done
  214. if [[ -z $config_file ]]; then
  215. print_message error "No config file found for $current_shell. Checked files: ${config_files[@]}"
  216. exit 1
  217. fi
  218. if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
  219. case $current_shell in
  220. fish)
  221. add_to_path "$config_file" "fish_add_path $INSTALL_DIR"
  222. ;;
  223. zsh)
  224. add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
  225. ;;
  226. bash)
  227. add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
  228. ;;
  229. ash)
  230. add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
  231. ;;
  232. sh)
  233. add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
  234. ;;
  235. *)
  236. export PATH=$INSTALL_DIR:$PATH
  237. print_message warning "Manually add the directory to $config_file (or similar):"
  238. print_message info " export PATH=$INSTALL_DIR:\$PATH"
  239. ;;
  240. esac
  241. fi
  242. if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
  243. echo "$INSTALL_DIR" >> $GITHUB_PATH
  244. print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
  245. fi
  246. echo -e ""
  247. echo -e "${MUTED}  ${NC} ▄ "
  248. echo -e "${MUTED}█▀▀█ █▀▀█ █▀▀█ █▀▀▄ ${NC}█▀▀▀ █▀▀█ █▀▀█ █▀▀█"
  249. echo -e "${MUTED}█░░█ █░░█ █▀▀▀ █░░█ ${NC}█░░░ █░░█ █░░█ █▀▀▀"
  250. echo -e "${MUTED}▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ${NC}▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"
  251. echo -e ""
  252. echo -e ""
  253. echo -e "${MUTED}To get started, navigate to a project and run:${NC}"
  254. echo -e "opencode ${MUTED}Use free models${NC}"
  255. echo -e "opencode auth login ${MUTED}Add paid provider API keys${NC}"
  256. echo -e "opencode help ${MUTED}List commands and options${NC}"
  257. echo -e ""
  258. echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/docs"
  259. echo -e ""
  260. echo -e ""