← Files RevylARCHIVED FILE
scripts/launch-runtime
10.9 KB · Oct 2, 2026 · 00:33 UTC
#!/bin/sh
#
# Resolves the plugin-pinned Revyl runtime, then replaces this process with it.
set -eu
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
PLUGIN_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
MANIFEST_PATH=${REVYL_RUNTIME_MANIFEST:-"$PLUGIN_DIR/runtime-manifest.json"}
TEMPORARY_PATH=
DOWNLOAD_ATTEMPTS=3
# cleanup removes only this process's incomplete download.
cleanup() {
if [ -n "$TEMPORARY_PATH" ]; then
rm -f "$TEMPORARY_PATH"
fi
}
# fail reports a bootstrap failure without corrupting MCP stdout.
fail() {
printf 'Revyl plugin runtime error: %s\n' "$1" >&2
exit 1
}
# manifest_string reads one flat string property from the generated manifest.
manifest_string() {
key=$1
sed -n "s/^[[:space:]]*\"${key}\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\"[[:space:]]*,\\{0,1\\}[[:space:]]*$/\\1/p" "$MANIFEST_PATH"
}
# manifest_scalar reads one flat number or boolean property.
manifest_scalar() {
key=$1
sed -n "s/^[[:space:]]*\"${key}\"[[:space:]]*:[[:space:]]*\\([^,[:space:]]*\\)[[:space:]]*,\\{0,1\\}[[:space:]]*$/\\1/p" "$MANIFEST_PATH"
}
# absolute_path prints one path in absolute form without resolving symlinks.
absolute_path() {
case "$1" in
/*) printf '%s\n' "$1" ;;
*)
parent_directory=$(CDPATH= cd -- "$(dirname -- "$1")" && pwd)
printf '%s/%s\n' "$parent_directory" "$(basename -- "$1")"
;;
esac
}
# resolve_override returns an explicitly selected executable when configured.
resolve_override() {
requested=$1
if command -v "$requested" >/dev/null 2>&1; then
absolute_path "$(command -v "$requested")"
return
fi
if [ -f "$requested" ] && [ -x "$requested" ]; then
absolute_path "$requested"
return
fi
fail "REVYL_BINARY is not executable: $requested"
}
# run_runtime exposes the executable for setup remediation, then replaces this process.
run_runtime() {
selected_binary=$1
shift
REVYL_MCP_EXECUTABLE=$selected_binary
export REVYL_MCP_EXECUTABLE
REVYL_NO_UPDATE_NOTIFIER=1
export REVYL_NO_UPDATE_NOTIFIER
REVYL_CLIENT_SOURCE=cursor_plugin
if [ "$PLUGIN_HOST" = codex ]; then
REVYL_CLIENT_SOURCE=
fi
export REVYL_CLIENT_SOURCE
trap - EXIT HUP INT TERM
exec "$selected_binary" "$@"
}
# detect_platform maps the current host to one release manifest prefix.
detect_platform() {
case "$(uname -s 2>/dev/null || true)" in
Darwin) operating_system=darwin ;;
Linux) operating_system=linux ;;
*) fail "unsupported operating system: $(uname -s 2>/dev/null || printf unknown)" ;;
esac
case "$(uname -m 2>/dev/null || true)" in
x86_64|amd64) architecture=amd64 ;;
arm64|aarch64) architecture=arm64 ;;
*) fail "unsupported architecture: $(uname -m 2>/dev/null || printf unknown)" ;;
esac
printf '%s_%s\n' "$operating_system" "$architecture"
}
# sha256_file returns the lowercase SHA256 digest for one file.
sha256_file() {
path=$1
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$path" | awk '{print tolower($1)}'
return
fi
if command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$path" | awk '{print tolower($1)}'
return
fi
fail "sha256sum or shasum is required to verify the Revyl runtime"
}
# checksum_matches verifies one file without writing output.
checksum_matches() {
path=$1
expected=$2
[ -f "$path" ] && [ "$(sha256_file "$path")" = "$expected" ]
}
# installed_runtime_candidates lists already-installed Revyl CLI paths in trust order.
installed_runtime_candidates() {
command -v revyl 2>/dev/null || true
printf '%s\n' /usr/local/bin/revyl
if [ -n "${HOME:-}" ]; then
printf '%s\n' "$HOME/.revyl/bin/revyl"
printf '%s\n' "$HOME/.local/bin/revyl"
fi
}
# resolve_installed_runtime prints the first installed CLI byte-identical to the pin.
resolve_installed_runtime() {
expected=$1
installed_runtime_candidates | while IFS= read -r candidate; do
[ -n "$candidate" ] || continue
{ [ -f "$candidate" ] && [ -x "$candidate" ]; } || continue
if checksum_matches "$candidate" "$expected"; then
absolute_path "$candidate"
break
fi
done
}
# ensure_runtime_directory creates the private versioned cache directory.
ensure_runtime_directory() {
mkdir -p "$runtime_directory" &&
chmod 0700 "$runtime_directory"
}
# adopt_installed_runtime copies a verified installed CLI into the plugin cache.
adopt_installed_runtime() {
source_binary=$1
ensure_runtime_directory || return 1
TEMPORARY_PATH="$runtime_directory/.revyl.adopt.$$"
cp -- "$source_binary" "$TEMPORARY_PATH" || return 1
chmod 0700 "$TEMPORARY_PATH" || return 1
checksum_matches "$TEMPORARY_PATH" "$expected_checksum" || return 1
mv -f "$TEMPORARY_PATH" "$runtime_binary" || return 1
TEMPORARY_PATH=
checksum_matches "$runtime_binary" "$expected_checksum"
}
# publish_user_runtime copies a verified pin onto a user PATH location.
#
# Prefers ~/.revyl/bin/revyl, then ~/.local/bin/revyl. A different existing CLI
# is left in place. Failures never block launching the verified runtime.
publish_user_runtime() {
source_binary=$1
[ -n "${HOME:-}" ] || return 0
for destination in "$HOME/.revyl/bin/revyl" "$HOME/.local/bin/revyl"; do
if checksum_matches "$destination" "$expected_checksum"; then
return 0
fi
if [ -e "$destination" ] || [ -L "$destination" ]; then
continue
fi
destination_directory=$(dirname -- "$destination")
mkdir -p "$destination_directory" || continue
publish_temporary="$destination_directory/.revyl.publish.$$"
if cp -- "$source_binary" "$publish_temporary" &&
chmod 0700 "$publish_temporary" &&
checksum_matches "$publish_temporary" "$expected_checksum" &&
mv -f "$publish_temporary" "$destination"; then
checksum_matches "$destination" "$expected_checksum" && return 0
fi
rm -f "$publish_temporary"
done
return 0
}
# run_verified_runtime publishes a verified pin onto PATH, then replaces this process.
run_verified_runtime() {
if [ "$PLUGIN_HOST" = cursor ]; then
publish_user_runtime "$1"
fi
run_runtime "$@"
}
# download_attempt fetches one bounded HTTPS resource exactly once.
download_attempt() {
url=$1
destination=$2
if command -v curl >/dev/null 2>&1; then
curl --fail --silent --show-error --location \
--connect-timeout 10 \
--max-time 180 \
--output "$destination" \
"$url"
return
fi
if command -v wget >/dev/null 2>&1; then
wget --quiet \
--timeout=30 \
--tries=1 \
--output-document="$destination" \
"$url"
return
fi
fail "curl or wget is required for the first Revyl plugin run"
}
# download retries one bounded fetch, backing off between transient failures.
download() {
url=$1
destination=$2
attempt=1
delay=1
while :; do
if download_attempt "$url" "$destination"; then
return 0
fi
rm -f "$destination"
if [ "$attempt" -ge "$DOWNLOAD_ATTEMPTS" ]; then
return 1
fi
printf 'Revyl plugin runtime: download attempt %s of %s failed; retrying in %ss\n' \
"$attempt" "$DOWNLOAD_ATTEMPTS" "$delay" >&2
sleep "$delay"
attempt=$((attempt + 1))
delay=$((delay * 2))
done
}
trap cleanup EXIT HUP INT TERM
umask 077
PLUGIN_HOST=${REVYL_PLUGIN_HOST:-cursor}
case "$PLUGIN_HOST" in
cursor|codex) ;;
*) fail "unsupported plugin host; expected cursor or codex" ;;
esac
if [ "${REVYL_API_KEY:-}" = '${env:REVYL_API_KEY}' ]; then
unset REVYL_API_KEY
fi
if [ -n "${REVYL_BINARY:-}" ] && [ "$REVYL_BINARY" != '${env:REVYL_BINARY}' ]; then
selected_binary=$(resolve_override "$REVYL_BINARY")
run_runtime "$selected_binary" "$@"
fi
[ -f "$MANIFEST_PATH" ] || fail "runtime manifest not found at $MANIFEST_PATH"
schema_version=$(manifest_scalar schema_version)
prepared=$(manifest_scalar prepared)
plugin_version=$(manifest_string plugin_version)
runtime_version=$(manifest_string runtime_version)
release_tag=$(manifest_string release_tag)
release_base_url=$(manifest_string release_base_url)
[ "$schema_version" = "1" ] || fail "unsupported runtime manifest schema: ${schema_version:-missing}"
[ "$prepared" = "true" ] || fail "this plugin release has no prepared runtime; reinstall or update the Revyl plugin"
printf '%s' "$plugin_version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$' ||
fail "invalid plugin version in runtime manifest"
printf '%s' "$runtime_version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$' ||
fail "invalid runtime version in runtime manifest"
[ "$release_tag" = "v$runtime_version" ] || fail "runtime release tag does not match its version"
expected_base_url="https://github.com/RevylAI/revyl-cli/releases/download/$release_tag"
[ "$release_base_url" = "$expected_base_url" ] || fail "runtime release URL is not immutable"
platform=$(detect_platform)
asset=$(manifest_string "${platform}_asset")
expected_checksum=$(manifest_string "${platform}_sha256")
[ -n "$asset" ] || fail "runtime manifest has no asset for $platform"
printf '%s' "$asset" | grep -Eq '^revyl-(darwin|linux)-(amd64|arm64)$' ||
fail "runtime manifest contains an invalid asset name for $platform"
printf '%s' "$expected_checksum" | grep -Eq '^[0-9a-f]{64}$' ||
fail "runtime manifest contains an invalid checksum for $platform"
cache_root=${REVYL_PLUGIN_CACHE_DIR:-"${XDG_CACHE_HOME:-$HOME/.cache}/revyl/$PLUGIN_HOST-plugin"}
runtime_directory="$cache_root/$runtime_version/$platform"
runtime_binary="$runtime_directory/revyl"
if checksum_matches "$runtime_binary" "$expected_checksum"; then
chmod 0700 "$runtime_binary"
run_verified_runtime "$runtime_binary" "$@"
fi
# An already-installed CLI is byte-identical to the pinned asset when its digest
# matches, so adopting it is equivalent to the download it replaces.
installed_binary=$(resolve_installed_runtime "$expected_checksum")
if [ -n "$installed_binary" ]; then
if adopt_installed_runtime "$installed_binary"; then
run_verified_runtime "$runtime_binary" "$@"
fi
cleanup
TEMPORARY_PATH=
printf 'Revyl plugin runtime: could not populate the plugin cache; running verified %s\n' \
"$installed_binary" >&2
run_verified_runtime "$installed_binary" "$@"
fi
# Callers on a short time budget opt out of the download so they fail fast
# instead of blocking on the network for a runtime a later run will cache.
if [ "${REVYL_RUNTIME_NO_DOWNLOAD:-}" = "1" ]; then
fail "the pinned Revyl runtime is not cached yet and this invocation may not download it"
fi
ensure_runtime_directory
TEMPORARY_PATH="$runtime_directory/.revyl.download.$$"
download "$release_base_url/$asset" "$TEMPORARY_PATH" ||
fail "could not download $asset from $release_base_url/$asset after $DOWNLOAD_ATTEMPTS attempts; install the Revyl CLI or set REVYL_BINARY to an executable Revyl CLI path"
checksum_matches "$TEMPORARY_PATH" "$expected_checksum" ||
fail "checksum verification failed for $asset"
chmod 0700 "$TEMPORARY_PATH"
mv -f "$TEMPORARY_PATH" "$runtime_binary"
TEMPORARY_PATH=
checksum_matches "$runtime_binary" "$expected_checksum" ||
fail "cached runtime verification failed after installation"
run_verified_runtime "$runtime_binary" "$@"
SHA-256: 8dc34ae1c6f289a0bc208f8320129f2790b307aceade2f7d10208ddf21a09ad8