#!/bin/sh
set -eu
REPO="harness/cli"
INSTALL_DIR="${HARNESS_INSTALL_DIR:-$HOME/.local/bin}"
USER_OVERRIDE="${HARNESS_INSTALL_DIR:+yes}"
NONINTERACTIVE="${HARNESS_NONINTERACTIVE:-}"
NO_VERIFY="${HARNESS_NO_VERIFY:-}"
CORE_ONLY="${HARNESS_CORE_ONLY:-}"
# ── helpers ────────────────────────────────────────────────────────────────────
info() { printf ' \033[34m•\033[0m %s\n' "$*"; }
success() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
warn() { printf ' \033[33m!\033[0m %s\n' "$*"; }
error() { printf ' \033[31m✗\033[0m %s\n' "$*" >&2; exit 1; }
is_interactive() {
[ -n "$NONINTERACTIVE" ] && return 1
{ true /dev/null
}
confirm() {
local prompt="$1"
local answer
printf ' \033[34m?\033[0m %s [Y/n] ' "$prompt"
read -r answer /dev/null 2>&1; then
sha256sum "$1" | cut -d' ' -f1
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$1" | cut -d' ' -f1
else
error "No sha256sum or shasum command found — cannot verify download. Pass --no-verify to skip verification."
fi
}
download_and_install() {
local version="$1"
local platform="$2"
local dest="$3"
local ver="${version#v}"
local pkg_name="harness-core_${ver}_${platform}"
local url="https://github.com/${REPO}/releases/download/${version}/${pkg_name}.tar.gz"
local checksum_url="https://github.com/${REPO}/releases/download/${version}/harness_${ver}_checksums.txt"
local tmp
tmp="$(mktemp -d)"
[ -z "$tmp" ] && error "Failed to create temporary directory"
info "Downloading ${pkg_name} $version ($platform)..."
curl -fsSL "$url" -o "$tmp/harness.tar.gz"
if [ -n "$NO_VERIFY" ]; then
warn "Skipping checksum verification (--no-verify)"
else
info "Verifying checksum..."
curl -fsSL "$checksum_url" -o "$tmp/checksums.txt"
local expected actual
expected="$(grep "${pkg_name}.tar.gz" "$tmp/checksums.txt" | cut -d' ' -f1)"
[ -z "$expected" ] && error "Checksum entry not found for ${pkg_name}.tar.gz"
actual="$(sha256_file "$tmp/harness.tar.gz")"
[ "$actual" = "$expected" ] || error "Checksum mismatch — download may be corrupted"
fi
tar -xzf "$tmp/harness.tar.gz" -C "$tmp"
mv "$tmp/harness" "$dest/harness"
chmod +x "$dest/harness"
success "Installed harness $version to $dest/harness"
# A failure here still leaves a working core, so warn instead of aborting.
if [ -z "$CORE_ONLY" ]; then
info "Installing har module..."
if "$dest/harness" install module har >/dev/null 2>&1; then
success "Installed har module to ~/.harness/bin"
else
warn "Could not install the har module — run 'harness install module har' to retry"
fi
fi
rm -rf "$tmp"
}
# ── shell config ───────────────────────────────────────────────────────────────
shell_config_block() {
local shell_name
shell_name="$(basename "${SHELL:-bash}")"
printf '# \n'
if [ "$shell_name" = "fish" ]; then
printf 'fish_add_path "$HOME/.local/bin"\n'
printf 'harness completion fish | source\n'
else
printf 'export PATH="$HOME/.local/bin:$PATH"\n'
printf 'source <(harness completion %s)\n' "$shell_name"
fi
printf '# \n'
}
patch_shell_rc() {
local rc="$1"
touch "$rc"
printf '\n%s\n\n' "$(shell_config_block)" >> "$rc"
}
already_patched() {
local rc="$1"
grep -q '' "$rc" 2>/dev/null
}
# ── main ───────────────────────────────────────────────────────────────────────
main() {
parse_args "$@"
printf '\n \033[1mHarness CLI installer\033[0m\n\n'
local platform
local version
platform="$(detect_platform)"
version="$(latest_version)"
[ -z "$version" ] && error "Could not determine latest version"
# create install dir if needed
mkdir -p "$INSTALL_DIR"
[ -d "$INSTALL_DIR" ] || error "$INSTALL_DIR is not a directory"
# install binaries
download_and_install "$version" "$platform" "$INSTALL_DIR"
HARNESS_INSTALL_TYPE=script "$INSTALL_DIR/harness" --post-install >/dev/null 2>&1 || true
local patched_rc=""
# shell config — only if interactive and user didn't override install dir
if is_interactive && [ -z "$USER_OVERRIDE" ]; then
local rc
local rc_name
rc="$(detect_shell_rc)"
rc_name="$(basename "$rc")"
if already_patched "$rc"; then
info "Shell config already set up in ~/$rc_name, skipping"
else
printf '\n'
info "Would you like us to update ~/$rc_name?"
info " - Add ~/.local/bin to PATH"
info " - Add shell completions"
printf '\n'
if confirm "Update ~/$rc_name"; then
patch_shell_rc "$rc"
patched_rc="$rc_name"
success "Updated ~/$rc_name"
else
printf '\n'
info "To set up manually, add to ~/$rc_name:"
printf '\n%s\n' "$(shell_config_block)"
fi
fi
fi
printf '\n'
if command -v harness >/dev/null 2>&1; then
success "Done! Run 'harness version' to verify."
elif [ -n "$patched_rc" ]; then
success "Done! Run 'source ~/$patched_rc' then 'harness version' to verify."
else
success "Done! Add $INSTALL_DIR to your PATH then run 'harness version' to verify."
fi
}
main "$@"