From a1416acbb8047fddd36147907b8669c57c50ded5 Mon Sep 17 00:00:00 2001 From: sujithatzackriya Date: Thu, 26 Mar 2026 22:07:12 +0530 Subject: [PATCH 1/6] docs: update README with architecture, API reference, and feature table --- README.md | 366 ++++++++++++++++++++++++++---------------------------- 1 file changed, 177 insertions(+), 189 deletions(-) diff --git a/README.md b/README.md index e0fc089..fc3f411 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,28 @@

syncfu

-

The notification layer your AI agents are missing.

+

+ The notification layer your AI agents are missing. +

+ +

+ Release + License + Build + Platform + Rust +

+ +

+ 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. +
+ +### Highlights + +| | Feature | Description | +|-|---------|-------------| +| πŸ”” | **Always-on-top overlay** | NSPanel on macOS β€” non-activating, click-through, joins all Spaces | +| ⏳ | **`--wait` flag** | CLI blocks until the user clicks an action button (SSE-backed) | +| 🎨 | **27 style properties** | Full visual control per notification β€” colors, fonts, borders, radii | +| πŸ–₯️ | **Multi-monitor** | Notifications follow your mouse cursor across displays | +| πŸ”— | **Webhook callbacks** | Action buttons POST to your `callbackUrl` for closed-loop automation | +| πŸ“Š | **Live progress bars** | Update in-flight notifications with progress, body changes, new actions | +| πŸ§ͺ | **181 tests** | 72 frontend + 70 Rust server + 29 CLI unit + 10 CLI integration | +| ⚑ | **Zero config** | No config files β€” everything is API-driven per notification | + +
+ +Built with **Tauri v2** + **Rust** (axum) + **React** (Zustand). macOS first, Windows + Linux coming. + +--- + +## Table of Contents + +- [Why this exists](#why-this-exists) +- [How it works](#how-it-works) +- [Install](#install) +- [Quick start](#quick-start) +- [Use cases](#use-cases) +- [System tray](#system-tray) +- [API reference](#api-reference) +- [Integrations](#integrations) +- [Architecture](#architecture) +- [Customization](#customization) +- [Roadmap](#roadmap) +- [Contributing](#contributing) +- [License](#license) --- @@ -58,8 +114,6 @@ CLI ──HTTP POST──▸ syncfu server ──▸ Overlay Notification CLI (--wait) ─────────▸ SSE stream ◂──── wait for action/dismiss ``` -### Port - | Protocol | Port | Purpose | |----------|------|---------| | HTTP REST | `9868` | Send, update, dismiss, wait (SSE) | @@ -70,21 +124,24 @@ CLI (--wait) ─────────▸ SSE stream ◂──── wait for ### One-liner (recommended) +**macOS / Linux:** ```bash curl -fsSL https://raw.githubusercontent.com/Zackriya-Solutions/syncfu/main/install.sh | sh ``` -Windows (PowerShell): +**Windows (PowerShell):** ```powershell irm https://raw.githubusercontent.com/Zackriya-Solutions/syncfu/main/install.ps1 | iex ``` -Install a specific version: +**Specific version:** ```bash curl -fsSL https://raw.githubusercontent.com/Zackriya-Solutions/syncfu/main/install.sh | sh -s -- --version=0.2.0 ``` -### From source +> SHA-256 checksum verification is enforced by default. Use `--skip-checksum` to bypass. + +### From source (CLI only) ```bash git clone https://github.com/Zackriya-Solutions/syncfu.git cd syncfu @@ -99,11 +156,9 @@ pnpm install cargo tauri build ``` ---- - -## Cheatsheet +> **Prerequisites:** [Rust](https://rustup.rs/), [Node.js](https://nodejs.org/) 18+, [pnpm](https://pnpm.io/), and [Tauri v2 prerequisites](https://v2.tauri.app/start/prerequisites/). -See [CHEATSHEET.md](CHEATSHEET.md) for a quick-reference of all CLI commands, flags, and common patterns on a single page. +See [CHEATSHEET.md](CHEATSHEET.md) for a quick-reference of all CLI commands and flags. --- @@ -120,13 +175,17 @@ syncfu send "Hello from syncfu!" syncfu send -t "Build Complete" -p high "All 142 tests passing" # With action buttons -syncfu send -t "PR #42" --action "approve:Approve:primary" --action "skip:Skip:secondary" "Review requested" +syncfu send -t "PR #42" \ + --action "approve:Approve:primary" \ + --action "skip:Skip:secondary" \ + "Review requested" # Block until the user responds (--wait) -ACTION=$(syncfu send -t "Approve?" -a "yes:Yes" -a "no:No:danger" --wait "Merge PR #42?") +ACTION=$(syncfu send -t "Approve?" \ + -a "yes:Yes" -a "no:No:danger" --wait "Merge PR #42?") echo "User chose: $ACTION" # "yes", "no", "dismissed", or "timeout" -# Or use curl +# Or use curl directly curl -X POST localhost:9868/notify \ -H "Content-Type: application/json" \ -d '{"sender":"test","title":"It works","body":"Your first notification"}' @@ -144,7 +203,7 @@ syncfu is designed to stay alive in the background β€” your agents depend on it. - **Starts at login** (configurable) β€” silently, tray + overlay only - **Closing the window** hides it to the tray β€” doesn't quit -- **Ctrl+Q / Cmd+Q** asks: *"Quit syncfu? Agents won't be able to notify you."* β€” with **Quit** and **Send to Background** options +- **Ctrl+Q / Cmd+Q** asks: *"Quit syncfu? Agents won't be able to notify you."* - **Tray β†’ Quit** also confirms before exiting - You'll never accidentally kill it @@ -152,9 +211,11 @@ syncfu is designed to stay alive in the background β€” your agents depend on it. ## Use cases -### AI agents & autonomous loops +
+AI agents & autonomous loops + +#### Claude Code skills and hooks -**Claude Code skills and hooks** Your `/remind` skill fires a cron, but the alert is just a terminal bell you'll never hear. Wire it to syncfu and get an overlay notification with action buttons β€” snooze, mark done, or open the file. ```bash @@ -164,7 +225,8 @@ syncfu send -t "$TITLE" -s remind --sound default \ "$BODY" ``` -**Autonomous coding loops** +#### Autonomous coding loops + Running `/loop` or a multi-agent workflow that takes 30 minutes? Get notified when each phase completes, when tests fail, or when the loop needs human input. ```bash @@ -173,7 +235,8 @@ syncfu send -t "Phase 3/5 complete" -s loop-operator \ "Integration tests: 42 passed, 0 failed. Starting E2E phase..." ``` -**Agent decision gates** +#### Agent decision gates + An agent needs human approval before a destructive action. Use `--wait` to block the agent until the user responds via the overlay notification. ```bash @@ -183,21 +246,15 @@ syncfu send -t "Confirm" -s agent -p high \ "Delete 47 stale branches?" && git branch -d $(git branch --merged) ``` -**Agent handoff alerts** -When one agent finishes and queues work for another, notify the human so they can review before the next agent picks it up. - -**Stalled loop detection** -A watchdog process monitors your autonomous loop and pings syncfu if no progress has been made in N minutes: *"Loop stalled β€” no commits in 12 minutes. Last action: running tests."* +#### More ideas +- **Agent handoff alerts** β€” notify the human between agent stages for review +- **Stalled loop detection** β€” watchdog pings syncfu if no progress in N minutes +- **Multi-agent dashboards** β€” each agent reports with its own sender ID, stacked as a live progress board -**Multi-agent orchestration dashboards** -Running 5 parallel agents? Each one reports status to syncfu with its own sender ID and group key. See them stacked as a live progress board on your screen. +
---- - -### CI/CD & DevOps - -**Build notifications** -GitHub Actions, GitLab CI, Jenkins β€” POST to syncfu when builds finish. Include pass/fail status, duration, coverage delta, and a "Open PR" action button. +
+CI/CD & DevOps ```bash syncfu send -t "Build passed" -s github-actions -i circle-check \ @@ -205,21 +262,14 @@ syncfu send -t "Build passed" -s github-actions -i circle-check \ "main built in 3m 42s β€” 142 tests passed, coverage 87% (+2.1%)" ``` -**Deploy progress** -Track multi-stage deployments with live progress bars that update in real-time via the update endpoint. - -**Infrastructure alerts** -Disk full, memory pressure, certificate expiring, container restart loop β€” get an overlay notification instead of an email you'll read tomorrow. - -**Database migration status** -Long-running migrations report progress: *"Migrating users table β€” 2.4M of 8.1M rows (30%)"* with a live progress bar. +- **Deploy progress** β€” track multi-stage deployments with live progress bars +- **Infrastructure alerts** β€” disk full, memory pressure, certificate expiring +- **Database migrations** β€” *"Migrating users table β€” 2.4M of 8.1M rows (30%)"* ---- - -### Development workflow +
-**Test watcher results** -`cargo watch` or `jest --watch` pipes results to syncfu. Green notification when tests pass. Red critical-priority notification when they fail β€” even if your terminal is buried under 14 windows. +
+Development workflow ```bash # Notify on test pass or fail @@ -227,146 +277,70 @@ cargo test && syncfu send -t "Tests passed" -p low -i circle-check "All green" \ || syncfu send -t "Tests failed" -p critical -i circle-x "Check terminal" ``` -**Long compilation finished** -Rust full rebuild? C++ linking? Go generate? Get notified when it's done instead of checking every 30 seconds. - -**PR review requested** -A webhook listener catches GitHub PR review requests and sends a syncfu notification with "Review" and "Skip" action buttons. - -**Merge conflict alerts** -Your branch just conflicted with main. A hook detects it and notifies you before you waste time building on a broken base. - -**Lint / type-check results** -Run `eslint` or `tsc --noEmit` in the background and get a clean/dirty notification overlay. +- **Long compilation finished** β€” Rust full rebuild, C++ linking, Go generate +- **PR review requested** β€” webhook listener with "Review" and "Skip" buttons +- **Merge conflict alerts** β€” detect before you waste time on a broken base +- **Lint / type-check results** β€” background `eslint` or `tsc --noEmit` ---- - -### Personal productivity & ADHD support +
-**Reminders that actually reach you** -If you have ADHD, you know: setting a reminder is useless if the reminder is a silent badge on an app you don't check. syncfu puts the reminder ON YOUR SCREEN as an unmissable overlay. +
+Personal productivity & ADHD support ```bash syncfu send -t "Stand-up in 5 minutes" -p high --sound default --timeout 300 \ "Prepare: yesterday's PR review, today's auth refactor" ``` -**Time-boxed focus sessions** -Start a 25-minute pomodoro. syncfu shows a progress bar that updates every minute. When time's up, a critical-priority notification appears: *"Pomodoro complete. Take a break."* - -**Context switching prompts** -Scheduled notifications that interrupt with context: *"You've been on this bug for 45 minutes. Current approach: checking race condition in manager.rs. Consider: stepping back and reading the test output again."* - -**Meeting prep alerts** -5 minutes before a meeting: *"Standup in 5m β€” you committed to reviewing the auth PR yesterday. It's still open."* - -**End-of-day review** -A nightly cron fires at 6pm: *"EOD check: 3 reminders still open, 2 PRs need review, tomorrow's first meeting is at 9am."* - -**Medication reminders** -For ADHD medication timing β€” a critical-priority notification with no auto-dismiss that stays on screen until you acknowledge it. Use `--wait` so the reminding system knows you actually took it. +- **Reminders that actually reach you** β€” overlay ON YOUR SCREEN, not a badge on an app you don't check +- **Time-boxed focus sessions** β€” pomodoro with live progress bar +- **Context switching prompts** β€” *"You've been on this bug for 45 minutes..."* +- **Medication reminders** β€” critical-priority, no auto-dismiss, `--wait` confirms you took it ```bash syncfu send -t "Medication" -p critical -i pill --timeout never \ -a "taken:Taken" -a "skip:Skip:danger" \ --wait "Time to take your medication" -# Blocks until user confirms β€” returns "taken" or "skip" ``` -**Hydration / posture / break nudges** -Recurring gentle notifications every 30-60 minutes. Low priority, auto-dismiss after 10 seconds, but enough to break the hyperfocus tunnel. - ---- - -### Server & infrastructure monitoring - -**Health check dashboard** -Poll your services every 60 seconds. Show a stacked notification group: all green, or highlight the failing service in red. +
-**SSL certificate expiry** -*"api.example.com certificate expires in 7 days"* β€” with a "Renew" action button that triggers your renewal script. +
+Server & infrastructure monitoring -**Disk space warnings** -*"/dev/sda1 is 92% full β€” 14GB remaining"* β€” grouped with other infra alerts. +- **Health check dashboard** β€” poll services every 60s, stacked notification group +- **SSL certificate expiry** β€” with a "Renew" action button +- **Disk space warnings** β€” *"/dev/sda1 is 92% full β€” 14GB remaining"* +- **Container restart loops** β€” critical priority alerts +- **Cron job completion** β€” nightly backup, database vacuum, log rotation -**Container restart loops** -*"api-server container has restarted 4 times in the last hour"* β€” critical priority. +
-**Cron job completion** -Your nightly backup, database vacuum, or log rotation finished (or failed). Know immediately. - ---- - -### Data & ML pipelines - -**Training run progress** -Long-running ML training jobs report epoch progress, loss curves (as text), and ETA. Get notified at milestones or when training completes. +
+Data & ML pipelines ```bash syncfu send -t "Epoch 45/100" -s training --progress 0.45 --group training-run-7 \ "Loss: 0.0234 (↓12%) β€” Val accuracy: 94.2% β€” ETA: 2h 15m" ``` -**Data pipeline stage completion** -ETL pipeline: extract done β†’ transform done β†’ load done. Each stage fires a notification. Failure at any stage fires a critical alert. - -**Model evaluation results** -*"Model v2.3 eval complete: accuracy 94.2% (+1.8%), latency p99 45ms (-12ms). Ready to deploy?"* with Deploy/Reject action buttons. - -**Dataset processing** -Processing 10M records? Get a progress bar notification that updates every 1000 records. - ---- - -### Team & collaboration - -**Slack/Discord message highlights** -A bot watches specific channels and forwards high-priority messages to syncfu. Never miss an @mention because Slack was in a background tab. - -**Email triage alerts** -Filter emails server-side and forward urgent ones as syncfu notifications: *"Email from CTO: 'Need the post-mortem by EOD'"* β€” with "Open Email" and "Snooze 1h" actions. - -**Shared incident response** -During an incident, a shared syncfu channel pushes updates to everyone on the team. Status changes, new findings, and action items appear as overlay notifications. - ---- - -### Home automation & IoT +- **Data pipeline stages** β€” extract β†’ transform β†’ load, with failure alerts +- **Model evaluation** β€” *"Model v2.3: accuracy 94.2% (+1.8%)"* with Deploy/Reject buttons +- **Dataset processing** β€” progress bar that updates every 1000 records -**Smart home events** -Home Assistant, Node-RED, or any IoT platform POSTs events to syncfu. Front door opened, dryer finished, garage left open. +
-**Package delivery** -Tracking webhook fires when a package is out for delivery or delivered. +
+Team, home automation & more -**Weather alerts** -Severe weather warnings pushed as high-priority notifications. +- **Slack/Discord highlights** β€” forward @mentions as overlay notifications +- **Email triage** β€” urgent emails surface as notifications with "Open" and "Snooze" actions +- **Smart home events** β€” Home Assistant, Node-RED, IoT webhooks +- **Price alerts** β€” stock/crypto threshold notifications +- **Billing warnings** β€” *"AWS spend: $847 (85% of $1000 budget)"* +- **Render complete** β€” video, 3D scene, image batch notifications ---- - -### Financial & monitoring - -**Price alerts** -Stock hits a target, crypto crosses a threshold, or a SaaS bill exceeds budget β€” instant overlay notification. - -**Billing threshold warnings** -*"AWS spend this month: $847 (85% of $1000 budget)"* β€” with a progress bar. - -**API rate limit warnings** -*"OpenAI API: 892/1000 requests used this minute"* β€” so you can throttle before you hit 429s. - ---- - -### Creative & content workflows - -**Render complete** -Video render, 3D scene, image generation batch β€” done. *"Blender render complete: 240 frames in 47 minutes."* - -**Content publishing confirmations** -Blog post deployed, social media post published, newsletter sent β€” confirmed via notification. - -**Transcription/processing complete** -Audio file finished transcribing, podcast episode processed, video subtitles generated. +
--- @@ -390,12 +364,12 @@ Closing the main window hides it β€” syncfu keeps running in the tray. The overl |--------|------|-------------| | `POST` | `/notify` | Send a notification | | `POST` | `/notify/{id}/update` | Update an existing notification (progress, body) | -| `POST` | `/notify/{id}/action` | Trigger an action (fires webhook to `callbackUrl`, dismisses) | +| `POST` | `/notify/{id}/action` | Trigger an action (fires webhook, dismisses) | | `POST` | `/notify/{id}/dismiss` | Dismiss a specific notification | -| `GET` | `/notify/{id}/wait` | SSE stream β€” blocks until action/dismiss (powers `--wait`) | +| `GET` | `/notify/{id}/wait` | SSE stream β€” blocks until action/dismiss | | `POST` | `/dismiss-all` | Dismiss all active notifications | -| `GET` | `/health` | Server status and active notification count | -| `GET` | `/active` | List all active notifications (JSON array) | +| `GET` | `/health` | Server status + active notification count | +| `GET` | `/active` | List all active notifications (JSON) | ### Notification payload @@ -419,13 +393,16 @@ Closing the main window hides it β€” syncfu keeps running in the tray. The overl } ``` +
+Full field reference + | Field | Type | Required | Description | |-------|------|----------|-------------| | `sender` | string | yes | Identifier for the sending process | | `title` | string | yes | Notification title | | `body` | string | yes | Body text (plain text) | | `icon` | string | no | Lucide icon name (e.g. `phone`, `git-pull-request`, `bell`) | -| `font` | string | no | Google Font name (e.g. `Space Grotesk`, `JetBrains Mono`) β€” loaded on demand | +| `font` | string | no | Google Font name (e.g. `Space Grotesk`, `JetBrains Mono`) | | `priority` | string | no | `low`, `normal` (default), `high`, `critical` | | `timeout` | object | no | `{"seconds": N}` or `"never"` or `"default"` (auto by priority) | | `actions` | array | no | Up to 3 action buttons | @@ -433,12 +410,15 @@ Closing the main window hides it β€” syncfu keeps running in the tray. The overl | `group` | string | no | Group key (tracked, grouping UI planned) | | `theme` | string | no | `light` or `dark` (auto-follows system by default) | | `sound` | string | no | Sound name (accepted, playback planned) | -| `callback_url` | string | no | URL to POST when an action button is clicked | +| `callback_url` | string | no | URL to POST when an action is clicked | | `style` | object | no | Per-notification style overrides (see below) | -### Style overrides +
+ +
+Style overrides (27 properties) -The `style` object lets you customize every visual property per notification. All fields optional: +Override any visual property per notification: ```json { @@ -462,7 +442,10 @@ The `style` object lets you customize every visual property per notification. Al Full list: `accentColor`, `cardBg`, `cardBorderRadius`, `iconColor`, `iconBg`, `iconBorderColor`, `titleColor`, `titleFontSize`, `bodyColor`, `bodyFontSize`, `senderColor`, `timeColor`, `btnBg`, `btnColor`, `btnBorderColor`, `btn2Bg`, `btn2Color`, `btn2BorderColor`, `dangerBg`, `dangerColor`, `dangerBorderColor`, `progressColor`, `progressTrackColor`, `countdownColor`, `closeBg`, `closeColor`, `closeBorderColor`. -### Per-action button styling +
+ +
+Per-action button styling Each action button can override its own colors: @@ -480,6 +463,9 @@ Each action button can override its own colors: } ] } +``` + +
--- @@ -487,7 +473,7 @@ Each action button can override its own colors: ### Claude Code hooks -Add to your `.claude/hooks.json` to get notified on every agent completion: +Get notified on every agent completion: ```json { @@ -566,11 +552,13 @@ requests.post('http://localhost:9868/notify', json={ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` -- **Rust backend**: axum HTTP server + SSE wait streams + waiter registry (tokio broadcast) -- **React frontend**: Zustand store, CSS animations, Lucide icons, Google Fonts -- **Overlay**: NSPanel (macOS) β€” non-activating, follows mouse cursor across monitors -- **CLI**: fire-and-forget by default, `--wait` opens SSE stream for blocking responses -- **System tray**: Pause, clear, settings, server status +| Component | Stack | +|-----------|-------| +| Backend | Rust β€” axum HTTP server, SSE streams, tokio broadcast channels | +| Frontend | React β€” Zustand store, CSS animations, Lucide icons, Google Fonts | +| Overlay | NSPanel (macOS) β€” non-activating, follows mouse cursor across monitors | +| CLI | Rust β€” fire-and-forget by default, `--wait` opens SSE stream | +| System tray | Tauri tray API β€” pause, clear, quit with confirmation | --- @@ -578,7 +566,7 @@ requests.post('http://localhost:9868/notify', json={ syncfu doesn't use config files β€” everything is controlled per-notification via the API. -**Per-notification styling** β€” override any of 27 CSS properties via the `style` field: +**Per-notification styling** β€” override any of 27 CSS properties: ```bash syncfu send -t "Custom" --style-json '{"accentColor":"#22c55e","cardBg":"rgba(10,40,20,0.96)"}' "Styled notification" ``` @@ -594,28 +582,26 @@ syncfu send -t "Fancy" --font "Space Grotesk" "With a custom font" ## Roadmap -- [x] Architecture & plan +### Shipped + - [x] Core overlay window + system tray - [x] NSPanel on macOS (non-activating, joins all Spaces) -- [x] Notification rendering + Liquid Glass design +- [x] Liquid Glass design + slide-in/out animations - [x] HTTP REST server (port 9868) - [x] Light/dark theme (auto + per-notification override) -- [x] Lucide icons (programmable via `icon` field) -- [x] Google Fonts (programmable via `font` field) -- [x] Slide-in/slide-out animations +- [x] Lucide icons + Google Fonts (programmable per notification) - [x] Auto-dismiss with countdown bar (pauses on hover) - [x] Critical pulsing glow (Siri-style) -- [x] Relative timestamps ("just now", "5m ago") -- [x] Priority-tinted icon containers -- [x] Dynamic panel resize (no click-blocking) - [x] Webhook callbacks (action buttons POST to `callbackUrl`) -- [x] Per-notification style overrides (27 customizable properties) -- [x] Per-action button styling (bg, color, borderColor, icon) -- [x] CLI binary (`syncfu send/dismiss/list/health`) -- [x] CLI `--wait` flag (SSE-based blocking until action/dismiss/timeout) -- [x] Multi-monitor support (notification follows mouse cursor) -- [x] 181 tests (72 frontend + 70 Rust server + 29 CLI unit + 10 CLI integration) -- [x] Click-through (overlay transparent, only cards interactive) +- [x] 27 per-notification style properties + per-action button styling +- [x] CLI (`syncfu send/dismiss/list/health`) + `--wait` flag (SSE) +- [x] Multi-monitor support (follows mouse cursor) +- [x] Click-through overlay (only cards interactive) +- [x] One-command install (`curl | sh` + PowerShell) +- [x] 181 tests (72 frontend + 70 server + 29 CLI unit + 10 CLI integration) + +### Planned + - [ ] Markdown body rendering - [ ] Sound playback - [ ] Notification grouping UI @@ -638,14 +624,16 @@ pnpm install cargo tauri dev ``` +**Prerequisites:** Rust 1.75+, Node.js 18+, pnpm, [Tauri v2 prerequisites](https://v2.tauri.app/start/prerequisites/) + --- ## License -MIT +MIT © [Zackriya Solutions](https://github.com/Zackriya-Solutions) ---

