install.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. #!/bin/sh
  2. # Roo Code CLI Installer
  3. # Usage: curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh
  4. #
  5. # Environment variables:
  6. # ROO_INSTALL_DIR - Installation directory (default: ~/.roo/cli)
  7. # ROO_BIN_DIR - Binary symlink directory (default: ~/.local/bin)
  8. # ROO_VERSION - Specific version to install (default: latest)
  9. # ROO_LOCAL_TARBALL - Path to local tarball to install (skips download)
  10. set -e
  11. # Configuration
  12. INSTALL_DIR="${ROO_INSTALL_DIR:-$HOME/.roo/cli}"
  13. BIN_DIR="${ROO_BIN_DIR:-$HOME/.local/bin}"
  14. REPO="RooCodeInc/Roo-Code"
  15. MIN_NODE_VERSION=20
  16. # Color output (only if terminal supports it)
  17. if [ -t 1 ]; then
  18. RED='\033[0;31m'
  19. GREEN='\033[0;32m'
  20. YELLOW='\033[1;33m'
  21. BLUE='\033[0;34m'
  22. BOLD='\033[1m'
  23. NC='\033[0m'
  24. else
  25. RED=''
  26. GREEN=''
  27. YELLOW=''
  28. BLUE=''
  29. BOLD=''
  30. NC=''
  31. fi
  32. info() { printf "${GREEN}==>${NC} %s\n" "$1"; }
  33. warn() { printf "${YELLOW}Warning:${NC} %s\n" "$1"; }
  34. error() { printf "${RED}Error:${NC} %s\n" "$1" >&2; exit 1; }
  35. # Check Node.js version
  36. check_node() {
  37. if ! command -v node >/dev/null 2>&1; then
  38. error "Node.js is not installed. Please install Node.js $MIN_NODE_VERSION or higher.
  39. Install Node.js:
  40. - macOS: brew install node
  41. - Linux: https://nodejs.org/en/download/package-manager
  42. - Or use a version manager like fnm, nvm, or mise"
  43. fi
  44. NODE_VERSION=$(node -v | sed 's/v//' | cut -d. -f1)
  45. if [ "$NODE_VERSION" -lt "$MIN_NODE_VERSION" ]; then
  46. error "Node.js $MIN_NODE_VERSION+ required. Found: $(node -v)
  47. Please upgrade Node.js to version $MIN_NODE_VERSION or higher."
  48. fi
  49. info "Found Node.js $(node -v)"
  50. }
  51. # Detect OS and architecture
  52. detect_platform() {
  53. OS=$(uname -s | tr '[:upper:]' '[:lower:]')
  54. ARCH=$(uname -m)
  55. case "$OS" in
  56. darwin) OS="darwin" ;;
  57. linux) OS="linux" ;;
  58. mingw*|msys*|cygwin*)
  59. error "Windows is not supported by this installer. Please use WSL or install manually."
  60. ;;
  61. *) error "Unsupported OS: $OS" ;;
  62. esac
  63. case "$ARCH" in
  64. x86_64|amd64) ARCH="x64" ;;
  65. arm64|aarch64) ARCH="arm64" ;;
  66. *) error "Unsupported architecture: $ARCH" ;;
  67. esac
  68. PLATFORM="${OS}-${ARCH}"
  69. info "Detected platform: $PLATFORM"
  70. }
  71. # Get latest release version or use specified version
  72. get_version() {
  73. # Skip version fetch if using local tarball
  74. if [ -n "$ROO_LOCAL_TARBALL" ]; then
  75. VERSION="${ROO_VERSION:-local}"
  76. info "Using local tarball (version: $VERSION)"
  77. return
  78. fi
  79. if [ -n "$ROO_VERSION" ]; then
  80. VERSION="$ROO_VERSION"
  81. info "Using specified version: $VERSION"
  82. return
  83. fi
  84. info "Fetching latest version..."
  85. # Try to get the latest cli release
  86. RELEASES_JSON=$(curl -fsSL "https://api.github.com/repos/$REPO/releases" 2>/dev/null) || {
  87. error "Failed to fetch releases from GitHub. Check your internet connection."
  88. }
  89. # Extract highest cli-v* tag by semantic version (do not rely on API ordering)
  90. VERSION=$(printf "%s" "$RELEASES_JSON" | node -e '
  91. const fs = require("fs")
  92. const input = fs.readFileSync(0, "utf8")
  93. let releases
  94. try {
  95. releases = JSON.parse(input)
  96. } catch {
  97. process.exit(1)
  98. }
  99. function parseVersion(version) {
  100. const core = String(version).trim().split("+", 1)[0].split("-", 1)[0]
  101. if (!core) return null
  102. const parts = core.split(".")
  103. if (parts.length === 0 || parts.some((part) => !/^\d+$/.test(part))) {
  104. return null
  105. }
  106. return parts.map((part) => Number.parseInt(part, 10))
  107. }
  108. function compareVersions(a, b) {
  109. const maxLength = Math.max(a.length, b.length)
  110. for (let i = 0; i < maxLength; i++) {
  111. const aPart = a[i] ?? 0
  112. const bPart = b[i] ?? 0
  113. if (aPart > bPart) return 1
  114. if (aPart < bPart) return -1
  115. }
  116. return 0
  117. }
  118. let latestVersion = ""
  119. let latestParts = null
  120. if (Array.isArray(releases)) {
  121. for (const release of releases) {
  122. if (!release || typeof release.tag_name !== "string" || !release.tag_name.startsWith("cli-v")) {
  123. continue
  124. }
  125. const candidate = release.tag_name.slice("cli-v".length)
  126. const candidateParts = parseVersion(candidate)
  127. if (!candidateParts) continue
  128. if (!latestParts || compareVersions(candidateParts, latestParts) > 0) {
  129. latestVersion = candidate
  130. latestParts = candidateParts
  131. }
  132. }
  133. }
  134. if (latestVersion) {
  135. process.stdout.write(latestVersion)
  136. }
  137. ')
  138. if [ -z "$VERSION" ]; then
  139. error "Could not find any CLI releases. The CLI may not have been released yet."
  140. fi
  141. info "Latest version: $VERSION"
  142. }
  143. # Download and extract
  144. download_and_install() {
  145. TARBALL="roo-cli-${PLATFORM}.tar.gz"
  146. # Create temp directory
  147. TMP_DIR=$(mktemp -d)
  148. trap "rm -rf $TMP_DIR" EXIT
  149. # Use local tarball if provided, otherwise download
  150. if [ -n "$ROO_LOCAL_TARBALL" ]; then
  151. if [ ! -f "$ROO_LOCAL_TARBALL" ]; then
  152. error "Local tarball not found: $ROO_LOCAL_TARBALL"
  153. fi
  154. info "Using local tarball: $ROO_LOCAL_TARBALL"
  155. cp "$ROO_LOCAL_TARBALL" "$TMP_DIR/$TARBALL"
  156. else
  157. URL="https://github.com/$REPO/releases/download/cli-v${VERSION}/${TARBALL}"
  158. info "Downloading from $URL..."
  159. # Download with progress indicator
  160. HTTP_CODE=$(curl -fsSL -w "%{http_code}" "$URL" -o "$TMP_DIR/$TARBALL" 2>/dev/null) || {
  161. if [ "$HTTP_CODE" = "404" ]; then
  162. error "Release not found for platform $PLATFORM version $VERSION.
  163. Available at: https://github.com/$REPO/releases"
  164. fi
  165. error "Download failed. HTTP code: $HTTP_CODE"
  166. }
  167. # Verify we got something
  168. if [ ! -s "$TMP_DIR/$TARBALL" ]; then
  169. error "Downloaded file is empty. Please try again."
  170. fi
  171. fi
  172. # Remove old installation if exists
  173. if [ -d "$INSTALL_DIR" ]; then
  174. info "Removing previous installation..."
  175. rm -rf "$INSTALL_DIR"
  176. fi
  177. mkdir -p "$INSTALL_DIR"
  178. # Extract
  179. info "Extracting to $INSTALL_DIR..."
  180. tar -xzf "$TMP_DIR/$TARBALL" -C "$INSTALL_DIR" --strip-components=1 || {
  181. error "Failed to extract tarball. The download may be corrupted."
  182. }
  183. # Save ripgrep binary before npm install (npm install will overwrite node_modules)
  184. RIPGREP_BIN=""
  185. if [ -f "$INSTALL_DIR/node_modules/@vscode/ripgrep/bin/rg" ]; then
  186. RIPGREP_BIN="$TMP_DIR/rg"
  187. cp "$INSTALL_DIR/node_modules/@vscode/ripgrep/bin/rg" "$RIPGREP_BIN"
  188. fi
  189. # Install npm dependencies
  190. info "Installing dependencies..."
  191. cd "$INSTALL_DIR"
  192. npm install --production --silent 2>/dev/null || {
  193. warn "npm install failed, trying with --legacy-peer-deps..."
  194. npm install --production --legacy-peer-deps --silent 2>/dev/null || {
  195. error "Failed to install dependencies. Make sure npm is available."
  196. }
  197. }
  198. cd - > /dev/null
  199. # Restore ripgrep binary after npm install
  200. if [ -n "$RIPGREP_BIN" ] && [ -f "$RIPGREP_BIN" ]; then
  201. mkdir -p "$INSTALL_DIR/node_modules/@vscode/ripgrep/bin"
  202. cp "$RIPGREP_BIN" "$INSTALL_DIR/node_modules/@vscode/ripgrep/bin/rg"
  203. chmod +x "$INSTALL_DIR/node_modules/@vscode/ripgrep/bin/rg"
  204. fi
  205. # Make executable
  206. chmod +x "$INSTALL_DIR/bin/roo"
  207. # Also make ripgrep executable if it exists
  208. if [ -f "$INSTALL_DIR/bin/rg" ]; then
  209. chmod +x "$INSTALL_DIR/bin/rg"
  210. fi
  211. }
  212. # Create symlink in bin directory
  213. setup_bin() {
  214. mkdir -p "$BIN_DIR"
  215. # Remove old symlink if exists
  216. if [ -L "$BIN_DIR/roo" ] || [ -f "$BIN_DIR/roo" ]; then
  217. rm -f "$BIN_DIR/roo"
  218. fi
  219. ln -sf "$INSTALL_DIR/bin/roo" "$BIN_DIR/roo"
  220. info "Created symlink: $BIN_DIR/roo"
  221. }
  222. # Check if bin dir is in PATH and provide instructions
  223. check_path() {
  224. case ":$PATH:" in
  225. *":$BIN_DIR:"*)
  226. # Already in PATH
  227. return 0
  228. ;;
  229. esac
  230. warn "$BIN_DIR is not in your PATH"
  231. echo ""
  232. echo "Add this line to your shell profile:"
  233. echo ""
  234. # Detect shell and provide specific instructions
  235. SHELL_NAME=$(basename "$SHELL")
  236. case "$SHELL_NAME" in
  237. zsh)
  238. echo " echo 'export PATH=\"$BIN_DIR:\$PATH\"' >> ~/.zshrc"
  239. echo " source ~/.zshrc"
  240. ;;
  241. bash)
  242. if [ -f "$HOME/.bashrc" ]; then
  243. echo " echo 'export PATH=\"$BIN_DIR:\$PATH\"' >> ~/.bashrc"
  244. echo " source ~/.bashrc"
  245. else
  246. echo " echo 'export PATH=\"$BIN_DIR:\$PATH\"' >> ~/.bash_profile"
  247. echo " source ~/.bash_profile"
  248. fi
  249. ;;
  250. fish)
  251. echo " set -Ux fish_user_paths $BIN_DIR \$fish_user_paths"
  252. ;;
  253. *)
  254. echo " export PATH=\"$BIN_DIR:\$PATH\""
  255. ;;
  256. esac
  257. echo ""
  258. }
  259. # Verify installation
  260. verify_install() {
  261. if [ -x "$BIN_DIR/roo" ]; then
  262. info "Verifying installation..."
  263. # Just check if it runs without error
  264. "$BIN_DIR/roo" --version >/dev/null 2>&1 || true
  265. fi
  266. }
  267. # Print success message
  268. print_success() {
  269. echo ""
  270. printf "${GREEN}${BOLD}✓ Roo Code CLI installed successfully!${NC}\n"
  271. echo ""
  272. echo " Installation: $INSTALL_DIR"
  273. echo " Binary: $BIN_DIR/roo"
  274. echo " Version: $VERSION"
  275. echo ""
  276. echo " ${BOLD}Get started:${NC}"
  277. echo " roo --help"
  278. echo ""
  279. echo " ${BOLD}Example:${NC}"
  280. echo " export OPENROUTER_API_KEY=sk-or-v1-..."
  281. echo " cd ~/my-project && roo \"What is this project?\""
  282. echo ""
  283. }
  284. # Main
  285. main() {
  286. echo ""
  287. printf "${BLUE}${BOLD}"
  288. echo " ╭─────────────────────────────────╮"
  289. echo " │ Roo Code CLI Installer │"
  290. echo " ╰─────────────────────────────────╯"
  291. printf "${NC}"
  292. echo ""
  293. check_node
  294. detect_platform
  295. get_version
  296. download_and_install
  297. setup_bin
  298. check_path
  299. verify_install
  300. print_success
  301. }
  302. main "$@"