Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions assets/install-macos-app.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env bash

# MindSearch macOS desktop app installer.
#
# Usage:
# bash assets/install-macos-app.sh
#
# The installer creates a small .app bundle that opens Terminal and starts
# the MindSearch API plus the Gradio frontend from the current checkout.

set -euo pipefail

APP_NAME="MindSearch"
PRIMARY_INSTALL_DIR="/Applications"
FALLBACK_INSTALL_DIR="${HOME}/Applications"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
ICON_PATH="${PROJECT_ROOT}/assets/mindsearch_openset.png"
API_MODULE="${PROJECT_ROOT}/mindsearch/app.py"
GRADIO_FRONTEND="${PROJECT_ROOT}/frontend/mindsearch_gradio.py"

info() { printf '[MindSearch] %s\n' "$1"; }
warn() { printf '[MindSearch] Warning: %s\n' "$1"; }
fail() { printf '[MindSearch] Error: %s\n' "$1" >&2; exit 1; }

if [[ "$(uname -s)" != "Darwin" ]]; then
fail "This installer only supports macOS."
fi

command -v osacompile >/dev/null 2>&1 || fail "osacompile is required on macOS."

if [ ! -f "${API_MODULE}" ]; then
fail "mindsearch/app.py was not found at ${API_MODULE}."
fi

if [ ! -f "${GRADIO_FRONTEND}" ]; then
fail "frontend/mindsearch_gradio.py was not found at ${GRADIO_FRONTEND}."
fi

project_path_for_applescript="${PROJECT_ROOT}/"
project_path_for_applescript="${project_path_for_applescript//\\/\\\\}"
project_path_for_applescript="${project_path_for_applescript//\"/\\\"}"

TMP_DIR="$(mktemp -d)"
trap 'rm -rf "${TMP_DIR}"' EXIT

info "Building ${APP_NAME}.app..."

cat > "${TMP_DIR}/${APP_NAME}.applescript" <<APPLESCRIPT
on run
set projectPathStr to "${project_path_for_applescript}"
tell application "Terminal"
activate
set apiCommand to "cd " & quoted form of projectPathStr & " && clear && echo 'Starting MindSearch API' && if [ ! -f .env ]; then echo 'Missing .env. Copy .env.example to .env and configure it before launching MindSearch.'; exit 1; fi && if [ -x .venv/bin/python ]; then PY=.venv/bin/python; else PY=python; fi && \$PY -m mindsearch.app --lang en --model_format internlm_server --search_engine DuckDuckGoSearch --asy"
do script apiCommand
delay 1
set frontendCommand to "cd " & quoted form of projectPathStr & " && clear && echo 'Starting MindSearch Gradio frontend' && if [ ! -f .env ]; then echo 'Missing .env. Copy .env.example to .env and configure it before launching MindSearch.'; exit 1; fi && if [ -x .venv/bin/python ]; then PY=.venv/bin/python; else PY=python; fi && \$PY frontend/mindsearch_gradio.py"
do script frontendCommand
end tell
end run
APPLESCRIPT

osacompile -o "${TMP_DIR}/${APP_NAME}.app" "${TMP_DIR}/${APP_NAME}.applescript"

if [ -f "${ICON_PATH}" ] && command -v sips >/dev/null 2>&1 && command -v iconutil >/dev/null 2>&1; then
info "Applying icon from assets/mindsearch_openset.png..."
ICONSET_DIR="${TMP_DIR}/mindsearch.iconset"
mkdir -p "${ICONSET_DIR}"

sips -z 16 16 "${ICON_PATH}" --out "${ICONSET_DIR}/icon_16x16.png" >/dev/null
sips -z 32 32 "${ICON_PATH}" --out "${ICONSET_DIR}/icon_16x16@2x.png" >/dev/null
sips -z 32 32 "${ICON_PATH}" --out "${ICONSET_DIR}/icon_32x32.png" >/dev/null
sips -z 64 64 "${ICON_PATH}" --out "${ICONSET_DIR}/icon_32x32@2x.png" >/dev/null
sips -z 128 128 "${ICON_PATH}" --out "${ICONSET_DIR}/icon_128x128.png" >/dev/null
sips -z 256 256 "${ICON_PATH}" --out "${ICONSET_DIR}/icon_128x128@2x.png" >/dev/null
sips -z 256 256 "${ICON_PATH}" --out "${ICONSET_DIR}/icon_256x256.png" >/dev/null
sips -z 512 512 "${ICON_PATH}" --out "${ICONSET_DIR}/icon_256x256@2x.png" >/dev/null
sips -z 512 512 "${ICON_PATH}" --out "${ICONSET_DIR}/icon_512x512.png" >/dev/null
cp "${ICON_PATH}" "${ICONSET_DIR}/icon_512x512@2x.png"

iconutil -c icns "${ICONSET_DIR}" -o "${TMP_DIR}/mindsearch.icns"
cp "${TMP_DIR}/mindsearch.icns" "${TMP_DIR}/${APP_NAME}.app/Contents/Resources/applet.icns"
else
warn "Icon tooling or assets/mindsearch_openset.png not found; using the default app icon."
fi

install_bundle() {
local install_dir="$1"
local destination="${install_dir}/${APP_NAME}.app"

mkdir -p "${install_dir}"
rm -rf "${destination}"
cp -R "${TMP_DIR}/${APP_NAME}.app" "${destination}"
printf '%s\n' "${destination}"
}

install_path=""
if install_path="$(install_bundle "${PRIMARY_INSTALL_DIR}" 2>/dev/null)"; then
:
else
warn "Could not write to ${PRIMARY_INSTALL_DIR}; installing to ${FALLBACK_INSTALL_DIR}."
install_path="$(install_bundle "${FALLBACK_INSTALL_DIR}")"
fi

info "Installed to ${install_path}"
info "Launch it from Spotlight, Launchpad, Finder, or with: open '${install_path}'"
info "The app starts the API server and the Gradio frontend in Terminal."
info "If you move this checkout, run this installer again so the app points to the new path."