- 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.

From 2af9c25e341ed15ec057e3a4c7d18a6ecf861451 Mon Sep 17 00:00:00 2001 From: sujithatzackriya Date: Thu, 26 Mar 2026 22:28:33 +0530 Subject: [PATCH 2/6] feat: add unified release workflow for desktop app + CLI Builds Tauri desktop bundles (macOS .dmg, Windows NSIS, Linux AppImage) alongside CLI binaries in a single release pipeline triggered on v* tags. --- .github/workflows/release.yml | 202 ++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 .github/workflows/release.yml 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." From 4b7ad0750c9a85ffd97237167ba7478c2342102d Mon Sep 17 00:00:00 2001 From: sujithatzackriya Date: Thu, 26 Mar 2026 22:28:43 +0530 Subject: [PATCH 3/6] feat: rewrite install.sh for desktop-first experience Downloads and installs the desktop app (.dmg on macOS, .AppImage on Linux) plus CLI binary. Starts the tray app after install so the server is immediately available on port 9868. Falls back to --cli-only if desktop download fails. --- install.sh | 266 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 211 insertions(+), 55 deletions(-) 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" From c6f3f237af6a0c7da7493d1d6ba9fb30a8919658 Mon Sep 17 00:00:00 2001 From: sujithatzackriya Date: Thu, 26 Mar 2026 22:28:56 +0530 Subject: [PATCH 4/6] feat: rewrite install.ps1 for desktop-first experience Downloads NSIS installer for the desktop app (silent install) plus CLI binary. Starts the tray app after install. Adds -CliOnly switch for headless/CI environments. --- install.ps1 | 152 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 108 insertions(+), 44 deletions(-) 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 From 9d4cace585e88893a816687b8c042162a3c85401 Mon Sep 17 00:00:00 2001 From: sujithatzackriya Date: Thu, 26 Mar 2026 22:29:08 +0530 Subject: [PATCH 5/6] chore: deprecate CLI-only release workflow Replaced by release.yml which builds both desktop app and CLI. Converted to manual workflow_dispatch to prevent duplicate releases. --- .github/workflows/release-cli.yml | 107 +++--------------------------- 1 file changed, 11 insertions(+), 96 deletions(-) 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 From 45599a6f23ecebc112dc31526c83e6d2ef71965f Mon Sep 17 00:00:00 2001 From: sujithatzackriya Date: Fri, 27 Mar 2026 01:39:45 +0530 Subject: [PATCH 6/6] chore: bump version to 0.3.0 --- Cargo.lock | 4 ++-- cli/Cargo.toml | 2 +- package.json | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) 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/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/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",