#!/usr/bin/env bash # Synthlo setup for macOS and Linux. Run as your normal user. set -euo pipefail umask 077 trap 'printf "\nSynthlo stopped at line %s. Resolve the error above and rerun.\n" "$LINENO" >&2' ERR API_KEY="" SYNTHLO_HOME="${SYNTHLO_HOME:-$HOME}" export SYNTHLO_HOME SYNTHLO_ROOT="$SYNTHLO_HOME/.local/share/synthlo" export PATH="$SYNTHLO_ROOT/node/bin:$SYNTHLO_ROOT/bin:$PATH" case "$(uname -s)" in Darwin) PLATFORM=darwin ;; Linux) PLATFORM=linux ;; *) echo 'Supported systems: macOS and Linux. On Windows use the PowerShell script.' >&2; exit 1 ;; esac node_ready() { command -v node >/dev/null 2>&1 && node -e 'const [a,b]=process.versions.node.split(".").map(Number); process.exit(a>22||(a===22&&b>=19)?0:1)' >/dev/null 2>&1 } command -v curl >/dev/null 2>&1 || { echo 'curl is required. Install curl and rerun.' >&2; exit 1; } echo 'Checking your Synthlo connection...' curl --connect-timeout 15 --max-time 30 -fsS 'http://192.168.100.242:8000/v1/models' -o /dev/null if ! node_ready || ! command -v npm >/dev/null 2>&1; then case "$(uname -m)" in x86_64|amd64) ARCH=x64 ;; aarch64|arm64) ARCH=arm64 ;; *) echo 'Install Node.js 22.19+ and npm for your CPU, then rerun.' >&2; exit 1 ;; esac if [ "$PLATFORM" = linux ] && { [ -f /etc/alpine-release ] || (ldd --version 2>&1 || true) | grep -qi musl; }; then echo 'On musl/Alpine Linux, install Node.js 22.19+ and npm using your preferred method, then rerun. Synthlo will not modify system packages.' >&2 exit 1 fi command -v tar >/dev/null 2>&1 || { echo 'tar is required to unpack Node.js.' >&2; exit 1; } if command -v sha256sum >/dev/null 2>&1; then HASH_TOOL=sha256sum elif command -v shasum >/dev/null 2>&1; then HASH_TOOL=shasum else echo 'sha256sum or shasum is required to verify Node.js.' >&2; exit 1; fi NODE_TMP=$(mktemp -d "${TMPDIR:-/tmp}/synthlo-node.XXXXXX") trap 'rm -rf "$NODE_TMP"' EXIT NODE_URL=https://nodejs.org/dist/latest-v22.x curl --connect-timeout 15 --max-time 120 -fsSL "$NODE_URL/SHASUMS256.txt" -o "$NODE_TMP/checksums" NODE_FILE=$(awk -v suffix="-$PLATFORM-$ARCH.tar.gz" 'index($2,"node-v")==1 && substr($2,length($2)-length(suffix)+1)==suffix { print $2; exit }' "$NODE_TMP/checksums") [[ "$NODE_FILE" =~ ^node-v22\.[0-9]+\.[0-9]+-(darwin|linux)-(arm64|x64)\.tar\.gz$ ]] || { echo 'No supported Node.js archive found.' >&2; exit 1; } echo "Installing a private Node.js runtime in $SYNTHLO_ROOT/node..." curl --connect-timeout 15 --max-time 600 -fsSL "$NODE_URL/$NODE_FILE" -o "$NODE_TMP/$NODE_FILE" EXPECTED=$(awk -v f="$NODE_FILE" '$2==f { print $1 }' "$NODE_TMP/checksums") if [ "$HASH_TOOL" = sha256sum ]; then ACTUAL=$(sha256sum "$NODE_TMP/$NODE_FILE" | awk '{print $1}') else ACTUAL=$(shasum -a 256 "$NODE_TMP/$NODE_FILE" | awk '{print $1}'); fi [ "$EXPECTED" = "$ACTUAL" ] || { echo 'Node.js checksum mismatch. Download was not installed.' >&2; exit 1; } mkdir -p "$SYNTHLO_ROOT/node" tar -xzf "$NODE_TMP/$NODE_FILE" -C "$SYNTHLO_ROOT/node" --strip-components=1 hash -r node_ready || { echo 'This Node.js binary is incompatible with your system. Install Node.js 22.19+ and rerun.' >&2; exit 1; } fi # A user-owned npm prefix avoids sudo and changes to a system Node installation. echo 'Installing Pi and OpenCode for your user...' npm install --global --prefix "$SYNTHLO_ROOT" --ignore-scripts @earendil-works/pi-coding-agent npm install --global --prefix "$SYNTHLO_ROOT" opencode-ai # Use the same runtime on future shells (including installations managed by nvm). NODE_EXEC=$(node -p 'process.execPath') mkdir -p "$SYNTHLO_ROOT/bin" ln -sf "$NODE_EXEC" "$SYNTHLO_ROOT/bin/node" PATH_LINE='export PATH="$HOME/.local/share/synthlo/node/bin:$HOME/.local/share/synthlo/bin:$PATH"' for PROFILE in "$SYNTHLO_HOME/.profile" "$SYNTHLO_HOME/.bashrc" "$SYNTHLO_HOME/.zshrc"; do if ! grep -Fq '# Synthlo tools' "$PROFILE" 2>/dev/null; then printf '\n# Synthlo tools\n%s\n' "$PATH_LINE" >> "$PROFILE" fi done # Bash login shells skip .profile if a .bash_profile already exists. if [ -f "$SYNTHLO_HOME/.bash_profile" ] && ! grep -Fq '# Synthlo tools' "$SYNTHLO_HOME/.bash_profile"; then printf '\n# Synthlo tools\n%s\n' "$PATH_LINE" >> "$SYNTHLO_HOME/.bash_profile" fi CURRENT_SHELL="${SHELL:-}" if [ "${CURRENT_SHELL##*/}" = fish ]; then mkdir -p "$SYNTHLO_HOME/.config/fish/conf.d" printf 'fish_add_path "$HOME/.local/share/synthlo/bin" "$HOME/.local/share/synthlo/node/bin"\n' > "$SYNTHLO_HOME/.config/fish/conf.d/synthlo.fish" fi if ! node_ready; then echo 'Node.js 22.19+ is required. Use the full setup command first; this config-only script installs nothing.' >&2 exit 1 fi export SYNTHLO_TOKEN="$API_KEY" export SYNTHLO_CONFIG='' node -e "eval(Buffer.from('KGFzeW5jICgpID0+IHsKLy8gU2hhcmVkIGJ5IFVuaXggYW5kIFdpbmRvd3MgaW5zdGFsbGVycy4gVmFsaWRhdGUgZXZlcnkgaW5wdXQgYmVmb3JlIHdyaXRpbmcgZmlsZXMuCmNvbnN0IGZzID0gcmVxdWlyZSgnZnMnKTsKY29uc3QgcGF0aCA9IHJlcXVpcmUoJ3BhdGgnKTsKY29uc3Qgb3MgPSByZXF1aXJlKCdvcycpOwpjb25zdCBjcnlwdG8gPSByZXF1aXJlKCdjcnlwdG8nKTsKY29uc3QgdG9rZW4gPSBwcm9jZXNzLmVudi5TWU5USExPX1RPS0VOOwoKLy8gVGhpcyBTeW50aGxvIGluc3RhbGxhdGlvbiB1c2VzIG11bHRpbW9kYWwgbW9kZWxzLiBFeHBsaWNpdCBiYWNrZW5kIG1ldGFkYXRhCi8vIGFuZCBvcGVyYXRvciBvdmVycmlkZXMgdGFrZSBwcmVjZWRlbmNlIG92ZXIgdGhhdCB3b3Jrc3BhY2UgZGVmYXVsdC4KY29uc3QgbW9kZWxJbnB1dCA9IGZ1bmN0aW9uIG1vZGVsSW5wdXQobW9kZWwpIHsKICAgIGNvbnN0IGlucHV0ID0gbW9kZWwubW9kYWxpdGllcz8uaW5wdXQgfHwgbW9kZWwuaW5wdXQ7CiAgICBpZiAoQXJyYXkuaXNBcnJheShpbnB1dCkgJiYgaW5wdXQubGVuZ3RoKSByZXR1cm4gWyd0ZXh0JywgLi4uKGlucHV0LmluY2x1ZGVzKCdpbWFnZScpID8gWydpbWFnZSddIDogW10pXTsKICAgIGlmICh0eXBlb2YgbW9kZWwuc3VwcG9ydHNfdmlzaW9uID09PSAnYm9vbGVhbicpIHJldHVybiBtb2RlbC5zdXBwb3J0c192aXNpb24gPyBbJ3RleHQnLCdpbWFnZSddIDogWyd0ZXh0J107CiAgICBpZiAoQXJyYXkuaXNBcnJheShtb2RlbC5jYXBhYmlsaXRpZXMpICYmIG1vZGVsLmNhcGFiaWxpdGllcy5pbmNsdWRlcygndGV4dC1vbmx5JykpIHJldHVybiBbJ3RleHQnXTsKICAgIHJldHVybiBbJ3RleHQnLCdpbWFnZSddOwp9OwoKLy8gQ2F0YWxvZyBtZW1iZXJzaGlwIGlzIG5vdCBwcm9vZiB0aGF0IGEgbW9kZWwgaXMgbG9hZGVkIGludG8gbWVtb3J5Lgpjb25zdCBkaXNjb3Zlck1vZGVscyA9IGFzeW5jIGZ1bmN0aW9uIGRpc2NvdmVyTW9kZWxzKGJhc2VVUkwsIGhlYWRlcnMgPSB7fSkgewogICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaChiYXNlVVJMICsgJy9tb2RlbHMnLCB7IGhlYWRlcnMsIHNpZ25hbDogQWJvcnRTaWduYWwudGltZW91dCgxMDAwMCkgfSk7CiAgICBpZiAoIXJlc3BvbnNlLm9rKSB0aHJvdyBuZXcgRXJyb3IoYE1vZGVsIGNhdGFsb2cgcmV0dXJuZWQgSFRUUCAke3Jlc3BvbnNlLnN0YXR1c31gKTsKICAgIGNvbnN0IGNhdGFsb2cgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7CiAgICBpZiAoIUFycmF5LmlzQXJyYXkoY2F0YWxvZy5kYXRhKSB8fCBjYXRhbG9nLmRhdGEuc29tZShtID0+ICFtIHx8IHR5cGVvZiBtLmlkICE9PSAnc3RyaW5nJyB8fCAhbS5pZCkpIHRocm93IG5ldyBFcnJvcignSW52YWxpZCB1cHN0cmVhbSBtb2RlbCBjYXRhbG9nJyk7CiAgICBpZiAoY2F0YWxvZy5kYXRhLmV2ZXJ5KG0gPT4gdHlwZW9mIG0uY3VycmVudGx5X2xvYWRlZCA9PT0gJ2Jvb2xlYW4nKSkgcmV0dXJuIGNhdGFsb2cuZGF0YTsKICAgIGNvbnN0IHN0YXR1c1Jlc3BvbnNlID0gYXdhaXQgZmV0Y2goYmFzZVVSTCArICcvc3RhdHVzJywgeyBoZWFkZXJzLCBzaWduYWw6IEFib3J0U2lnbmFsLnRpbWVvdXQoMTAwMDApIH0pOwogICAgaWYgKCFzdGF0dXNSZXNwb25zZS5vaykgdGhyb3cgbmV3IEVycm9yKGBDYW5ub3QgdmVyaWZ5IGxvYWRlZCBtb2RlbHM6IHN0YXR1cyBIVFRQICR7c3RhdHVzUmVzcG9uc2Uuc3RhdHVzfWApOwogICAgY29uc3Qgc3RhdHVzID0gYXdhaXQgc3RhdHVzUmVzcG9uc2UuanNvbigpOwogICAgY29uc3QgcnVudGltZSA9IHN0YXR1cy5tb2RlbF9tYW5hZ2VyPy5tb2RlbHM7CiAgICBpZiAoIUFycmF5LmlzQXJyYXkocnVudGltZSkpIHRocm93IG5ldyBFcnJvcignQ2Fubm90IHZlcmlmeSBsb2FkZWQgbW9kZWxzOiBpbnZhbGlkIHJ1bnRpbWUgc3RhdHVzJyk7CiAgICByZXR1cm4gY2F0YWxvZy5kYXRhLm1hcChtb2RlbCA9PiB7CiAgICAgICAgY29uc3Qgc3RhdGUgPSBydW50aW1lLmZpbmQobSA9PiBtLmlkID09PSBtb2RlbC5pZCk7CiAgICAgICAgaWYgKCFzdGF0ZSB8fCB0eXBlb2Ygc3RhdGUubG9hZGVkICE9PSAnYm9vbGVhbicpIHRocm93IG5ldyBFcnJvcignQ2Fubm90IHZlcmlmeSBsb2FkZWQgc3RhdGUgZm9yICcgKyBtb2RlbC5pZCk7CiAgICAgICAgcmV0dXJuIHsgLi4ubW9kZWwsIGNhcGFiaWxpdGllczogbW9kZWwuY2FwYWJpbGl0aWVzID8/IHN0YXRlLmNhcGFiaWxpdGllcywgbW9kYWxpdGllczogbW9kZWwubW9kYWxpdGllcyA/PyBzdGF0ZS5tb2RhbGl0aWVzLCBpbnB1dDogbW9kZWwuaW5wdXQgPz8gc3RhdGUuaW5wdXQsIHN1cHBvcnRzX3Zpc2lvbjogbW9kZWwuc3VwcG9ydHNfdmlzaW9uID8/IHN0YXRlLnN1cHBvcnRzX3Zpc2lvbiwgY3VycmVudGx5X2xvYWRlZDogc3RhdGUubG9hZGVkIH07CiAgICB9KTsKfTsKCi8vIEJ1aWxkIGZyZXNoIExBTiBjb25maWd1cmF0aW9ucyBvbiB0aGUgdGFyZ2V0IG1hY2hpbmUsIHdpdGhvdXQgY3JlZGVudGlhbHMuCmNvbnN0IGRpc2NvdmVyTG9jYWwgPSBhc3luYyBmdW5jdGlvbiBsb2NhbENvbmZpZyhiYXNlVVJMKSB7CiAgICBjb25zdCBjYXRhbG9nID0gYXdhaXQgZGlzY292ZXJNb2RlbHMoYmFzZVVSTCk7CiAgICBjb25zdCBtb2RlbHMgPSBjYXRhbG9nLmZpbHRlcihtID0+IG0uY3VycmVudGx5X2xvYWRlZCA9PT0gdHJ1ZSk7CiAgICBpZiAoIW1vZGVscy5sZW5ndGgpIHRocm93IG5ldyBFcnJvcignTm8gbW9kZWxzIGFyZSBsb2FkZWQgb24gdGhlIGxvY2FsIEFJIHNlcnZlci4gTG9hZCBhIG1vZGVsIGFuZCByZXJ1bi4nKTsKICAgIGNvbnN0IGlkcyA9IG5ldyBTZXQoKTsKICAgIGNvbnN0IHBpTW9kZWxzID0gbW9kZWxzLmZpbHRlcihtID0+ICFpZHMuaGFzKG0uaWQpICYmIGlkcy5hZGQobS5pZCkpLm1hcChtID0+IHsKICAgICAgICBjb25zdCBjdHggPSBOdW1iZXIobS5tYXhfY29udGV4dF9sZW5ndGgpID4gMCA/IE51bWJlcihtLm1heF9jb250ZXh0X2xlbmd0aCkgOiAxMjgwMDA7CiAgICAgICAgY29uc3QgY2FwcyA9IEFycmF5LmlzQXJyYXkobS5jYXBhYmlsaXRpZXMpID8gbS5jYXBhYmlsaXRpZXMgOiBbXTsKICAgICAgICByZXR1cm4geyBpZDogbS5pZCwgbmFtZTogbS5pZC5zcGxpdCgnLycpLnBvcCgpLCByZWFzb25pbmc6IGNhcHMuaW5jbHVkZXMoJ3JlYXNvbmluZycpLCBpbnB1dDogbW9kZWxJbnB1dChtKSwgY29udGV4dFdpbmRvdzogY3R4LCBtYXhUb2tlbnM6IE1hdGgubWluKDMyNzY4LCBNYXRoLmZsb29yKGN0eCAvIDQpKSB9OwogICAgfSk7CiAgICBjb25zdCBvcGVuTW9kZWxzID0gT2JqZWN0LmZyb21FbnRyaWVzKHBpTW9kZWxzLm1hcCgobSwgaSkgPT4gWydtb2RlbC0nICsgKGkgKyAxKSwgeyBpZDogbS5pZCwgbmFtZTogbS5uYW1lLCBhdHRhY2htZW50OiBtLmlucHV0LmluY2x1ZGVzKCdpbWFnZScpLCBtb2RhbGl0aWVzOiB7IGlucHV0OiBtLmlucHV0LCBvdXRwdXQ6IFsndGV4dCddIH0sIGxpbWl0OiB7IGNvbnRleHQ6IG0uY29udGV4dFdpbmRvdywgb3V0cHV0OiBtLm1heFRva2VucyB9IH1dKSk7CiAgICAvLyBQaSByZXF1aXJlcyBhIG5vbmVtcHR5IGFwaUtleSB2YWx1ZSBldmVuIGZvciBzZXJ2ZXJzIHdpdGhvdXQgYXV0aGVudGljYXRpb24uCiAgICAvLyAibG9jYWwiIGlzIGEgcHVibGljIHBsYWNlaG9sZGVyLCBuZXZlciBhIFN5bnRobG8gb3IgYmFja2VuZCBjcmVkZW50aWFsLgogICAgcmV0dXJuIHsKICAgICAgICBvcGVuY29kZTogeyAnJHNjaGVtYSc6ICdodHRwczovL29wZW5jb2RlLmFpL2NvbmZpZy5qc29uJywgbW9kZWw6ICdsb2NhbC1zeW50aG8vbW9kZWwtMScsIHByb3ZpZGVyOiB7ICdsb2NhbC1zeW50aG8nOiB7IG5wbTogJ0BhaS1zZGsvb3BlbmFpLWNvbXBhdGlibGUnLCBuYW1lOiAnTG9jYWwgbmV0d29yayBBSScsIG9wdGlvbnM6IHsgYmFzZVVSTCwgdGltZW91dDogMTUwMDAwMCB9LCBtb2RlbHM6IG9wZW5Nb2RlbHMgfSB9IH0sCiAgICAgICAgcGk6IHsgcHJvdmlkZXJzOiB7ICdsb2NhbC1zeW50aG8nOiB7IGJhc2VVcmw6IGJhc2VVUkwsIGFwaTogJ29wZW5haS1jb21wbGV0aW9ucycsIGFwaUtleTogJ2xvY2FsJywgY29tcGF0OiB7IHN1cHBvcnRzRGV2ZWxvcGVyUm9sZTogZmFsc2UsIHN1cHBvcnRzUmVhc29uaW5nRWZmb3J0OiBmYWxzZSwgc3VwcG9ydHNVc2FnZUluU3RyZWFtaW5nOiBmYWxzZSwgbWF4VG9rZW5zRmllbGQ6ICdtYXhfdG9rZW5zJyB9LCBtb2RlbHM6IHBpTW9kZWxzIH0gfSB9LAogICAgICAgIHBpU2V0dGluZ3M6IHsgZGVmYXVsdFByb3ZpZGVyOiAnbG9jYWwtc3ludGhvJywgZGVmYXVsdE1vZGVsOiBwaU1vZGVsc1swXS5pZCB9CiAgICB9Owp9OwoKY29uc3QgZ2VuZXJhdGVkID0gYXdhaXQgZGlzY292ZXJMb2NhbCgiaHR0cDovLzE5Mi4xNjguMTAwLjI0Mjo4MDAwL3YxIik7CmdlbmVyYXRlZC5yZXBsYWNlQWxsID0gZmFsc2U7CmNvbnN0IHJlcGxhY2VBbGwgPSBnZW5lcmF0ZWQucmVwbGFjZUFsbCA9PT0gdHJ1ZTsKY29uc3QgcHJvdmlkZXJJZCA9IGdlbmVyYXRlZC5waVNldHRpbmdzLmRlZmF1bHRQcm92aWRlcjsKY29uc3Qgb2JqZWN0ID0gdiA9PiB2ICE9PSBudWxsICYmIHR5cGVvZiB2ID09PSAnb2JqZWN0JyAmJiAhQXJyYXkuaXNBcnJheSh2KTsKY29uc3QgdXNlckhvbWUgPSBwcm9jZXNzLmVudi5TWU5USExPX0hPTUUgfHwgb3MuaG9tZWRpcigpOwpjb25zdCBvcGVuRGlyID0gcGF0aC5qb2luKHByb2Nlc3MuZW52LlhER19DT05GSUdfSE9NRSB8fCBwYXRoLmpvaW4odXNlckhvbWUsICcuY29uZmlnJyksICdvcGVuY29kZScpOwpjb25zdCBwaURpciA9IHByb2Nlc3MuZW52LlBJX0NPRElOR19BR0VOVF9ESVIgfHwgcGF0aC5qb2luKHVzZXJIb21lLCAnLnBpJywgJ2FnZW50Jyk7CmNvbnN0IHRhcmdldHMgPSBbcGF0aC5qb2luKG9wZW5EaXIsICdvcGVuY29kZS5qc29uJyksIHBhdGguam9pbihwaURpciwgJ21vZGVscy5qc29uJyksIHBhdGguam9pbihwaURpciwgJ3NldHRpbmdzLmpzb24nKV07CmNvbnN0IGpzb25jID0gcGF0aC5qb2luKG9wZW5EaXIsICdvcGVuY29kZS5qc29uYycpOwppZiAoZnMuZXhpc3RzU3luYyhqc29uYykgJiYgIXJlcGxhY2VBbGwpIHsKICAgIHRocm93IG5ldyBFcnJvcignT3BlbkNvZGUgdXNlcyBvcGVuY29kZS5qc29uYyBoZXJlLiBNZXJnZSB0aGUgU3ludGhsbyBwcm92aWRlciBmcm9tIHRoZSBkYXNoYm9hcmQgY29uZmlnIHByZXZpZXcgaW50byB0aGF0IGZpbGUgYmVmb3JlIHVzaW5nIHRoaXMgdXBkYXRlci4gTm8gY29uZmlnIGZpbGVzIHdlcmUgY2hhbmdlZC4nKTsKfQpmdW5jdGlvbiByZWFkKGZpbGUpIHsKICAgIGlmICghZnMuZXhpc3RzU3luYyhmaWxlKSkgcmV0dXJuIHt9OwogICAgaWYgKGZzLmxzdGF0U3luYyhmaWxlKS5pc1N5bWJvbGljTGluaygpKSB0aHJvdyBuZXcgRXJyb3IoYFJlZnVzaW5nIHRvIHJlcGxhY2UgYSBzeW1saW5rOiAke2ZpbGV9YCk7CiAgICBpZiAocmVwbGFjZUFsbCkgcmV0dXJuIHt9OwogICAgdHJ5IHsKICAgICAgICBjb25zdCBkYXRhID0gSlNPTi5wYXJzZShmcy5yZWFkRmlsZVN5bmMoZmlsZSwgJ3V0ZjgnKS5yZXBsYWNlKC9eXHVGRUZGLywgJycpKTsKICAgICAgICBpZiAoIW9iamVjdChkYXRhKSkgdGhyb3cgbmV3IEVycm9yKCdleHBlY3RlZCBhIEpTT04gb2JqZWN0Jyk7CiAgICAgICAgcmV0dXJuIGRhdGE7CiAgICB9IGNhdGNoIChlcnJvcikgeyB0aHJvdyBuZXcgRXJyb3IoYENhbm5vdCBtZXJnZSAke2ZpbGV9OiAke2Vycm9yLm1lc3NhZ2V9LiBGaXggaXQgYW5kIHJlcnVuOyBubyBjb25maWcgZmlsZXMgd2VyZSBjaGFuZ2VkLmApOyB9Cn0KY29uc3QgW29wZW4sIHBpLCBzZXR0aW5nc10gPSB0YXJnZXRzLm1hcChyZWFkKTsKZm9yIChjb25zdCBbdmFsdWUsIGxhYmVsXSBvZiBbW29wZW4ucHJvdmlkZXIsICdPcGVuQ29kZSBwcm92aWRlciddLCBbcGkucHJvdmlkZXJzLCAnUGkgcHJvdmlkZXJzJ11dKSB7CiAgICBpZiAodmFsdWUgIT09IHVuZGVmaW5lZCAmJiAhb2JqZWN0KHZhbHVlKSkgdGhyb3cgbmV3IEVycm9yKGAke2xhYmVsfSBtdXN0IGJlIGFuIG9iamVjdC4gTm8gY29uZmlnIGZpbGVzIHdlcmUgY2hhbmdlZC5gKTsKfQpjb25zdCBvcGVuUHJvdmlkZXIgPSBnZW5lcmF0ZWQub3BlbmNvZGUucHJvdmlkZXJbcHJvdmlkZXJJZF07CmNvbnN0IHBpUHJvdmlkZXIgPSBnZW5lcmF0ZWQucGkucHJvdmlkZXJzW3Byb3ZpZGVySWRdOwoKCi8vIFJlbW92ZSBvdXIgbmFtZWQgcHJvdmlkZXJzIGFuZCBhbGlhc2VzIHBvaW50aW5nIGF0IHRoaXMgQUkgc2VydmljZSBvbmx5LgpmdW5jdGlvbiBtYW5hZ2VkKGlkLCBwcm92aWRlcikgewogICAgaWYgKFsnc3ludGhsbycsICdsb2NhbC1zeW50aG8nLCAnbG9jYWwtc3ludGhsbyddLmluY2x1ZGVzKGlkKSkgcmV0dXJuIHRydWU7CiAgICBjb25zdCBhZGRyZXNzID0gcHJvdmlkZXI/Lm9wdGlvbnM/LmJhc2VVUkwgfHwgcHJvdmlkZXI/LmJhc2VVcmwgfHwgcHJvdmlkZXI/LmJhc2VVUkw7CiAgICB0cnkgewogICAgICAgIGNvbnN0IHVybCA9IG5ldyBVUkwoYWRkcmVzcyk7CiAgICAgICAgcmV0dXJuIFsnc3ludGhsby5jb20nLCAnd3d3LnN5bnRobG8uY29tJ10uaW5jbHVkZXModXJsLmhvc3RuYW1lKSB8fCAodXJsLmhvc3RuYW1lID09PSAnMTkyLjE2OC4xMDAuMjQyJyAmJiB1cmwucG9ydCA9PT0gJzgwMDAnKTsKICAgIH0gY2F0Y2ggeyByZXR1cm4gZmFsc2U7IH0KfQpjb25zdCByZW1vdmVkT3BlbiA9IE9iamVjdC5rZXlzKG9wZW4ucHJvdmlkZXIgfHwge30pLmZpbHRlcihpZCA9PiBtYW5hZ2VkKGlkLCBvcGVuLnByb3ZpZGVyW2lkXSkpOwpjb25zdCByZW1vdmVkUGkgPSBPYmplY3Qua2V5cyhwaS5wcm92aWRlcnMgfHwge30pLmZpbHRlcihpZCA9PiBtYW5hZ2VkKGlkLCBwaS5wcm92aWRlcnNbaWRdKSk7CmNvbnN0IGtlZXAgPSAocHJvdmlkZXJzLCByZW1vdmVkKSA9PiBPYmplY3QuZnJvbUVudHJpZXMoT2JqZWN0LmVudHJpZXMocHJvdmlkZXJzIHx8IHt9KS5maWx0ZXIoKFtpZF0pID0+ICFyZW1vdmVkLmluY2x1ZGVzKGlkKSkpOwpjb25zdCBjbGVhbk9wZW4gPSB7IC4uLm9wZW4gfTsKaWYgKHR5cGVvZiBjbGVhbk9wZW4uc21hbGxfbW9kZWwgPT09ICdzdHJpbmcnICYmIHJlbW92ZWRPcGVuLnNvbWUoaWQgPT4gY2xlYW5PcGVuLnNtYWxsX21vZGVsLnN0YXJ0c1dpdGgoaWQgKyAnLycpKSkgZGVsZXRlIGNsZWFuT3Blbi5zbWFsbF9tb2RlbDsKZm9yIChjb25zdCBmaWVsZCBvZiBbJ2VuYWJsZWRfcHJvdmlkZXJzJywgJ2Rpc2FibGVkX3Byb3ZpZGVycyddKSB7CiAgICBpZiAoQXJyYXkuaXNBcnJheShjbGVhbk9wZW5bZmllbGRdKSkgewogICAgICAgIGNsZWFuT3BlbltmaWVsZF0gPSBjbGVhbk9wZW5bZmllbGRdLmZpbHRlcihpZCA9PiAhcmVtb3ZlZE9wZW4uaW5jbHVkZXMoaWQpKTsKICAgICAgICBpZiAoZmllbGQgPT09ICdlbmFibGVkX3Byb3ZpZGVycycgJiYgIWNsZWFuT3BlbltmaWVsZF0uaW5jbHVkZXMocHJvdmlkZXJJZCkpIGNsZWFuT3BlbltmaWVsZF0ucHVzaChwcm92aWRlcklkKTsKICAgIH0KfQpjb25zdCBjbGVhblNldHRpbmdzID0geyAuLi5zZXR0aW5ncyB9OwppZiAoQXJyYXkuaXNBcnJheShjbGVhblNldHRpbmdzLmVuYWJsZWRNb2RlbHMpKSBjbGVhblNldHRpbmdzLmVuYWJsZWRNb2RlbHMgPSBjbGVhblNldHRpbmdzLmVuYWJsZWRNb2RlbHMuZmlsdGVyKGlkID0+IHR5cGVvZiBpZCAhPT0gJ3N0cmluZycgfHwgIXJlbW92ZWRQaS5zb21lKHAgPT4gaWQuc3RhcnRzV2l0aChwICsgJy8nKSkpOwpjb25zdCBuZXh0ID0gWwogICAgeyAuLi5jbGVhbk9wZW4sICckc2NoZW1hJzogZ2VuZXJhdGVkLm9wZW5jb2RlLiRzY2hlbWEsIG1vZGVsOiBnZW5lcmF0ZWQub3BlbmNvZGUubW9kZWwsIHByb3ZpZGVyOiB7IC4uLmtlZXAob3Blbi5wcm92aWRlciwgcmVtb3ZlZE9wZW4pLCBbcHJvdmlkZXJJZF06IG9wZW5Qcm92aWRlciB9IH0sCiAgICB7IC4uLnBpLCBwcm92aWRlcnM6IHsgLi4ua2VlcChwaS5wcm92aWRlcnMsIHJlbW92ZWRQaSksIFtwcm92aWRlcklkXTogcGlQcm92aWRlciB9IH0sCiAgICB7IC4uLmNsZWFuU2V0dGluZ3MsIC4uLmdlbmVyYXRlZC5waVNldHRpbmdzIH0KXTsKLy8gSW4gcmVzZXQgbW9kZSwgYmFjayB1cCBKU09OQyB0b28sIHRoZW4gcmVtb3ZlIGl0IHNvIGl0IGNhbm5vdCBvdmVycmlkZSBKU09OLgppZiAocmVwbGFjZUFsbCAmJiBmcy5leGlzdHNTeW5jKGpzb25jKSAmJiBmcy5sc3RhdFN5bmMoanNvbmMpLmlzU3ltYm9saWNMaW5rKCkpIHRocm93IG5ldyBFcnJvcignUmVmdXNpbmcgdG8gcmVwbGFjZSBhIHN5bWxpbms6ICcgKyBqc29uYyk7CmNvbnN0IHN1ZmZpeCA9IERhdGUubm93KCkgKyAnLScgKyBjcnlwdG8ucmFuZG9tQnl0ZXMoNCkudG9TdHJpbmcoJ2hleCcpOwovLyBGaW5pc2ggYWxsIGJhY2t1cHMgYmVmb3JlIHJlcGxhY2luZyBhbnkgY29uZmlndXJhdGlvbi4KZm9yIChjb25zdCBmaWxlIG9mIFsuLi50YXJnZXRzLCAuLi4ocmVwbGFjZUFsbCA/IFtqc29uY10gOiBbXSldKSB7CiAgICBpZiAoIWZzLmV4aXN0c1N5bmMoZmlsZSkpIGNvbnRpbnVlOwogICAgY29uc3QgYmFja3VwID0gZmlsZSArICcuc3ludGhsby1iYWNrdXAtJyArIHN1ZmZpeDsKICAgIGZzLmNvcHlGaWxlU3luYyhmaWxlLCBiYWNrdXAsIGZzLmNvbnN0YW50cy5DT1BZRklMRV9FWENMKTsKICAgIGZzLmNobW9kU3luYyhiYWNrdXAsIDBvNjAwKTsKICAgIGNvbnNvbGUubG9nKCdCYWNrdXA6ICcgKyBiYWNrdXApOwp9CmZvciAobGV0IGkgPSAwOyBpIDwgdGFyZ2V0cy5sZW5ndGg7IGkrKykgewogICAgY29uc3QgZmlsZSA9IHRhcmdldHNbaV07CiAgICBmcy5ta2RpclN5bmMocGF0aC5kaXJuYW1lKGZpbGUpLCB7IHJlY3Vyc2l2ZTogdHJ1ZSwgbW9kZTogMG83MDAgfSk7CiAgICBjb25zdCB0bXAgPSBmaWxlICsgJy5zeW50aGxvLScgKyBzdWZmaXg7CiAgICB0cnkgewogICAgICAgIGZzLndyaXRlRmlsZVN5bmModG1wLCBKU09OLnN0cmluZ2lmeShuZXh0W2ldLCBudWxsLCAyKSArICdcbicsIHsgbW9kZTogMG82MDAsIGZsYWc6ICd3eCcgfSk7CiAgICAgICAgZnMucmVuYW1lU3luYyh0bXAsIGZpbGUpOwogICAgfSBmaW5hbGx5IHsgaWYgKGZzLmV4aXN0c1N5bmModG1wKSkgZnMudW5saW5rU3luYyh0bXApOyB9CiAgICBjb25zb2xlLmxvZygnQ29uZmlndXJlZDogJyArIGZpbGUpOwp9CmlmIChyZXBsYWNlQWxsICYmIGZzLmV4aXN0c1N5bmMoanNvbmMpKSBmcy51bmxpbmtTeW5jKGpzb25jKTsKY29uc29sZS5sb2coJ1JlYWR5LiBPcGVuIGEgbmV3IHRlcm1pbmFsIGluIHlvdXIgcHJvamVjdCwgdGhlbiBydW4gcGkgb3Igb3BlbmNvZGUuJyk7Cgp9KSgpLmNhdGNoKGVycm9yID0+IHsgY29uc29sZS5lcnJvcihlcnJvci5tZXNzYWdlKTsgcHJvY2Vzcy5leGl0Q29kZSA9IDE7IH0pOw==', 'base64').toString('utf8'))" unset SYNTHLO_TOKEN SYNTHLO_CONFIG API_KEY