Single-file bash installer (setup-openwhispr.sh) with distro detection, dependency installation, STT/AI cleanup provider selection, API key management, and first-run launch. Includes README, LICENSE (MIT), CHANGELOG, .gitignore, ShellCheck CI, issue templates, PR template, and GitHub setup guide. https://claude.ai/code/session_01XKYC1basxdwtHy71tky7xm
620 lines
23 KiB
Bash
Executable File
620 lines
23 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# setup-openwhispr.sh — One-command installer for OpenWhispr on Linux
|
|
# https://github.com/YOUR_USERNAME/openwhispr-easy-setup
|
|
# License: MIT
|
|
|
|
# Do NOT use set -e — we handle errors explicitly
|
|
|
|
readonly SCRIPT_VERSION="1.0.0"
|
|
readonly BASHRC="$HOME/.bashrc"
|
|
readonly MARKER="# Added by openwhispr-easy-setup"
|
|
readonly GITHUB_API="https://api.github.com/repos/OpenWhispr/openwhispr/releases/latest"
|
|
readonly TMP_DIR="/tmp/openwhispr-install"
|
|
|
|
# ── Colours & formatting ─────────────────────────────────────────────────────
|
|
|
|
if [[ -t 1 ]]; then
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
RED='\033[0;31m'
|
|
CYAN='\033[0;36m'
|
|
RESET='\033[0m'
|
|
else
|
|
BOLD='' DIM='' GREEN='' YELLOW='' RED='' CYAN='' RESET=''
|
|
fi
|
|
|
|
# ── Helper functions ──────────────────────────────────────────────────────────
|
|
|
|
# shellcheck disable=SC2059
|
|
info() { printf "${GREEN}[✓]${RESET} %s\n" "$*"; }
|
|
# shellcheck disable=SC2059
|
|
warn() { printf "${YELLOW}[!]${RESET} %s\n" "$*"; }
|
|
# shellcheck disable=SC2059
|
|
err() { printf "${RED}[✗]${RESET} %s\n" "$*" >&2; }
|
|
# shellcheck disable=SC2059
|
|
step() { printf "\n${BOLD}${CYAN}[%s]${RESET} ${BOLD}%s${RESET}\n" "$1" "$2"; }
|
|
# shellcheck disable=SC2059
|
|
divider() { printf "${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n"; }
|
|
|
|
prompt_default() {
|
|
# Usage: prompt_default "prompt text" "default" VARNAME
|
|
local prompt="$1" default="$2" varname="$3" reply
|
|
printf "%s [default: %s]: " "$prompt" "$default"
|
|
read -r reply
|
|
reply="${reply:-$default}"
|
|
printf -v "$varname" '%s' "$reply"
|
|
}
|
|
|
|
prompt_hidden() {
|
|
# Usage: prompt_hidden "prompt text" VARNAME
|
|
local prompt="$1" varname="$2" reply
|
|
printf "%s: " "$prompt"
|
|
read -rs reply
|
|
printf "\n"
|
|
printf -v "$varname" '%s' "$reply"
|
|
}
|
|
|
|
store_api_key() {
|
|
# Usage: store_api_key VARNAME value
|
|
local keyname="$1" keyval="$2"
|
|
if [[ -z "$keyval" ]]; then
|
|
warn "Empty value for $keyname — skipping."
|
|
return 1
|
|
fi
|
|
# Check if already present in bashrc
|
|
if grep -q "^export ${keyname}=" "$BASHRC" 2>/dev/null; then
|
|
info "$keyname already set in $BASHRC — not overwriting."
|
|
else
|
|
printf '\n%s\nexport %s="%s"\n' "$MARKER" "$keyname" "$keyval" >> "$BASHRC"
|
|
info "$keyname stored in $BASHRC"
|
|
fi
|
|
# Also export for current session
|
|
export "$keyname=$keyval"
|
|
}
|
|
|
|
command_exists() { command -v "$1" >/dev/null 2>&1; }
|
|
|
|
# ── Variables set during the run ──────────────────────────────────────────────
|
|
|
|
DISTRO="" # debian, ubuntu, mint, fedora, arch, opensuse
|
|
DISTRO_FAMILY="" # deb, rpm-dnf, rpm-zypper, arch
|
|
ARCH="" # amd64, arm64
|
|
SESSION_TYPE="" # x11, wayland
|
|
DESKTOP="" # GNOME, KDE, etc.
|
|
STT_CHOICE=""
|
|
CLEANUP_CHOICE=""
|
|
STT_LABEL=""
|
|
CLEANUP_LABEL=""
|
|
INSTALLED_VERSION=""
|
|
LATEST_VERSION=""
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# [1/8] Intro
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
step "1/8" "Welcome to OpenWhispr Easy Setup v${SCRIPT_VERSION}"
|
|
divider
|
|
cat <<'INTRO'
|
|
OpenWhispr is an Electron-based voice dictation app for Linux.
|
|
Press a hotkey, speak, and your words appear wherever the cursor is.
|
|
|
|
Your voice stays on your machine. Always.
|
|
The only question is whether you want AI to clean up
|
|
the text after transcription.
|
|
|
|
Speech-to-text (STT) is local Whisper/Parakeet by default.
|
|
Audio never leaves your machine unless YOU explicitly choose a cloud option.
|
|
|
|
This script will:
|
|
1. Detect your distro and architecture
|
|
2. Install OpenWhispr (latest release)
|
|
3. Install paste/typing dependencies
|
|
4. Configure STT provider
|
|
5. Configure AI text cleanup
|
|
6. Launch OpenWhispr for first-run setup
|
|
INTRO
|
|
divider
|
|
printf "\nPress Enter to continue or Ctrl-C to cancel... "
|
|
read -r
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# [2/8] Detect distro + arch
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
step "2/8" "Detecting distribution and architecture"
|
|
|
|
detect_distro() {
|
|
if [[ -f /etc/os-release ]]; then
|
|
# shellcheck source=/dev/null
|
|
. /etc/os-release
|
|
case "${ID,,}" in
|
|
ubuntu) DISTRO="ubuntu"; DISTRO_FAMILY="deb" ;;
|
|
debian) DISTRO="debian"; DISTRO_FAMILY="deb" ;;
|
|
linuxmint) DISTRO="mint"; DISTRO_FAMILY="deb" ;;
|
|
fedora) DISTRO="fedora"; DISTRO_FAMILY="rpm-dnf" ;;
|
|
opensuse*|sles) DISTRO="opensuse"; DISTRO_FAMILY="rpm-zypper" ;;
|
|
arch|manjaro|endeavouros) DISTRO="arch"; DISTRO_FAMILY="arch" ;;
|
|
*)
|
|
err "Unsupported distribution: ${ID} (${PRETTY_NAME:-unknown})"
|
|
err "Supported: Ubuntu, Debian, Mint, Fedora, openSUSE, Arch"
|
|
exit 1
|
|
;;
|
|
esac
|
|
else
|
|
err "Cannot detect distribution (/etc/os-release not found)."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
detect_arch() {
|
|
local machine
|
|
machine="$(uname -m)"
|
|
case "$machine" in
|
|
x86_64) ARCH="amd64" ;;
|
|
aarch64) ARCH="arm64" ;;
|
|
*)
|
|
err "Unsupported architecture: $machine"
|
|
err "Supported: x86_64 (amd64), aarch64 (arm64)"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
detect_distro
|
|
detect_arch
|
|
info "Distro: ${DISTRO} (${DISTRO_FAMILY}) | Arch: ${ARCH}"
|
|
|
|
# ── Check base dependencies ───────────────────────────────────────────────────
|
|
|
|
if ! command_exists curl; then
|
|
err "curl is required but not installed."
|
|
err "Install it with your package manager and re-run this script."
|
|
exit 1
|
|
fi
|
|
|
|
if ! command_exists jq; then
|
|
warn "jq is required but not installed."
|
|
printf "Install jq now? [Y/n]: "
|
|
read -r jq_reply
|
|
jq_reply="${jq_reply:-Y}"
|
|
if [[ "${jq_reply,,}" == "y" ]]; then
|
|
case "$DISTRO_FAMILY" in
|
|
deb) sudo apt update && sudo apt install -y jq ;;
|
|
rpm-dnf) sudo dnf install -y jq ;;
|
|
rpm-zypper) sudo zypper install -y jq ;;
|
|
arch) sudo pacman -S --noconfirm jq ;;
|
|
esac
|
|
if ! command_exists jq; then
|
|
err "Failed to install jq. Please install it manually and re-run."
|
|
exit 1
|
|
fi
|
|
info "jq installed."
|
|
else
|
|
err "jq is required. Please install it and re-run."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# [3/8] Install OpenWhispr
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
step "3/8" "Installing OpenWhispr"
|
|
|
|
# Check currently installed version
|
|
get_installed_version() {
|
|
if command_exists openwhispr; then
|
|
openwhispr --version 2>/dev/null || true
|
|
elif command_exists open-whispr; then
|
|
open-whispr --version 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
INSTALLED_VERSION="$(get_installed_version)"
|
|
|
|
# Fetch latest release info
|
|
info "Fetching latest release info..."
|
|
RELEASE_JSON="$(curl -sf "$GITHUB_API")" || {
|
|
err "Failed to fetch release info from GitHub."
|
|
err "Check your internet connection and try again."
|
|
exit 1
|
|
}
|
|
|
|
LATEST_VERSION="$(printf '%s' "$RELEASE_JSON" | jq -r '.tag_name // empty')"
|
|
if [[ -z "$LATEST_VERSION" ]]; then
|
|
err "Could not determine latest version from GitHub."
|
|
exit 1
|
|
fi
|
|
info "Latest version: ${LATEST_VERSION}"
|
|
|
|
# Strip leading 'v' for comparison
|
|
LATEST_CLEAN="${LATEST_VERSION#v}"
|
|
INSTALLED_CLEAN="${INSTALLED_VERSION#v}"
|
|
|
|
if [[ -n "$INSTALLED_VERSION" && "$INSTALLED_CLEAN" == "$LATEST_CLEAN" ]]; then
|
|
info "OpenWhispr ${LATEST_VERSION} is already installed — skipping."
|
|
else
|
|
if [[ -n "$INSTALLED_VERSION" ]]; then
|
|
info "Upgrading from ${INSTALLED_VERSION} to ${LATEST_VERSION}..."
|
|
fi
|
|
|
|
# Determine asset pattern
|
|
case "$DISTRO_FAMILY" in
|
|
deb) ASSET_EXT=".deb" ;;
|
|
rpm-dnf|rpm-zypper) ASSET_EXT=".rpm" ;;
|
|
arch) ASSET_EXT=".tar.gz" ;;
|
|
esac
|
|
|
|
# Map arch names (assets may use different conventions)
|
|
case "$ARCH" in
|
|
amd64) ARCH_PATTERNS=("amd64" "x86_64" "x64") ;;
|
|
arm64) ARCH_PATTERNS=("arm64" "aarch64") ;;
|
|
esac
|
|
|
|
# Find matching asset URL
|
|
ASSET_URL=""
|
|
for arch_pat in "${ARCH_PATTERNS[@]}"; do
|
|
ASSET_URL="$(printf '%s' "$RELEASE_JSON" | jq -r \
|
|
--arg ext "$ASSET_EXT" --arg arch "$arch_pat" \
|
|
'.assets[] | select(.name | (endswith($ext) and contains($arch))) | .browser_download_url' \
|
|
| head -1)"
|
|
[[ -n "$ASSET_URL" ]] && break
|
|
done
|
|
|
|
if [[ -z "$ASSET_URL" ]]; then
|
|
err "No ${ASSET_EXT} asset found for ${ARCH} in release ${LATEST_VERSION}."
|
|
err "Check: https://github.com/OpenWhispr/openwhispr/releases/latest"
|
|
exit 1
|
|
fi
|
|
|
|
ASSET_NAME="$(basename "$ASSET_URL")"
|
|
info "Downloading ${ASSET_NAME}..."
|
|
|
|
mkdir -p "$TMP_DIR"
|
|
curl --progress-bar -fL -o "${TMP_DIR}/${ASSET_NAME}" "$ASSET_URL" || {
|
|
err "Download failed."
|
|
exit 1
|
|
}
|
|
|
|
info "Installing..."
|
|
case "$DISTRO_FAMILY" in
|
|
deb)
|
|
sudo apt install -y "${TMP_DIR}/${ASSET_NAME}" || {
|
|
err "Installation failed. Try: sudo dpkg -i ${TMP_DIR}/${ASSET_NAME}"
|
|
exit 1
|
|
}
|
|
;;
|
|
rpm-dnf)
|
|
sudo dnf install -y "${TMP_DIR}/${ASSET_NAME}" || {
|
|
err "Installation failed."
|
|
exit 1
|
|
}
|
|
;;
|
|
rpm-zypper)
|
|
sudo zypper install -y "${TMP_DIR}/${ASSET_NAME}" || {
|
|
err "Installation failed."
|
|
exit 1
|
|
}
|
|
;;
|
|
arch)
|
|
sudo mkdir -p /opt/openwhispr
|
|
sudo tar -xzf "${TMP_DIR}/${ASSET_NAME}" -C /opt/openwhispr || {
|
|
err "Extraction failed."
|
|
exit 1
|
|
}
|
|
# Find the binary and symlink
|
|
local_bin="$(find /opt/openwhispr -maxdepth 2 -name 'openwhispr' -o -name 'open-whispr' 2>/dev/null | head -1)"
|
|
if [[ -n "$local_bin" ]]; then
|
|
sudo ln -sf "$local_bin" /usr/local/bin/openwhispr
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
# Clean up
|
|
rm -rf "$TMP_DIR"
|
|
info "OpenWhispr ${LATEST_VERSION} installed successfully."
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# [4/8] Install paste dependencies
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
step "4/8" "Installing paste/typing dependencies"
|
|
|
|
SESSION_TYPE="${XDG_SESSION_TYPE:-}"
|
|
DESKTOP="${XDG_CURRENT_DESKTOP:-}"
|
|
|
|
if [[ -z "$SESSION_TYPE" ]]; then
|
|
# Try to detect
|
|
if [[ -n "${WAYLAND_DISPLAY:-}" ]]; then
|
|
SESSION_TYPE="wayland"
|
|
elif [[ -n "${DISPLAY:-}" ]]; then
|
|
SESSION_TYPE="x11"
|
|
else
|
|
warn "Cannot detect session type (X11/Wayland). Skipping paste deps."
|
|
fi
|
|
fi
|
|
|
|
info "Session: ${SESSION_TYPE:-unknown} | Desktop: ${DESKTOP:-unknown}"
|
|
|
|
install_paste_dep() {
|
|
local pkg="$1"
|
|
if command_exists "$pkg"; then
|
|
info "$pkg is already installed."
|
|
return 0
|
|
fi
|
|
info "Installing $pkg..."
|
|
case "$DISTRO_FAMILY" in
|
|
deb) sudo apt install -y "$pkg" ;;
|
|
rpm-dnf) sudo dnf install -y "$pkg" ;;
|
|
rpm-zypper) sudo zypper install -y "$pkg" ;;
|
|
arch) sudo pacman -S --noconfirm "$pkg" ;;
|
|
esac
|
|
}
|
|
|
|
case "$SESSION_TYPE" in
|
|
x11)
|
|
install_paste_dep "xdotool"
|
|
;;
|
|
wayland)
|
|
if [[ "$DESKTOP" == *"GNOME"* ]]; then
|
|
info "GNOME on Wayland — OpenWhispr uses D-Bus natively. No extra deps."
|
|
elif [[ "$DESKTOP" == *"KDE"* ]]; then
|
|
install_paste_dep "kdotool"
|
|
else
|
|
install_paste_dep "wtype"
|
|
fi
|
|
;;
|
|
*)
|
|
warn "Skipping paste dependency install (unknown session type)."
|
|
;;
|
|
esac
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# [5/8] STT provider
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
step "5/8" "Speech-to-text (STT) provider"
|
|
|
|
cat <<'STT_MENU'
|
|
|
|
How should OpenWhispr transcribe your voice?
|
|
|
|
Option Method Streaming Cost Audio sent to
|
|
─────────────────────────────────────────────────────────────────────────────
|
|
1) Local Whisper/Parakeet (default) no free nobody, ever
|
|
2) Groq whisper-large-v3-turbo no $0.04/hr* Groq servers
|
|
3) OpenAI Realtime API YES $0.06/min OpenAI servers
|
|
|
|
*Groq free tier: ~2,000 audio seconds/day (~600 dictations/day free).
|
|
NOT used for training. 30-day retention max.
|
|
Verify: https://console.groq.com/docs/your-data
|
|
|
|
Streaming (option 3) = text appears word-by-word as you speak.
|
|
Options 1 and 2 transcribe after you stop speaking.
|
|
|
|
All cloud options send audio to a server (like Siri/Google Assistant)
|
|
but unlike Siri/Google: NOT tied to your identity, NOT used for
|
|
training, deleted within 30 days.
|
|
|
|
Best free combo: option 1 (local) + Groq cleanup [step 6]
|
|
Best free cloud combo: option 2 (Groq STT) + Groq cleanup [same key]
|
|
Best streaming: option 3 (OpenAI Realtime) + any cleanup
|
|
|
|
STT_MENU
|
|
|
|
prompt_default "Enter 1-3" "1" STT_CHOICE
|
|
|
|
case "$STT_CHOICE" in
|
|
1)
|
|
STT_LABEL="Local Whisper/Parakeet (offline)"
|
|
info "STT: Local — audio never leaves your machine."
|
|
;;
|
|
2)
|
|
STT_LABEL="Groq whisper-large-v3-turbo (cloud)"
|
|
info "STT: Groq cloud transcription."
|
|
if [[ -n "${GROQ_API_KEY:-}" ]]; then
|
|
info "GROQ_API_KEY already set — reusing."
|
|
else
|
|
prompt_hidden "Enter your Groq API key (from https://console.groq.com)" GROQ_KEY_INPUT
|
|
store_api_key "GROQ_API_KEY" "$GROQ_KEY_INPUT"
|
|
fi
|
|
;;
|
|
3)
|
|
STT_LABEL="OpenAI Realtime API (cloud, streaming)"
|
|
info "STT: OpenAI Realtime (streaming)."
|
|
if [[ -n "${OPENAI_API_KEY:-}" ]]; then
|
|
info "OPENAI_API_KEY already set — reusing."
|
|
else
|
|
prompt_hidden "Enter your OpenAI API key (from https://platform.openai.com/api-keys)" OPENAI_KEY_INPUT
|
|
store_api_key "OPENAI_API_KEY" "$OPENAI_KEY_INPUT"
|
|
fi
|
|
;;
|
|
*)
|
|
warn "Invalid choice '$STT_CHOICE' — defaulting to local."
|
|
STT_CHOICE="1"
|
|
STT_LABEL="Local Whisper/Parakeet (offline)"
|
|
;;
|
|
esac
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# [6/8] AI cleanup provider
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
step "6/8" "AI text cleanup provider"
|
|
|
|
cat <<'CLEANUP_MENU'
|
|
|
|
After transcription, AI can fix grammar, spelling, and filler words.
|
|
Only transcribed TEXT is sent — your voice stays local regardless.
|
|
None of these providers use your text for training.
|
|
|
|
Provider Model Per use 50/day/mo 200/day/mo
|
|
──────────────────────────────────────────────────────────────────────────
|
|
1) OpenAI gpt-4o-mini-2024-07-18 $0.000058 $0.09 $0.35
|
|
2) Anthropic claude-haiku-4-5 $0.000450 $0.68 $2.70
|
|
3) Groq llama-3.3-70b-versatile free* free* free*
|
|
4) Local Ollama (fully offline) free free free
|
|
5) Skip raw transcription only free free free
|
|
|
|
*Groq free: 1,000 req/day, 100K tokens/day. NOT used for training.
|
|
Verify: https://console.groq.com/docs/your-data
|
|
|
|
Pricing based on ~50 words per dictation (~125 input + 65 output tokens).
|
|
Verify current pricing:
|
|
OpenAI: https://openai.com/api/pricing
|
|
Anthropic: https://www.anthropic.com/pricing
|
|
Groq: https://groq.com/pricing
|
|
|
|
CLEANUP_MENU
|
|
|
|
prompt_default "Enter 1-5" "1" CLEANUP_CHOICE
|
|
|
|
case "$CLEANUP_CHOICE" in
|
|
1)
|
|
CLEANUP_LABEL="OpenAI gpt-4o-mini"
|
|
info "Cleanup: OpenAI gpt-4o-mini."
|
|
if [[ -n "${OPENAI_API_KEY:-}" ]]; then
|
|
info "OPENAI_API_KEY already set — reusing."
|
|
else
|
|
prompt_hidden "Enter your OpenAI API key (from https://platform.openai.com/api-keys)" OPENAI_KEY_INPUT
|
|
store_api_key "OPENAI_API_KEY" "$OPENAI_KEY_INPUT"
|
|
fi
|
|
;;
|
|
2)
|
|
CLEANUP_LABEL="Anthropic claude-haiku-4-5"
|
|
info "Cleanup: Anthropic claude-haiku-4-5."
|
|
if [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then
|
|
info "ANTHROPIC_API_KEY already set — reusing."
|
|
else
|
|
prompt_hidden "Enter your Anthropic API key (from https://console.anthropic.com)" ANTHROPIC_KEY_INPUT
|
|
store_api_key "ANTHROPIC_API_KEY" "$ANTHROPIC_KEY_INPUT"
|
|
fi
|
|
;;
|
|
3)
|
|
CLEANUP_LABEL="Groq llama-3.3-70b (free)"
|
|
info "Cleanup: Groq llama-3.3-70b."
|
|
if [[ -n "${GROQ_API_KEY:-}" ]]; then
|
|
info "GROQ_API_KEY already set — reusing."
|
|
else
|
|
prompt_hidden "Enter your Groq API key (from https://console.groq.com)" GROQ_KEY_INPUT
|
|
store_api_key "GROQ_API_KEY" "$GROQ_KEY_INPUT"
|
|
fi
|
|
;;
|
|
4)
|
|
CLEANUP_LABEL="Local Ollama (offline)"
|
|
info "Cleanup: Local Ollama."
|
|
if command_exists ollama; then
|
|
info "Ollama is already installed."
|
|
else
|
|
warn "Ollama is not installed."
|
|
cat <<'OLLAMA_INFO'
|
|
|
|
To install Ollama:
|
|
curl -fsSL https://ollama.ai/install.sh | sh
|
|
|
|
Then pull a model:
|
|
ollama pull llama3.1
|
|
|
|
OLLAMA_INFO
|
|
printf "Install Ollama now? [Y/n]: "
|
|
read -r ollama_reply
|
|
ollama_reply="${ollama_reply:-Y}"
|
|
if [[ "${ollama_reply,,}" == "y" ]]; then
|
|
curl -fsSL https://ollama.ai/install.sh | sh || {
|
|
warn "Ollama install failed. Install manually later."
|
|
}
|
|
else
|
|
warn "Skipping Ollama install — you can install it later."
|
|
fi
|
|
fi
|
|
;;
|
|
5)
|
|
CLEANUP_LABEL="None (raw transcription)"
|
|
info "Cleanup: Skipped — raw transcription only."
|
|
;;
|
|
*)
|
|
warn "Invalid choice '$CLEANUP_CHOICE' — defaulting to OpenAI."
|
|
CLEANUP_CHOICE="1"
|
|
CLEANUP_LABEL="OpenAI gpt-4o-mini"
|
|
if [[ -n "${OPENAI_API_KEY:-}" ]]; then
|
|
info "OPENAI_API_KEY already set — reusing."
|
|
else
|
|
prompt_hidden "Enter your OpenAI API key (from https://platform.openai.com/api-keys)" OPENAI_KEY_INPUT
|
|
store_api_key "OPENAI_API_KEY" "$OPENAI_KEY_INPUT"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# [7/8] First launch
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
step "7/8" "First launch"
|
|
|
|
cat <<'LAUNCH_INFO'
|
|
|
|
OpenWhispr will open now for first-run setup.
|
|
|
|
- It will download speech models (~75-500 MB depending on choice).
|
|
- Set your hotkey in Settings (default: backtick `).
|
|
- The app manages its own autostart.
|
|
|
|
LAUNCH_INFO
|
|
|
|
LAUNCHED=false
|
|
if command_exists openwhispr; then
|
|
openwhispr &>/dev/null &
|
|
LAUNCHED=true
|
|
elif command_exists open-whispr; then
|
|
open-whispr &>/dev/null &
|
|
LAUNCHED=true
|
|
fi
|
|
|
|
if [[ "$LAUNCHED" == true ]]; then
|
|
info "OpenWhispr launched in the background."
|
|
else
|
|
warn "Could not find openwhispr in PATH."
|
|
warn "Try launching manually:"
|
|
warn " openwhispr"
|
|
warn " open-whispr"
|
|
warn " /opt/openwhispr/openwhispr"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# [8/8] Summary
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
step "8/8" "Done!"
|
|
|
|
printf "\n"
|
|
divider
|
|
# shellcheck disable=SC2059
|
|
printf "${BOLD} OpenWhispr is ready${RESET}\n"
|
|
divider
|
|
cat <<EOF
|
|
|
|
Hotkey: backtick (\`) — change in Settings
|
|
STT: ${STT_LABEL}
|
|
AI cleanup: ${CLEANUP_LABEL}
|
|
|
|
API keys stored in: ${BASHRC}
|
|
Apply now: source ${BASHRC}
|
|
|
|
Useful commands:
|
|
openwhispr launch the app
|
|
openwhispr --help show options
|
|
|
|
Settings → Hotkey change your hotkey
|
|
Settings → Models download better speech models
|
|
Settings → Processing change STT or AI provider
|
|
|
|
Troubleshoot: https://github.com/OpenWhispr/openwhispr/blob/main/TROUBLESHOOTING.md
|
|
This installer: https://github.com/YOUR_USERNAME/openwhispr-easy-setup
|
|
|
|
EOF
|
|
divider
|
|
printf "\n"
|