diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..0eb22b1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Report a problem with the installer +title: "[Bug] " +labels: bug +assignees: "" +--- + +## Describe the bug + +A clear and concise description of what went wrong. + +## Steps to reproduce + +1. Run `bash setup-openwhispr.sh` +2. Choose option ... +3. See error ... + +## Expected behaviour + +What you expected to happen. + +## Environment + +- **Distro**: (e.g., Ubuntu 24.04) +- **Architecture**: (e.g., x86_64 / aarch64) +- **Session type**: (X11 / Wayland) +- **Desktop**: (GNOME / KDE / other) +- **OpenWhispr version**: (if already installed) +- **Script version**: (run `head -5 setup-openwhispr.sh`) + +## Terminal output + +``` +Paste relevant output here +``` + +## Additional context + +Anything else that might help. diff --git a/.github/ISSUE_TEMPLATE/distro_report.md b/.github/ISSUE_TEMPLATE/distro_report.md new file mode 100644 index 0000000..818f40e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/distro_report.md @@ -0,0 +1,33 @@ +--- +name: Distro compatibility report +about: Report whether the installer works on your distro +title: "[Distro] " +labels: distro-report +assignees: "" +--- + +## Distribution details + +- **Distro**: (e.g., Pop!_OS 24.04) +- **Based on**: (e.g., Ubuntu / Debian / Arch / independent) +- **Architecture**: (x86_64 / aarch64) +- **Session type**: (X11 / Wayland) +- **Desktop environment**: (GNOME / KDE / Sway / other) + +## Result + +- [ ] Script detected distro correctly +- [ ] OpenWhispr installed successfully +- [ ] Paste dependencies installed correctly +- [ ] API keys stored correctly +- [ ] OpenWhispr launched after install + +## Notes + +Any issues, workarounds, or suggestions for this distro. + +## Terminal output + +``` +Paste full script output here +``` diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..d2bfe03 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,19 @@ +## What does this PR do? + +Brief description of the change. + +## Checklist + +- [ ] `bash -n setup-openwhispr.sh` passes (no syntax errors) +- [ ] ShellCheck passes with no new warnings +- [ ] Tested on at least one supported distro +- [ ] Updated CHANGELOG.md if user-facing +- [ ] No hardcoded paths or usernames (uses YOUR_USERNAME placeholder or is generic) + +## Testing + +Describe how you tested this change. + +## Related issues + +Closes # diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..7fe490b --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: ShellCheck + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + shellcheck: + name: ShellCheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install ShellCheck + run: sudo apt-get install -y shellcheck + + - name: Run ShellCheck + run: shellcheck -x setup-openwhispr.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..63b0393 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# OS +.DS_Store +Thumbs.db + +# Editor +*.swp +*.swo +*~ +.vscode/ +.idea/ + +# Temp +/tmp/ +*.bak +*.tmp diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..799adfa --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/), +and this project adheres to [Semantic Versioning](https://semver.org/). + +## [1.0.0] — 2026-03-25 + +### Added + +- Single-file bash installer `setup-openwhispr.sh` +- Automatic distro detection (Ubuntu, Debian, Mint, Fedora, openSUSE, Arch) +- Architecture detection (amd64, arm64) +- GitHub releases integration — fetches latest OpenWhispr automatically +- Paste dependency detection (xdotool / kdotool / wtype / D-Bus) +- STT provider selection: local Whisper/Parakeet, Groq, OpenAI Realtime +- AI cleanup provider selection: OpenAI, Anthropic, Groq, Ollama, skip +- Safe API key storage in `~/.bashrc` (never overwrites existing keys) +- Idempotent — safe to re-run, skips already-installed versions +- ShellCheck CI via GitHub Actions +- README with cost table, privacy table, and one-command install +- Issue templates for bug reports and distro compatibility reports +- Pull request template diff --git a/GITHUB_SETUP.md b/GITHUB_SETUP.md new file mode 100644 index 0000000..3e05bf5 --- /dev/null +++ b/GITHUB_SETUP.md @@ -0,0 +1,88 @@ +# GitHub Repository Setup Guide + +Step-by-step instructions to publish this project on GitHub. + +## 1. Create a new GitHub repository + +Go to [github.com/new](https://github.com/new) and create a repository: + +- **Name**: `openwhispr-easy-setup` +- **Description**: `One-command installer for OpenWhispr on Linux — distro detection, dependencies, API keys, first-run setup` +- **Visibility**: Public +- **Do NOT** initialize with README, .gitignore, or license (we already have them) + +## 2. Push local files + +```bash +cd openwhispr-easy-setup +git init +git add . +git commit -m "Initial release v1.0.0" +git branch -M main +git remote add origin https://github.com/YOUR_USERNAME/openwhispr-easy-setup.git +git push -u origin main +``` + +## 3. Replace YOUR_USERNAME placeholder + +Replace `YOUR_USERNAME` with your actual GitHub username in all files: + +```bash +sed -i 's/YOUR_USERNAME/your-actual-username/g' setup-openwhispr.sh README.md GITHUB_SETUP.md LICENSE +``` + +Then commit: + +```bash +git add -A +git commit -m "Replace YOUR_USERNAME with actual username" +git push +``` + +## 4. Tag the v1.0.0 release + +```bash +git tag -a v1.0.0 -m "v1.0.0 — Initial release" +git push origin v1.0.0 +``` + +Then go to **Releases → Draft a new release**: +- **Tag**: `v1.0.0` +- **Title**: `v1.0.0 — Initial release` +- **Description**: Copy the [CHANGELOG.md](CHANGELOG.md) entries for v1.0.0 +- Click **Publish release** + +## 5. Add GitHub topics + +Go to your repository page → click the gear icon next to **About** → add these topics: + +``` +linux +voice-dictation +openwhispr +speech-to-text +whisper +easy-install +``` + +## 6. Set repository description + +In the same **About** section, set the description to: + +> One-command installer for OpenWhispr on Linux — distro detection, dependencies, API keys, first-run setup + +## 7. Verify + +- [ ] README renders correctly with badges +- [ ] ShellCheck CI runs on push (check **Actions** tab) +- [ ] Issue templates appear when creating new issues +- [ ] PR template appears when opening a pull request +- [ ] `bash <(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/openwhispr-easy-setup/main/setup-openwhispr.sh)` works + +## Done! + +Share the one-liner with anyone who wants to install OpenWhispr: + +```bash +bash <(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/openwhispr-easy-setup/main/setup-openwhispr.sh) +``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9eff88f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 YOUR_USERNAME + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..70e56a9 --- /dev/null +++ b/README.md @@ -0,0 +1,114 @@ +# OpenWhispr Easy Setup + +[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![ShellCheck](https://github.com/YOUR_USERNAME/openwhispr-easy-setup/actions/workflows/lint.yml/badge.svg)](https://github.com/YOUR_USERNAME/openwhispr-easy-setup/actions/workflows/lint.yml) + +**One-command installer for [OpenWhispr](https://github.com/OpenWhispr/openwhispr) on Linux** — distro detection, dependencies, API keys, first-run setup. + +> *Your voice stays on your machine. Always. The only question is whether you want AI to clean up the text after transcription.* + +## Quick install + +```bash +bash <(curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/openwhispr-easy-setup/main/setup-openwhispr.sh) +``` + +Or clone and run: + +```bash +git clone https://github.com/YOUR_USERNAME/openwhispr-easy-setup.git +cd openwhispr-easy-setup +bash setup-openwhispr.sh +``` + +## What it does + +| Step | Action | Details | +|------|--------|---------| +| 1/8 | **Intro** | Explains OpenWhispr and the privacy model | +| 2/8 | **Detect distro + arch** | Ubuntu, Debian, Mint, Fedora, openSUSE, Arch — amd64/arm64 | +| 3/8 | **Install OpenWhispr** | Fetches latest release from GitHub, picks correct package | +| 4/8 | **Paste dependencies** | xdotool (X11), wtype (Wayland), kdotool (KDE), or D-Bus (GNOME) | +| 5/8 | **STT provider** | Local Whisper/Parakeet, Groq, or OpenAI Realtime | +| 6/8 | **AI cleanup** | OpenAI, Anthropic, Groq, Ollama, or skip | +| 7/8 | **First launch** | Opens OpenWhispr for model download and hotkey setup | +| 8/8 | **Summary** | Prints config, useful commands, and next steps | + +## STT options + +| Option | Method | Streaming | Cost | Audio sent to | +|--------|--------|-----------|------|---------------| +| 1 (default) | Local Whisper/Parakeet | 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. + +## AI cleanup options + +| Provider | Model | Per use | 50/day/mo | 200/day/mo | +|----------|-------|---------|-----------|------------| +| OpenAI | gpt-4o-mini-2024-07-18 | $0.000058 | $0.09 | $0.35 | +| Anthropic | claude-haiku-4-5 | $0.000450 | $0.68 | $2.70 | +| Groq | llama-3.3-70b-versatile | Free* | Free* | Free* | +| Ollama | Local (fully offline) | Free | Free | Free | +| Skip | Raw transcription only | Free | Free | Free | + +\*Groq free: 1,000 req/day, 100K tokens/day. + +## Privacy + +| Data | Where it goes | Condition | +|------|---------------|-----------| +| **Audio** | Stays on your machine | Default (local Whisper/Parakeet) | +| **Audio** | Groq or OpenAI servers | Only if you choose cloud STT | +| **Text** | AI cleanup provider | Only if you choose AI cleanup | +| **API keys** | `~/.bashrc` on your machine | Never sent anywhere by this script | + +No provider uses your data for training. Cloud data is deleted within 30 days. + +## Useful commands + +```bash +openwhispr # Launch the app +openwhispr --help # Show options +source ~/.bashrc # Reload API keys in current shell +``` + +In the app: +- **Settings → Hotkey** — change your hotkey (default: backtick `` ` ``) +- **Settings → Models** — download better speech models +- **Settings → Processing** — change STT or AI provider + +## Supported distros + +| Distro | Package format | Status | +|--------|---------------|--------| +| Ubuntu / Debian / Mint | `.deb` | Supported | +| Fedora | `.rpm` (dnf) | Supported | +| openSUSE | `.rpm` (zypper) | Supported | +| Arch / Manjaro / EndeavourOS | `.tar.gz` | Supported | + +Architecture: `x86_64` (amd64) and `aarch64` (arm64). + +## Requirements + +- **bash 4+** +- **curl** — for downloading +- **jq** — for parsing GitHub API (script offers to install if missing) + +## Safe to re-run + +The script is idempotent: +- Skips OpenWhispr install if the latest version is already present +- Never overwrites existing API keys in `~/.bashrc` +- Detects already-installed paste dependencies + +## Troubleshooting + +- [OpenWhispr Troubleshooting Guide](https://github.com/OpenWhispr/openwhispr/blob/main/TROUBLESHOOTING.md) +- [Open an issue](https://github.com/YOUR_USERNAME/openwhispr-easy-setup/issues/new/choose) + +## License + +[MIT](LICENSE) diff --git a/setup-openwhispr.sh b/setup-openwhispr.sh new file mode 100755 index 0000000..ca9e0d5 --- /dev/null +++ b/setup-openwhispr.sh @@ -0,0 +1,619 @@ +#!/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 <