diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 14dba7a..49a0927 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -1,98 +1,13 @@ -name: Release CLI +# Superseded by release.yml which builds both desktop app and CLI. +# This file is kept for reference only — it does not run. +# +# To build CLI-only releases (e.g., for CI/headless), use: +# workflow_dispatch on release.yml with cli-only flag (future enhancement) +name: "[deprecated] Release CLI Only" on: - push: - tags: ['v*'] - -permissions: - contents: write - -jobs: - build: - strategy: - fail-fast: false - matrix: - include: - - target: aarch64-apple-darwin - artifact: syncfu-darwin-arm64 - os: macos-latest - - target: x86_64-apple-darwin - artifact: syncfu-darwin-x86_64 - os: macos-latest - - target: x86_64-unknown-linux-gnu - artifact: syncfu-linux-x86_64 - os: ubuntu-latest - - target: aarch64-unknown-linux-gnu - artifact: syncfu-linux-arm64 - os: ubuntu-latest - - target: x86_64-pc-windows-gnu - artifact: syncfu-windows-x86_64.exe - os: ubuntu-latest - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - with: - targets: ${{ matrix.target }} - - - uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - with: - key: ${{ matrix.target }} - - - name: Install cargo-zigbuild and cross-compile deps - if: runner.os == 'Linux' - run: | - pip3 install ziglang - cargo install cargo-zigbuild --version 0.19.8 --locked - sudo apt-get update - sudo apt-get install -y mingw-w64 - - - name: Install ARM64 cross-linker - if: matrix.target == 'aarch64-unknown-linux-gnu' - run: | - sudo apt-get install -y gcc-aarch64-linux-gnu cmake libclang-dev - echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV - - - name: Build CLI (cross-compile via zigbuild) - if: runner.os == 'Linux' - run: cargo zigbuild --release -p syncfu-cli --target ${{ matrix.target }} - - - name: Build CLI (native) - if: runner.os != 'Linux' - run: cargo build --release -p syncfu-cli --target ${{ matrix.target }} - - - name: Rename binary - run: | - if [[ "${{ matrix.target }}" == *windows* ]]; then - cp target/${{ matrix.target }}/release/syncfu.exe ${{ matrix.artifact }} - else - cp target/${{ matrix.target }}/release/syncfu ${{ matrix.artifact }} - fi - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: ${{ matrix.artifact }} - path: ${{ matrix.artifact }} - - release: - needs: build - runs-on: ubuntu-latest - - steps: - - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - with: - merge-multiple: true - - - name: Generate checksums - run: sha256sum syncfu-* > checksums.txt - - - name: Create release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 - with: - generate_release_notes: true - files: | - syncfu-* - checksums.txt + workflow_dispatch: + inputs: + note: + description: "Deprecated — use the main release.yml workflow instead" + required: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..f1826fa --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,202 @@ +name: Release + +on: + push: + tags: ['v*'] + +permissions: + contents: write + +jobs: + # --- Build CLI binaries for all platforms --- + build-cli: + strategy: + fail-fast: false + matrix: + include: + - target: aarch64-apple-darwin + artifact: syncfu-darwin-arm64 + os: macos-latest + - target: x86_64-apple-darwin + artifact: syncfu-darwin-x86_64 + os: macos-latest + - target: x86_64-unknown-linux-gnu + artifact: syncfu-linux-x86_64 + os: ubuntu-latest + - target: aarch64-unknown-linux-gnu + artifact: syncfu-linux-arm64 + os: ubuntu-latest + - target: x86_64-pc-windows-gnu + artifact: syncfu-windows-x86_64.exe + os: ubuntu-latest + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - uses: Swatinem/rust-cache@v2 + with: + key: cli-${{ matrix.target }} + + - name: Install cargo-zigbuild and cross-compile deps + if: runner.os == 'Linux' + run: | + pip3 install ziglang + cargo install cargo-zigbuild --version 0.19.8 --locked + sudo apt-get update + sudo apt-get install -y mingw-w64 + + - name: Install ARM64 cross-linker + if: matrix.target == 'aarch64-unknown-linux-gnu' + run: | + sudo apt-get install -y gcc-aarch64-linux-gnu cmake libclang-dev + echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV + + - name: Build CLI (cross-compile via zigbuild) + if: runner.os == 'Linux' + run: cargo zigbuild --release -p syncfu-cli --target ${{ matrix.target }} + + - name: Build CLI (native) + if: runner.os != 'Linux' + run: cargo build --release -p syncfu-cli --target ${{ matrix.target }} + + - name: Rename binary + run: | + if [[ "${{ matrix.target }}" == *windows* ]]; then + cp target/${{ matrix.target }}/release/syncfu.exe ${{ matrix.artifact }} + else + cp target/${{ matrix.target }}/release/syncfu ${{ matrix.artifact }} + fi + + - uses: actions/upload-artifact@v4 + with: + name: cli-${{ matrix.artifact }} + path: ${{ matrix.artifact }} + + # --- Build desktop app for macOS, Windows, Linux --- + build-desktop: + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest + target: aarch64-apple-darwin + args: --target aarch64-apple-darwin + - os: macos-latest + target: x86_64-apple-darwin + args: --target x86_64-apple-darwin + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + args: "" + - os: windows-latest + target: x86_64-pc-windows-msvc + args: "" + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - uses: Swatinem/rust-cache@v2 + with: + key: desktop-${{ matrix.target }} + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: desktop/pnpm-lock.yaml + + - name: Install frontend deps + working-directory: desktop + run: pnpm install --frozen-lockfile + + - name: Install Linux dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + + - name: Build desktop app + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + projectPath: desktop + tauriScript: pnpm tauri + args: ${{ matrix.args }} + tagName: ${{ github.ref_name }} + releaseName: "syncfu ${{ github.ref_name }}" + releaseBody: "" + releaseDraft: true + prerelease: false + includeUpdaterJson: false + + # --- Collect all artifacts into a single release --- + release: + needs: [build-cli, build-desktop] + runs-on: ubuntu-latest + + steps: + - uses: actions/download-artifact@v4 + with: + pattern: cli-* + merge-multiple: true + path: cli-artifacts + + - name: Generate checksums for CLI binaries + working-directory: cli-artifacts + run: sha256sum syncfu-* > checksums.txt + + - name: Upload CLI artifacts to release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${GITHUB_REF_NAME}" + # Wait for tauri-action to create the draft release + sleep 10 + RELEASE_ID=$(gh api repos/${{ github.repository }}/releases --jq ".[] | select(.tag_name==\"${TAG}\") | .id" | head -1) + if [ -z "$RELEASE_ID" ]; then + echo "Release not found for tag ${TAG}, creating..." + gh release create "${TAG}" --repo "${{ github.repository }}" --draft --title "syncfu ${TAG}" + fi + gh release upload "${TAG}" cli-artifacts/* --repo "${{ github.repository }}" --clobber + + - name: Publish release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${GITHUB_REF_NAME}" + gh release edit "${TAG}" --repo "${{ github.repository }}" --draft=false \ + --notes "## Install + + **macOS / Linux (one command):** + \`\`\`sh + curl -fsSL https://raw.githubusercontent.com/${{ github.repository }}/main/install.sh | sh + \`\`\` + + **Windows (PowerShell):** + \`\`\`powershell + irm https://raw.githubusercontent.com/${{ github.repository }}/main/install.ps1 | iex + \`\`\` + + ## What's included + - **Desktop app** — tray icon, overlay notifications, HTTP server on port 9868 + - **CLI** — \`syncfu send \"hello\"\` from any terminal or AI agent + + ## Changes + See commit history for details." diff --git a/Cargo.lock b/Cargo.lock index 4bbb7bf..2d3fbc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4602,7 +4602,7 @@ dependencies = [ [[package]] name = "syncfu" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", "async-stream", @@ -4631,7 +4631,7 @@ dependencies = [ [[package]] name = "syncfu-cli" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", "clap", diff --git a/README.md b/README.md index e0fc089..fc3f411 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,28 @@
The notification layer your AI agents are missing.
++ The notification layer your AI agents are missing. +
+ + + ++ Install · + Quick Start · + API · + Use Cases · + Integrations · + syncfu.dev +
+ +--- syncfu is a standalone overlay notification system that sits between your background processes — AI agents, autonomous loops, skills, CI pipelines, cron jobs, anything — and you. It renders always-on-top native notifications that bypass the OS notification center, so nothing gets buried. @@ -30,7 +51,42 @@ syncfu send -t "Loop complete" -p high -i circle-check \ "All 47 tests passing." ``` -Built with Tauri v2 + Rust + React. macOS first, Windows + Linux coming. +- syncfu.dev — because your agents shouldn't have to wait for you to check the terminal. + syncfu.dev — because your agents shouldn't have to wait for you to check the terminal.
diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 953fbed..fce05b0 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "syncfu-cli" -version = "0.2.0" +version = "0.3.0" edition = "2021" description = "CLI for syncfu — send notifications from anywhere" license = "MIT" diff --git a/install.ps1 b/install.ps1 index ff99c20..7722b64 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,12 +1,13 @@ #Requires -Version 5.1 param( - [string]$Version = "" + [string]$Version = "", + [switch]$CliOnly ) $ErrorActionPreference = 'Stop' $Repo = "Zackriya-Solutions/syncfu" -$Artifact = "syncfu-windows-x86_64.exe" +$CliArtifact = "syncfu-windows-x86_64.exe" $BinaryName = "syncfu.exe" # --- Resolve version --- @@ -44,15 +45,16 @@ if ($Version -notmatch '^\d+\.\d+\.\d+$') { exit 1 } -# --- Install directory --- +# --- Install directories --- if ($env:SYNCFU_INSTALL_DIR) { - $InstallDir = $env:SYNCFU_INSTALL_DIR + $CliDir = $env:SYNCFU_INSTALL_DIR } else { - $InstallDir = Join-Path $HOME ".syncfu\bin" + $CliDir = Join-Path $HOME ".syncfu\bin" } -$Url = "https://github.com/$Repo/releases/download/v$Version/$Artifact" -$ChecksumUrl = "https://github.com/$Repo/releases/download/v$Version/checksums.txt" +$BaseUrl = "https://github.com/$Repo/releases/download/v$Version" +# Tauri NSIS installer artifact name +$DesktopArtifact = "syncfu_${Version}_x64-setup.exe" Write-Host "" Write-Host " syncfu installer" -ForegroundColor White @@ -61,80 +63,142 @@ Write-Host " Version: " -ForegroundColor Cyan -NoNewline Write-Host "v$Version" Write-Host " Platform: " -ForegroundColor Cyan -NoNewline Write-Host "windows/x86_64" -Write-Host " Install: " -ForegroundColor Cyan -NoNewline -Write-Host "$InstallDir" +if (-not $CliOnly) { + Write-Host " Desktop: " -ForegroundColor Cyan -NoNewline + Write-Host "yes (tray + overlay notifications)" +} +Write-Host " CLI: " -ForegroundColor Cyan -NoNewline +Write-Host "$CliDir\$BinaryName" Write-Host "" -# --- Download --- $TmpDir = Join-Path $env:TEMP "syncfu-install-$([System.IO.Path]::GetRandomFileName())" New-Item -ItemType Directory -Force -Path $TmpDir | Out-Null -$TmpFile = Join-Path $TmpDir $BinaryName try { + # ============================================= + # 1. Install desktop app (unless -CliOnly) + # ============================================= + if (-not $CliOnly) { + Write-Host "info " -ForegroundColor Green -NoNewline + Write-Host "Downloading syncfu desktop installer..." + + $InstallerPath = Join-Path $TmpDir $DesktopArtifact + try { + Invoke-WebRequest -Uri "$BaseUrl/$DesktopArtifact" -OutFile $InstallerPath -UseBasicParsing + Write-Host "info " -ForegroundColor Green -NoNewline + Write-Host "Running desktop installer (silent)..." + + # NSIS silent install + Start-Process -FilePath $InstallerPath -ArgumentList "/S" -Wait -NoNewWindow + Write-Host "info " -ForegroundColor Green -NoNewline + Write-Host "Desktop app installed" + } catch { + Write-Host "warn " -ForegroundColor Yellow -NoNewline + Write-Host "Desktop app download failed. Installing CLI only." + $CliOnly = $true + } + } + + # ============================================= + # 2. Install CLI binary + # ============================================= Write-Host "info " -ForegroundColor Green -NoNewline - Write-Host "Downloading syncfu v$Version..." + Write-Host "Downloading syncfu CLI..." - Invoke-WebRequest -Uri $Url -OutFile $TmpFile -UseBasicParsing + $CliPath = Join-Path $TmpDir $BinaryName + Invoke-WebRequest -Uri "$BaseUrl/$CliArtifact" -OutFile $CliPath -UseBasicParsing # --- Verify checksum --- Write-Host "info " -ForegroundColor Green -NoNewline Write-Host "Verifying checksum..." - try { - $ChecksumFile = Join-Path $TmpDir "checksums.txt" - Invoke-WebRequest -Uri $ChecksumUrl -OutFile $ChecksumFile -UseBasicParsing - - $EscapedArtifact = [regex]::Escape($Artifact) - $Lines = @(Get-Content $ChecksumFile | Where-Object { $_ -match "^\S+\s+$EscapedArtifact$" }) - if ($Lines.Count -ne 1) { - Write-Host "error " -ForegroundColor Red -NoNewline - Write-Host "Expected exactly 1 checksum entry for $Artifact, found $($Lines.Count)" - exit 1 - } - $Expected = ($Lines[0] -replace '\s+.*', '').ToLower() - $Actual = (Get-FileHash -Path $TmpFile -Algorithm SHA256).Hash.ToLower() + $ChecksumFile = Join-Path $TmpDir "checksums.txt" + Invoke-WebRequest -Uri "$BaseUrl/checksums.txt" -OutFile $ChecksumFile -UseBasicParsing - if ($Expected -ne $Actual) { - Write-Host "error " -ForegroundColor Red -NoNewline - Write-Host "Checksum mismatch! Expected: $Expected, Got: $Actual" - exit 1 - } - Write-Host "info " -ForegroundColor Green -NoNewline - Write-Host "Checksum verified" - } catch { + $EscapedArtifact = [regex]::Escape($CliArtifact) + $Lines = @(Get-Content $ChecksumFile | Where-Object { $_ -match "^\S+\s+$EscapedArtifact$" }) + if ($Lines.Count -ne 1) { + Write-Host "error " -ForegroundColor Red -NoNewline + Write-Host "Expected exactly 1 checksum entry for $CliArtifact, found $($Lines.Count)" + exit 1 + } + $Expected = ($Lines[0] -replace '\s+.*', '').ToLower() + $Actual = (Get-FileHash -Path $CliPath -Algorithm SHA256).Hash.ToLower() + + if ($Expected -ne $Actual) { Write-Host "error " -ForegroundColor Red -NoNewline - Write-Host "Checksum verification failed: $_" + Write-Host "Checksum mismatch! Expected: $Expected, Got: $Actual" exit 1 } + Write-Host "info " -ForegroundColor Green -NoNewline + Write-Host "Checksum verified" - # --- Install --- - New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null - Move-Item -Force $TmpFile (Join-Path $InstallDir $BinaryName) + # --- Install CLI --- + New-Item -ItemType Directory -Force -Path $CliDir | Out-Null + Move-Item -Force $CliPath (Join-Path $CliDir $BinaryName) Write-Host "info " -ForegroundColor Green -NoNewline - Write-Host "Installed to $InstallDir\$BinaryName" + Write-Host "CLI installed to $CliDir\$BinaryName" # --- Add to PATH --- $UserPath = [Environment]::GetEnvironmentVariable('Path', 'User') $PathEntries = $UserPath -split ';' - if ($InstallDir -notin $PathEntries) { - [Environment]::SetEnvironmentVariable('Path', "$InstallDir;$UserPath", 'User') - $env:Path = "$InstallDir;$env:Path" + if ($CliDir -notin $PathEntries) { + [Environment]::SetEnvironmentVariable('Path', "$CliDir;$UserPath", 'User') + $env:Path = "$CliDir;$env:Path" Write-Host "" Write-Host "info " -ForegroundColor Green -NoNewline - Write-Host "Added $InstallDir to your PATH." + Write-Host "Added $CliDir to your PATH." Write-Host " Restart your terminal for PATH changes to take effect." } + # ============================================= + # 3. Start desktop app + # ============================================= + if (-not $CliOnly) { + $SyncfuExe = Join-Path $env:LOCALAPPDATA "syncfu\syncfu.exe" + if (Test-Path $SyncfuExe) { + Write-Host "" + Write-Host "info " -ForegroundColor Green -NoNewline + Write-Host "Starting syncfu (tray + overlay)..." + Start-Process -FilePath $SyncfuExe -WindowStyle Hidden + + Start-Sleep -Seconds 3 + try { + $null = Invoke-WebRequest -Uri "http://127.0.0.1:9868/health" -UseBasicParsing -TimeoutSec 2 + Write-Host "info " -ForegroundColor Green -NoNewline + Write-Host "syncfu is running - server listening on port 9868" + } catch { + Write-Host "warn " -ForegroundColor Yellow -NoNewline + Write-Host "syncfu started but server not yet responding. It may take a moment." + } + } + } + # --- Done --- Write-Host "" + if (-not $CliOnly) { + Write-Host " syncfu is installed and running!" -ForegroundColor White + Write-Host "" + Write-Host " Quick test:" -ForegroundColor Cyan + Write-Host ' syncfu send "Hello from syncfu!"' + } else { + Write-Host " syncfu CLI installed (headless mode)." -ForegroundColor White + Write-Host "" + Write-Host " Start the server:" -ForegroundColor Cyan + Write-Host " syncfu serve" + Write-Host "" + Write-Host " Send a notification:" -ForegroundColor Cyan + Write-Host ' syncfu send "Hello from syncfu!"' + } + Write-Host "" Write-Host "info " -ForegroundColor Green -NoNewline Write-Host "Done! Run 'syncfu --help' to get started." Write-Host "" } catch { Write-Host "error " -ForegroundColor Red -NoNewline - Write-Host "Download failed. Check: https://github.com/$Repo/releases/tag/v$Version" + Write-Host "Installation failed: $_" exit 1 } finally { Remove-Item -Recurse -Force $TmpDir -ErrorAction SilentlyContinue diff --git a/install.sh b/install.sh index 70ff90b..4569aa4 100755 --- a/install.sh +++ b/install.sh @@ -2,34 +2,37 @@ set -eu REPO="Zackriya-Solutions/syncfu" -BINARY_NAME="syncfu" +APP_NAME="syncfu" SKIP_CHECKSUM=0 # --- Colors (only if terminal) --- if [ -t 1 ]; then - BOLD='\033[1m' GREEN='\033[32m' CYAN='\033[36m' RED='\033[31m' RESET='\033[0m' + BOLD='\033[1m' GREEN='\033[32m' CYAN='\033[36m' RED='\033[31m' YELLOW='\033[33m' RESET='\033[0m' else - BOLD='' GREEN='' CYAN='' RED='' RESET='' + BOLD='' GREEN='' CYAN='' RED='' YELLOW='' RESET='' fi info() { printf "${GREEN}info${RESET} %s\n" "$@"; } -warn() { printf "${CYAN}warn${RESET} %s\n" "$@"; } +warn() { printf "${YELLOW}warn${RESET} %s\n" "$@"; } error() { printf "${RED}error${RESET} %s\n" "$@" >&2; exit 1; } # --- Parse arguments --- VERSION="" +CLI_ONLY=0 for arg in "$@"; do case "$arg" in --version=*) VERSION="${arg#--version=}" ;; --skip-checksum) SKIP_CHECKSUM=1 ;; + --cli-only) CLI_ONLY=1 ;; --help|-h) - printf "Usage: install.sh [--version=X.Y.Z] [--skip-checksum]\n" - printf "\nInstalls the syncfu CLI from GitHub Releases.\n" + printf "Usage: install.sh [--version=X.Y.Z] [--skip-checksum] [--cli-only]\n" + printf "\nInstalls the syncfu desktop app and CLI.\n" printf "\nOptions:\n" printf " --version=X.Y.Z Install a specific version (default: latest)\n" - printf " --skip-checksum Skip SHA-256 integrity verification\n" + printf " --skip-checksum Skip SHA-256 integrity verification for CLI binary\n" + printf " --cli-only Install only the CLI (no desktop app, headless mode)\n" printf "\nEnvironment:\n" - printf " SYNCFU_INSTALL_DIR Override install directory (must be absolute path)\n" + printf " SYNCFU_INSTALL_DIR Override CLI install directory (must be absolute path)\n" exit 0 ;; esac @@ -53,10 +56,8 @@ case "$ARCH" in *) error "Unsupported architecture: $ARCH" ;; esac -ARTIFACT="${BINARY_NAME}-${OS_NAME}-${ARCH_NAME}" - # --- Require curl --- -command -v curl >/dev/null 2>&1 || error "curl is required but not installed. Install it with your package manager." +command -v curl >/dev/null 2>&1 || error "curl is required but not installed." # --- Resolve version --- if [ -z "$VERSION" ]; then @@ -71,43 +72,146 @@ if [ -z "$VERSION" ]; then fi fi -# --- Validate version format (digits and dots only) --- +# --- Validate version format --- if ! expr "$VERSION" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' > /dev/null 2>&1; then error "Unexpected version format: $VERSION" fi -# --- Install directory --- +# --- CLI install directory --- if [ -n "${SYNCFU_INSTALL_DIR:-}" ]; then case "$SYNCFU_INSTALL_DIR" in /*) ;; *) error "SYNCFU_INSTALL_DIR must be an absolute path" ;; esac - INSTALL_DIR="$SYNCFU_INSTALL_DIR" + CLI_DIR="$SYNCFU_INSTALL_DIR" elif [ -w /usr/local/bin ]; then - INSTALL_DIR="/usr/local/bin" + CLI_DIR="/usr/local/bin" else - INSTALL_DIR="$HOME/.syncfu/bin" + CLI_DIR="$HOME/.syncfu/bin" fi -# --- Download --- -URL="https://github.com/${REPO}/releases/download/v${VERSION}/${ARTIFACT}" -CHECKSUM_URL="https://github.com/${REPO}/releases/download/v${VERSION}/checksums.txt" - +# --- Work directory --- WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/syncfu-install.XXXXXXXXXX") trap 'rm -rf "$WORK_DIR"' EXIT +# --- Artifact names --- +CLI_ARTIFACT="${APP_NAME}-${OS_NAME}-${ARCH_NAME}" + +if [ "$OS_NAME" = "darwin" ]; then + # Tauri produces: syncfu_0.2.0_aarch64.dmg / syncfu_0.2.0_x86_64.dmg + if [ "$ARCH_NAME" = "arm64" ]; then + TAURI_ARCH="aarch64" + else + TAURI_ARCH="x86_64" + fi + DESKTOP_ARTIFACT="${APP_NAME}_${VERSION}_${TAURI_ARCH}.dmg" +elif [ "$OS_NAME" = "linux" ]; then + DESKTOP_ARTIFACT="${APP_NAME}_${VERSION}_amd64.AppImage" +fi + +BASE_URL="https://github.com/${REPO}/releases/download/v${VERSION}" + printf "\n" printf " ${BOLD}syncfu${RESET} installer\n" printf "\n" -printf " ${CYAN}Version:${RESET} v%s\n" "$VERSION" -printf " ${CYAN}Platform:${RESET} %s/%s\n" "$OS_NAME" "$ARCH_NAME" -printf " ${CYAN}Install:${RESET} %s\n" "$INSTALL_DIR" +printf " ${CYAN}Version:${RESET} v%s\n" "$VERSION" +printf " ${CYAN}Platform:${RESET} %s/%s\n" "$OS_NAME" "$ARCH_NAME" +if [ "$CLI_ONLY" = "0" ]; then + printf " ${CYAN}Desktop:${RESET} yes (tray + overlay notifications)\n" +fi +printf " ${CYAN}CLI:${RESET} %s/syncfu\n" "$CLI_DIR" printf "\n" -info "Downloading syncfu v${VERSION}..." -HTTP_CODE=$(curl -sL -w '%{http_code}' -o "${WORK_DIR}/${BINARY_NAME}" "$URL" 2>/dev/null || true) +# ============================================= +# 1. Install desktop app (unless --cli-only) +# ============================================= +if [ "$CLI_ONLY" = "0" ]; then + if [ "$OS_NAME" = "darwin" ]; then + install_macos_app() { + info "Downloading syncfu desktop app..." + DMG_PATH="${WORK_DIR}/${DESKTOP_ARTIFACT}" + HTTP_CODE=$(curl -sL -w '%{http_code}' -o "$DMG_PATH" "${BASE_URL}/${DESKTOP_ARTIFACT}" 2>/dev/null || true) + if [ "$HTTP_CODE" != "200" ]; then + warn "Desktop app download failed (HTTP ${HTTP_CODE:-???}). Falling back to CLI-only." + return 1 + fi + + info "Installing syncfu.app to /Applications..." + MOUNT_DIR=$(mktemp -d "${TMPDIR:-/tmp}/syncfu-dmg.XXXXXXXXXX") + + hdiutil attach "$DMG_PATH" -nobrowse -mountpoint "$MOUNT_DIR" -quiet 2>/dev/null + if [ -d "$MOUNT_DIR/syncfu.app" ]; then + # Remove old version if present + if [ -d "/Applications/syncfu.app" ]; then + rm -rf "/Applications/syncfu.app" + fi + cp -R "$MOUNT_DIR/syncfu.app" "/Applications/syncfu.app" + # Remove quarantine + xattr -rd com.apple.quarantine "/Applications/syncfu.app" 2>/dev/null || true + info "Installed syncfu.app to /Applications" + else + hdiutil detach "$MOUNT_DIR" -quiet 2>/dev/null || true + rm -rf "$MOUNT_DIR" + warn "Could not find syncfu.app in DMG. Falling back to CLI-only." + return 1 + fi + + hdiutil detach "$MOUNT_DIR" -quiet 2>/dev/null || true + rm -rf "$MOUNT_DIR" + return 0 + } + + install_macos_app || CLI_ONLY=1 + + elif [ "$OS_NAME" = "linux" ]; then + install_linux_app() { + info "Downloading syncfu AppImage..." + APPIMAGE_PATH="${WORK_DIR}/${DESKTOP_ARTIFACT}" + HTTP_CODE=$(curl -sL -w '%{http_code}' -o "$APPIMAGE_PATH" "${BASE_URL}/${DESKTOP_ARTIFACT}" 2>/dev/null || true) + if [ "$HTTP_CODE" != "200" ]; then + warn "Desktop app download failed (HTTP ${HTTP_CODE:-???}). Falling back to CLI-only." + return 1 + fi + + APPIMAGE_DIR="$HOME/.local/share/syncfu" + mkdir -p "$APPIMAGE_DIR" + chmod +x "$APPIMAGE_PATH" + mv "$APPIMAGE_PATH" "$APPIMAGE_DIR/syncfu.AppImage" + info "Installed syncfu AppImage to $APPIMAGE_DIR" + + # Create desktop entry + DESKTOP_DIR="$HOME/.local/share/applications" + mkdir -p "$DESKTOP_DIR" + cat > "$DESKTOP_DIR/syncfu.desktop" << 'DESKTOP_EOF' +[Desktop Entry] +Name=syncfu +Comment=Universal notification overlay +Exec=$HOME/.local/share/syncfu/syncfu.AppImage +Icon=syncfu +Type=Application +Categories=Utility; +StartupNotify=false +DESKTOP_EOF + # Fix the Exec path (can't use variable inside heredoc with single quotes) + sed -i "s|\$HOME|$HOME|g" "$DESKTOP_DIR/syncfu.desktop" + info "Created desktop entry" + return 0 + } + + install_linux_app || CLI_ONLY=1 + fi +fi + +# ============================================= +# 2. Install CLI binary +# ============================================= +info "Downloading syncfu CLI..." +CLI_URL="${BASE_URL}/${CLI_ARTIFACT}" +CHECKSUM_URL="${BASE_URL}/checksums.txt" + +HTTP_CODE=$(curl -sL -w '%{http_code}' -o "${WORK_DIR}/syncfu" "$CLI_URL" 2>/dev/null || true) if [ "$HTTP_CODE" != "200" ]; then - error "Download failed (HTTP ${HTTP_CODE:-???}). Check: https://github.com/${REPO}/releases/tag/v${VERSION}" + error "CLI download failed (HTTP ${HTTP_CODE:-???}). Check: https://github.com/${REPO}/releases/tag/v${VERSION}" fi # --- Verify checksum --- @@ -118,14 +222,14 @@ else if ! curl -fsSL -o "${WORK_DIR}/checksums.txt" "$CHECKSUM_URL" 2>/dev/null; then error "Could not download checksums.txt — cannot verify integrity. Use --skip-checksum to bypass." fi - EXPECTED=$(grep -F "${ARTIFACT}" "${WORK_DIR}/checksums.txt" | awk '{print $1}') + EXPECTED=$(grep -F "${CLI_ARTIFACT}" "${WORK_DIR}/checksums.txt" | awk '{print $1}') if [ -z "$EXPECTED" ]; then - error "Artifact '${ARTIFACT}' not found in checksums.txt. Use --skip-checksum to bypass." + error "Artifact '${CLI_ARTIFACT}' not found in checksums.txt. Use --skip-checksum to bypass." fi if command -v sha256sum >/dev/null 2>&1; then - ACTUAL=$(sha256sum "${WORK_DIR}/${BINARY_NAME}" | awk '{print $1}') + ACTUAL=$(sha256sum "${WORK_DIR}/syncfu" | awk '{print $1}') elif command -v shasum >/dev/null 2>&1; then - ACTUAL=$(shasum -a 256 "${WORK_DIR}/${BINARY_NAME}" | awk '{print $1}') + ACTUAL=$(shasum -a 256 "${WORK_DIR}/syncfu" | awk '{print $1}') else error "No sha256sum or shasum found — cannot verify integrity. Use --skip-checksum to bypass." fi @@ -135,49 +239,101 @@ else info "Checksum verified" fi -# --- Install --- -chmod +x "${WORK_DIR}/${BINARY_NAME}" - -# Remove macOS quarantine attribute +# --- Install CLI --- +chmod +x "${WORK_DIR}/syncfu" if [ "$OS_NAME" = "darwin" ]; then - xattr -d com.apple.quarantine "${WORK_DIR}/${BINARY_NAME}" 2>/dev/null || true + xattr -d com.apple.quarantine "${WORK_DIR}/syncfu" 2>/dev/null || true fi - -mkdir -p "$INSTALL_DIR" -mv "${WORK_DIR}/${BINARY_NAME}" "${INSTALL_DIR}/${BINARY_NAME}" - -info "Installed to ${INSTALL_DIR}/${BINARY_NAME}" +mkdir -p "$CLI_DIR" +mv "${WORK_DIR}/syncfu" "${CLI_DIR}/syncfu" +info "CLI installed to ${CLI_DIR}/syncfu" # --- PATH instructions --- case ":${PATH:-}:" in - *":${INSTALL_DIR}:"*) - # Already in PATH + *":${CLI_DIR}:"*) ;; *) printf "\n" warn "Add syncfu to your PATH:" printf "\n" - SHELL_NAME=$(basename "${SHELL:-/bin/sh}") case "$SHELL_NAME" in - zsh) - printf " echo 'export PATH=\"%s:\$PATH\"' >> ~/.zshrc && source ~/.zshrc\n" "$INSTALL_DIR" - ;; - bash) - printf " echo 'export PATH=\"%s:\$PATH\"' >> ~/.bashrc && source ~/.bashrc\n" "$INSTALL_DIR" - ;; - fish) - printf " fish_add_path %s\n" "$INSTALL_DIR" - ;; - *) - printf " export PATH=\"%s:\$PATH\"\n" "$INSTALL_DIR" - ;; + zsh) printf " echo 'export PATH=\"%s:\$PATH\"' >> ~/.zshrc && source ~/.zshrc\n" "$CLI_DIR" ;; + bash) printf " echo 'export PATH=\"%s:\$PATH\"' >> ~/.bashrc && source ~/.bashrc\n" "$CLI_DIR" ;; + fish) printf " fish_add_path %s\n" "$CLI_DIR" ;; + *) printf " export PATH=\"%s:\$PATH\"\n" "$CLI_DIR" ;; esac printf "\n" ;; esac +# ============================================= +# 3. Start desktop app (tray only, no window) +# ============================================= +if [ "$CLI_ONLY" = "0" ]; then + if [ "$OS_NAME" = "darwin" ] && [ -d "/Applications/syncfu.app" ]; then + info "Starting syncfu (tray + overlay)..." + open -a "/Applications/syncfu.app" + # Wait briefly for the server to come up + for i in 1 2 3 4 5 6 7 8; do + sleep 0.5 + if curl -s "http://127.0.0.1:9868/health" >/dev/null 2>&1; then + break + fi + done + + if curl -s "http://127.0.0.1:9868/health" >/dev/null 2>&1; then + info "syncfu is running — server listening on port 9868" + else + warn "syncfu app started but server not yet responding. It may take a moment." + fi + + elif [ "$OS_NAME" = "linux" ]; then + APPIMAGE="$HOME/.local/share/syncfu/syncfu.AppImage" + if [ -x "$APPIMAGE" ]; then + info "Starting syncfu (tray + overlay)..." + nohup "$APPIMAGE" >/dev/null 2>&1 & + sleep 2 + if curl -s "http://127.0.0.1:9868/health" >/dev/null 2>&1; then + info "syncfu is running — server listening on port 9868" + else + warn "syncfu started but server not yet responding. It may take a moment." + fi + fi + fi +fi + +# --- Verify CLI --- +SYNCFU_BIN="${CLI_DIR}/syncfu" +if [ -x "$SYNCFU_BIN" ]; then + INSTALLED_VERSION=$("$SYNCFU_BIN" --version 2>/dev/null || true) + if [ -n "$INSTALLED_VERSION" ]; then + info "$INSTALLED_VERSION" + fi +fi + # --- Done --- printf "\n" +if [ "$CLI_ONLY" = "0" ]; then + printf " ${BOLD}syncfu is installed and running!${RESET}\n" + printf "\n" + printf " ${CYAN}Quick test:${RESET}\n" + printf " syncfu send \"Hello from syncfu!\"\n" + printf "\n" + printf " The desktop app runs in the system tray. Notifications appear\n" + printf " as overlay panels — no focus stealing, works across all Spaces.\n" +else + printf " ${BOLD}syncfu CLI installed (headless mode).${RESET}\n" + printf "\n" + printf " ${CYAN}Start the server:${RESET}\n" + printf " syncfu serve\n" + printf "\n" + printf " ${CYAN}Send a notification:${RESET}\n" + printf " syncfu send \"Hello from syncfu!\"\n" + printf "\n" + printf " For overlay notifications, install the desktop app:\n" + printf " curl -fsSL https://syncfu.dev/install.sh | sh\n" +fi +printf "\n" info "Done! Run ${BOLD}syncfu --help${RESET} to get started." printf "\n" diff --git a/package.json b/package.json index 3d6b7a0..a5b7e29 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "syncfu", "private": true, - "version": "0.2.0", + "version": "0.3.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 2ae5394..2689d04 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "syncfu" -version = "0.2.0" +version = "0.3.0" edition = "2021" description = "Universal overlay notification system for AI agents and background processes" license = "MIT" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 344fbe3..99f8306 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/nicosujith/tauri-v2-schema/main/tauri.conf.json", "productName": "syncfu", - "version": "0.2.0", + "version": "0.3.0", "identifier": "dev.syncfu.app", "build": { "frontendDist": "../dist",