install 8.9 KB

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