#!/usr/bin/env sh # Official installer for superpenguin. # Source: https://install.superpenguin.ai # # curl -fsSL https://install.superpenguin.ai | sh # # Detects OS/arch, fetches latest.json from the CLI's release prefix, # downloads the matching tarball, SHA-256 verifies it, extracts to # ~/.local/share/okibi/clis/superpenguin/versions//, and # symlinks the binary into ~/.local/bin and its version-matched Agent Skill # into ~/.agents/skills, then links it into detected agents' native skill # directories. Identity-enabled archives also carry a compatible # okibi-auth helper beside that CLI. The generated CLI prefers a separately # verified canonical helper when one exists, otherwise it uses this bundled # copy. Idempotent — re-running upgrades the CLI and its managed skill to # latest.json together. # # Env overrides: # SUPERPENGUIN_RELEASE_BASE Release prefix URL (default baked at serve time). # SUPERPENGUIN_UPDATE_URL Stable public update channel (default baked at serve time). # SUPERPENGUIN_INSTALL_ROOT Install dir (default ~/.local/share/okibi). # SUPERPENGUIN_BIN_DIR Symlink dir (default ~/.local/bin). # SUPERPENGUIN_SKILLS_DIR Agent Skills dir (default ~/.agents/skills). # # The installer writes private, NUL-delimited update metadata beside both the # versioned binary and stable launcher. New generated binaries use it for a # fail-open daily update check; no release token is written to shell config. set -eu CLI_NAME='superpenguin' OWNERSHIP_ID='jd74rswq6f1pxepkq7az3gxg8x8ek3q1' DEFAULT_RELEASE_BASE='https://releases.okibi.ai/clis/christopher18/superpenguin/r/ecb29952f685e5d6d4357a84c95ccc74a6ab6be91cf2587d' RELEASE_BASE="${SUPERPENGUIN_RELEASE_BASE:-$DEFAULT_RELEASE_BASE}" DEFAULT_UPDATE_URL='https://okibi.ai/i/cli/jd74rswq6f1pxepkq7az3gxg8x8ek3q1' UPDATE_URL="${SUPERPENGUIN_UPDATE_URL:-$DEFAULT_UPDATE_URL}" INSTALL_ROOT="${SUPERPENGUIN_INSTALL_ROOT:-$HOME/.local/share/okibi}" BIN_DIR="${SUPERPENGUIN_BIN_DIR:-$HOME/.local/bin}" SKILLS_DIR="${SUPERPENGUIN_SKILLS_DIR:-$HOME/.agents/skills}" err() { printf 'install: %s\n' "$*" >&2; exit 1; } info() { printf 'install: %s\n' "$*"; } absolute_dir() { mkdir -p "$1" (cd "$1" && pwd -P) } INSTALL_ROOT="$(absolute_dir "$INSTALL_ROOT")" BIN_DIR="$(absolute_dir "$BIN_DIR")" SKILLS_DIR="$(absolute_dir "$SKILLS_DIR")" if [ -e "$BIN_DIR/$CLI_NAME" ] || [ -L "$BIN_DIR/$CLI_NAME" ]; then existing_owner='' if [ -f "$BIN_DIR/$CLI_NAME.okibi-update" ]; then existing_owner="$(tr '\000' '\n' < "$BIN_DIR/$CLI_NAME.okibi-update" | sed -n '6p')" fi [ "$existing_owner" = "$OWNERSHIP_ID" ] \ || err "$BIN_DIR/$CLI_NAME is already occupied by another installation" fi link_detected_agent_skill() { agent_name="$1" agent_home="$2" [ -d "$agent_home" ] || return 0 agent_skills="$agent_home/skills" agent_dest="$agent_skills/$SKILL_NAME" [ "$agent_dest" = "$SKILL_DEST" ] && return 0 if [ -L "$agent_dest" ]; then agent_target="$(readlink "$agent_dest" 2>/dev/null || true)" [ "$agent_target" != "$SKILL_DEST" ] || return 0 fi if [ -f "$agent_dest/.okibi-owner" ] \ && [ "$(cat "$agent_dest/.okibi-owner")" = "$OWNERSHIP_ID" ]; then rm -rf "$agent_dest" elif [ -e "$agent_dest" ] || [ -L "$agent_dest" ]; then info "NOTE: $agent_name skill not linked because $agent_dest already exists and is not managed by $CLI_NAME" return 0 fi mkdir -p "$agent_skills" ln -s "$SKILL_DEST" "$agent_dest" info "linked Agent Skill for $agent_name at $agent_dest" } link_detected_agent_skills() { claude_home="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" codex_home="${CODEX_HOME:-$HOME/.codex}" config_home="${XDG_CONFIG_HOME:-$HOME/.config}" link_detected_agent_skill 'Claude Code' "$claude_home" link_detected_agent_skill 'Codex' "$codex_home" link_detected_agent_skill 'Cursor' "$HOME/.cursor" link_detected_agent_skill 'Gemini CLI' "$HOME/.gemini" link_detected_agent_skill 'GitHub Copilot' "$HOME/.copilot" link_detected_agent_skill 'Windsurf' "$HOME/.codeium/windsurf" link_detected_agent_skill 'OpenCode' "$config_home/opencode" } command -v curl >/dev/null 2>&1 || err 'curl is required' command -v tar >/dev/null 2>&1 || err 'tar is required' detect_platform() { os='' arch='' case "$(uname -s)" in Darwin) os=darwin ;; Linux) os=linux ;; *) err "unsupported OS: $(uname -s) (this installer ships macOS and Linux builds; Windows: append .ps1 to the install URL)" ;; esac case "$(uname -m)" in arm64 | aarch64) arch=arm64 ;; x86_64 | amd64) arch=x64 ;; *) err "unsupported architecture: $(uname -m)" ;; esac printf '%s-%s' "$os" "$arch" } # Bare-bones JSON extraction — avoids requiring jq. Pulls `field` from the # artifact object whose "platform" matches. Fragile by design: latest.json # is written by Okibi's publish pipeline with a fixed shape. extract_json_field() { platform="$1" field="$2" file="$3" tr -d '\n' <"$file" \ | grep -o "{[^{}]*\"platform\"[[:space:]]*:[[:space:]]*\"$platform\"[^{}]*}" \ | grep -o "\"$field\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" \ | head -1 \ | sed -E "s/.*\"$field\"[[:space:]]*:[[:space:]]*\"([^\"]*)\".*/\\1/" } extract_json_string() { field="$1" file="$2" tr -d '\n' <"$file" \ | grep -o "\"$field\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" \ | head -1 \ | sed -E "s/.*\"$field\"[[:space:]]*:[[:space:]]*\"([^\"]*)\".*/\\1/" } verify_sha256() { file="$1" expected="$2" if command -v sha256sum >/dev/null 2>&1; then actual="$(sha256sum "$file" | awk '{print $1}')" elif command -v shasum >/dev/null 2>&1; then actual="$(shasum -a 256 "$file" | awk '{print $1}')" else err 'no sha256sum or shasum available to verify the download' fi [ "$actual" = "$expected" ] \ || err "checksum mismatch: expected $expected, got $actual" } PLATFORM="$(detect_platform)" info "platform: $PLATFORM" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT MANIFEST="$TMP/latest.json" info "fetching $RELEASE_BASE/latest.json" curl -fsSL "$RELEASE_BASE/latest.json" -o "$MANIFEST" \ || err "could not reach the release host at $RELEASE_BASE" VERSION="$(extract_json_string version "$MANIFEST")" [ -n "$VERSION" ] || err 'latest.json is missing a version field' BINARY_NAME="$(extract_json_string binaryName "$MANIFEST")" [ -z "$BINARY_NAME" ] || [ "$BINARY_NAME" = "$CLI_NAME" ] \ || err 'latest.json does not match this CLI name' # The version becomes a path segment that is rm -rf'd below, so a hostile or # corrupt manifest must not be able to smuggle in path separators or "..". case "$VERSION" in *[!A-Za-z0-9._+-]* | *..*) err "latest.json has an unsafe version string: $VERSION" ;; esac URL="$(extract_json_field "$PLATFORM" url "$MANIFEST")" SHA="$(extract_json_field "$PLATFORM" sha256 "$MANIFEST")" [ -n "$URL" ] || err "no $CLI_NAME build published for $PLATFORM yet" [ -n "$SHA" ] || err "latest.json has no checksum for $PLATFORM" # skillName was added after generated CLI installers first shipped. Old # immutable releases remain installable; current releases carry the skill in # the same checksummed archive as the binary. SKILL_NAME="$(extract_json_string skillName "$MANIFEST")" SKILL_DEST='' SKILL_DEST_MANAGED=0 if [ -n "$SKILL_NAME" ]; then case "$SKILL_NAME" in *[!a-z0-9-]* | -* | *-) err "latest.json has an unsafe skill name: $SKILL_NAME" ;; esac SKILL_DEST="$SKILLS_DIR/$SKILL_NAME" if [ -f "$SKILL_DEST/.okibi-owner" ] \ && [ "$(cat "$SKILL_DEST/.okibi-owner")" = "$OWNERSHIP_ID" ]; then SKILL_DEST_MANAGED=1 elif [ -L "$SKILL_DEST" ]; then existing_target="$(readlink "$SKILL_DEST" 2>/dev/null || true)" case "$existing_target" in "$INSTALL_ROOT"/clis/"$CLI_NAME"/versions/*/skills/"$SKILL_NAME") SKILL_DEST_MANAGED=1 ;; esac fi if { [ -e "$SKILL_DEST" ] || [ -L "$SKILL_DEST" ]; } \ && [ "$SKILL_DEST_MANAGED" -ne 1 ]; then err "$SKILL_DEST is already occupied by another installation" fi fi # Relative URLs resolve against the release base. case "$URL" in http://* | https://*) ;; *) URL="$RELEASE_BASE/$URL" ;; esac TARBALL="$TMP/$CLI_NAME.tar.gz" info "downloading $CLI_NAME v$VERSION" curl -fsSL "$URL" -o "$TARBALL" || err 'download failed' verify_sha256 "$TARBALL" "$SHA" info 'checksum verified' VERSION_DIR="$INSTALL_ROOT/clis/$CLI_NAME/versions/$VERSION" rm -rf "$VERSION_DIR" mkdir -p "$VERSION_DIR" # Okibi CLI tarballs are flat: the binary at the archive root. tar -xzf "$TARBALL" -C "$VERSION_DIR" [ -f "$VERSION_DIR/$CLI_NAME" ] || err "archive did not contain a $CLI_NAME binary" chmod +x "$VERSION_DIR/$CLI_NAME" 2>/dev/null || true [ ! -f "$VERSION_DIR/okibi-auth" ] || chmod +x "$VERSION_DIR/okibi-auth" 2>/dev/null || true info "installed to $VERSION_DIR" # NUL delimiters preserve spaces and shell metacharacters without asking the # generated binary to evaluate a config file as code. printf '%s\0%s\0%s\0%s\0%s\0%s\0' \ "$RELEASE_BASE" "$UPDATE_URL" "$INSTALL_ROOT" "$BIN_DIR" "$SKILLS_DIR" "$OWNERSHIP_ID" \ > "$VERSION_DIR/.okibi-update" chmod 600 "$VERSION_DIR/.okibi-update" 2>/dev/null || true if [ -n "$SKILL_NAME" ]; then SKILL_SOURCE="$VERSION_DIR/skills/$SKILL_NAME" [ -f "$SKILL_SOURCE/SKILL.md" ] \ || err "archive did not contain skills/$SKILL_NAME/SKILL.md" grep -Eq "^name:[[:space:]]*$SKILL_NAME[[:space:]]*$" "$SKILL_SOURCE/SKILL.md" \ || err "bundled SKILL.md name does not match $SKILL_NAME" [ -f "$SKILL_SOURCE/.okibi-managed" ] \ && [ "$(cat "$SKILL_SOURCE/.okibi-managed")" = "$CLI_NAME" ] \ || err "bundled skill is missing its Okibi ownership marker" if [ -f "$SKILL_SOURCE/.okibi-owner" ]; then [ "$(cat "$SKILL_SOURCE/.okibi-owner")" = "$OWNERSHIP_ID" ] \ || err "bundled skill has the wrong Okibi ownership identity" else printf '%s\n' "$OWNERSHIP_ID" > "$SKILL_SOURCE/.okibi-owner" fi mkdir -p "$SKILLS_DIR" if [ "$SKILL_DEST_MANAGED" -eq 1 ]; then rm -rf "$SKILL_DEST" fi SKILL_SOURCE="$(cd "$SKILL_SOURCE" && pwd)" ln -s "$SKILL_SOURCE" "$SKILL_DEST" info "installed Agent Skill to $SKILL_DEST" link_detected_agent_skills else info "NOTE: this older release has no bundled Agent Skill" fi mkdir -p "$BIN_DIR" ln -sfn "$VERSION_DIR/$CLI_NAME" "$BIN_DIR/$CLI_NAME" printf '%s\0%s\0%s\0%s\0%s\0%s\0' \ "$RELEASE_BASE" "$UPDATE_URL" "$INSTALL_ROOT" "$BIN_DIR" "$SKILLS_DIR" "$OWNERSHIP_ID" \ > "$BIN_DIR/$CLI_NAME.okibi-update" chmod 600 "$BIN_DIR/$CLI_NAME.okibi-update" 2>/dev/null || true info "symlinked $BIN_DIR/$CLI_NAME" if [ -f "$VERSION_DIR/okibi-auth" ]; then info "installed bundled Identity helper beside $CLI_NAME" fi # A direct install after a rename replaces older managed names for this same # immutable CLI id. Reused names owned by another CLI have different metadata # and are left untouched. find "$BIN_DIR" -maxdepth 1 -type f -name '*.okibi-update' 2>/dev/null | while IFS= read -r metadata; do [ "$metadata" = "$BIN_DIR/$CLI_NAME.okibi-update" ] && continue owner="$(tr '\000' '\n' < "$metadata" | sed -n '6p')" [ "$owner" = "$OWNERSHIP_ID" ] || continue old_name="$(basename "$metadata" .okibi-update)" rm -f "$BIN_DIR/$old_name" "$BIN_DIR/$old_name-okibi-auth" "$metadata" rm -rf "$INSTALL_ROOT/clis/$old_name" rm -rf "$HOME/.config/$old_name" "$HOME/.local/share/$old_name" "$HOME/.cache/$old_name" done find "$SKILLS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | while IFS= read -r installed_skill; do [ "$installed_skill" = "$SKILL_DEST" ] && continue [ -f "$installed_skill/.okibi-owner" ] || continue [ "$(cat "$installed_skill/.okibi-owner")" = "$OWNERSHIP_ID" ] || continue rm -rf "$installed_skill" done remove_retired_agent_skills() { agent_home="$1" [ -d "$agent_home/skills" ] || return 0 find "$agent_home/skills" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) 2>/dev/null | while IFS= read -r installed_skill; do [ "$installed_skill" = "$agent_home/skills/$SKILL_NAME" ] && continue [ -f "$installed_skill/.okibi-owner" ] || continue [ "$(cat "$installed_skill/.okibi-owner")" = "$OWNERSHIP_ID" ] || continue rm -rf "$installed_skill" done } remove_retired_agent_skills "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" remove_retired_agent_skills "${CODEX_HOME:-$HOME/.codex}" remove_retired_agent_skills "$HOME/.cursor" remove_retired_agent_skills "$HOME/.gemini" remove_retired_agent_skills "$HOME/.copilot" remove_retired_agent_skills "$HOME/.codeium/windsurf" remove_retired_agent_skills "${XDG_CONFIG_HOME:-$HOME/.config}/opencode" case ":$PATH:" in *":$BIN_DIR:"*) ;; *) info '' info "NOTE: $BIN_DIR is not on your PATH. Add this to your shell profile:" info '' info " export PATH=\"$BIN_DIR:\$PATH\"" info '' ;; esac info "done: installed $CLI_NAME v$VERSION. Try: $CLI_NAME --help"