#!/bin/sh # Palette Code installer for macOS and Linux. # # curl -fsSL https://llm.pltt.dev/install | sh # curl -fsSL https://llm.pltt.dev/install | PALETTE_CODE_VERSION=0.1.2 sh # # What it does: detects os/arch, downloads the matching release tarball from # https://llm.pltt.dev/download/, installs the single binary to ~/.palette-code/bin and adds that # directory to PATH in your shell rc (skip with PALETTE_CODE_NO_MODIFY_PATH=1). # Windows: irm https://llm.pltt.dev/install.ps1 | iex set -eu BASE="${PALETTE_CODE_BASE:-https://llm.pltt.dev}" VERSION="${PALETTE_CODE_VERSION:-${1:-}}" extra_path="" INSTALL_DIR="${PALETTE_CODE_INSTALL_DIR:-$HOME/.palette-code/bin}" say() { printf '\033[1;35mpalette-code\033[0m %s\n' "$*"; } die() { printf '\033[1;31mpalette-code\033[0m %s\n' "$*" >&2; exit 1; } os=$(uname -s) case "$os" in Darwin) os=darwin ;; Linux) os=linux ;; MINGW*|MSYS*|CYGWIN*) die "On Windows run: irm $BASE/install.ps1 | iex" ;; *) die "unsupported OS: $os" ;; esac arch=$(uname -m) case "$arch" in arm64|aarch64) arch=arm64 ;; x86_64|amd64) arch=x64 ;; *) die "unsupported architecture: $arch" ;; esac target="palette-code-$os-$arch" if command -v curl >/dev/null 2>&1; then fetch() { curl -fsSL "$1" -o "$2"; } elif command -v wget >/dev/null 2>&1; then fetch() { wget -qO "$2" "$1"; } else die "need curl or wget" fi command -v tar >/dev/null 2>&1 || die "need tar" if [ -n "$VERSION" ]; then url="$BASE/download/$target-$VERSION.tgz" else url="$BASE/download/$target.tgz" fi tmp=$(mktemp -d 2>/dev/null || mktemp -d -t palette-code) trap 'rm -rf "$tmp"' EXIT say "downloading $url" fetch "$url" "$tmp/pc.tgz" || die "download failed: $url (no build for $target${VERSION:+ $VERSION}? see $BASE/download)" tar -xzf "$tmp/pc.tgz" -C "$tmp" bin="$tmp/package/bin/palette-code" [ -f "$bin" ] || die "tarball did not contain package/bin/palette-code" mkdir -p "$INSTALL_DIR" install -m 755 "$bin" "$INSTALL_DIR/palette-code" # A freshly downloaded file carries no quarantine flag when fetched by curl, but clear it anyway # in case the tarball came through a browser first. if [ "$os" = darwin ] && command -v xattr >/dev/null 2>&1; then xattr -d com.apple.quarantine "$INSTALL_DIR/palette-code" 2>/dev/null || true fi installed=$("$INSTALL_DIR/palette-code" --version 2>/dev/null | head -n 1 || true) say "installed ${installed:-palette-code} -> $INSTALL_DIR/palette-code" # Helper tools (the docs, media and computer-use MCP servers the agent is seeded with). The desktop app # ships them with their runtime, so a Mac that has the app gets tiny wrappers pointing at it; otherwise # they come from npm when Node is present. Without either, the agent still works and /mcps says why. helpers_app="${PALETTE_CODE_HELPERS:-/Applications/Palette Code.app/Contents/Resources/helpers/bin}" if [ -d "$helpers_app" ]; then for shim in "$helpers_app"/*; do name=$(basename "$shim") printf '#!/bin/sh\nexec "%s" "$@"\n' "$shim" > "$INSTALL_DIR/$name" && chmod 755 "$INSTALL_DIR/$name" done say "helper tools (docs, media, computer use) linked from the desktop app" elif command -v npm >/dev/null 2>&1; then say "installing helper tools with npm (docs, media, computer use), this takes a minute..." if npm install -g palette-docs palette-media palette-computer-use >/dev/null 2>&1; then npm_bin="$(npm prefix -g 2>/dev/null)/bin" case ":$PATH:" in *":$npm_bin:"*) say "helper tools installed" ;; *) say "helper tools installed in $npm_bin"; extra_path="$npm_bin" ;; esac else say "helper tools: npm install failed; later run: npm install -g palette-docs palette-media palette-computer-use" fi else say "helper tools (docs, media, computer use) need the desktop app or Node.js: https://llm.pltt.dev/download" fi # The login shell's own rc must carry the PATH line: a fresh Mac has no ~/.zshrc and zsh never reads # ~/.profile, so create the right file instead of falling back to .profile. case "${SHELL:-}" in */zsh) login_rc="$HOME/.zshrc" ;; */bash) login_rc="$HOME/.bashrc" ;; *) if [ "$os" = darwin ]; then login_rc="$HOME/.zshrc"; else login_rc="$HOME/.profile"; fi ;; esac # Make the command work in THIS window too, not only in a new one (users type `palette-code` right after # the install line): link it into a directory that is already on PATH and writable, e.g. Homebrew's bin. linked="" case ":$PATH:" in *":$INSTALL_DIR:"*) linked="$INSTALL_DIR" ;; esac if [ -z "$linked" ]; then old_ifs=$IFS; IFS=: for d in $PATH; do IFS=$old_ifs [ -n "$d" ] && [ -d "$d" ] && [ -w "$d" ] || continue case "$d" in "$INSTALL_DIR"|/bin|/usr/bin|*/sbin|.) continue ;; esac if ln -sf "$INSTALL_DIR/palette-code" "$d/palette-code" 2>/dev/null; then linked="$d"; break; fi done IFS=$old_ifs fi case ":$PATH:" in *":$INSTALL_DIR:"*) ;; *) if [ "${PALETTE_CODE_NO_MODIFY_PATH:-0}" = 1 ]; then say "add to PATH: export PATH=\"$INSTALL_DIR:\$PATH\"" else line="export PATH=\"$INSTALL_DIR${extra_path:+:$extra_path}:\$PATH\" # palette-code" added="" for rc in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile"; do if [ -f "$rc" ] && ! grep -qs "# palette-code" "$rc"; then printf '\n%s\n' "$line" >> "$rc"; added="$added $rc" fi done if ! grep -qs "# palette-code" "$login_rc" 2>/dev/null; then printf '\n%s\n' "$line" >> "$login_rc"; added="$added $login_rc" fi if [ -d "$HOME/.config/fish" ] && ! grep -qs "palette-code" "$HOME/.config/fish/config.fish" 2>/dev/null; then printf '\nfish_add_path %s # palette-code\n' "$INSTALL_DIR" >> "$HOME/.config/fish/config.fish" added="$added $HOME/.config/fish/config.fish" fi [ -n "$added" ] && say "added $INSTALL_DIR to PATH in:$added" fi ;; esac # The key, without an `export` lesson: ask for it here (the terminal, not the piped script, is stdin) and # keep it in the login shell's rc, which is what every later `palette-code` reads. Skipped when a key is # already set, already saved, or there is no terminal (CI). if [ -z "${PALETTE_API_KEY:-}" ] && ! grep -qs "PALETTE_API_KEY" "$login_rc" 2>/dev/null && [ -c /dev/tty ] \ && [ "${PALETTE_CODE_NO_PROMPT:-0}" != 1 ]; then printf '\n%s\n' "Paste your Palette API key and press Enter (or press Enter to add it later):" > /dev/tty 2>/dev/null printf '> ' > /dev/tty 2>/dev/null if read -r key < /dev/tty 2>/dev/null; then case "$key" in pk_*) printf '\nexport PALETTE_API_KEY="%s" # palette-code\n' "$key" >> "$login_rc"; chmod 600 "$login_rc" 2>/dev/null || true export PALETTE_API_KEY="$key"; say "key saved" ;; "") say "no key yet. Later: open $login_rc and add export PALETTE_API_KEY=" ;; *) say "that does not look like a Palette key (they start with pk_). Add it later in $login_rc: export PALETTE_API_KEY=" ;; esac fi fi say "" if [ -n "$linked" ]; then say "DONE. Type: palette-code" else say "DONE. Type: palette-code (if it says 'command not found', open a new Terminal window and type it there)" fi if [ -z "${PALETTE_API_KEY:-}" ] && ! grep -qs "PALETTE_API_KEY" "$login_rc" 2>/dev/null; then say "(the first run will ask for your Palette API key)" fi say "help: $BASE/docs"