Skip to content

Commit 4dd9a9f

Browse files
committed
Add GitHub Actions release builds with first-run onboarding
1 parent f628821 commit 4dd9a9f

9 files changed

Lines changed: 567 additions & 28 deletions

File tree

.github/workflows/release.yml

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
name: Build & Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
inputs:
9+
dry_run:
10+
description: 'Build only (skip release creation)'
11+
required: false
12+
default: 'false'
13+
14+
permissions:
15+
contents: write
16+
17+
jobs:
18+
build:
19+
name: Build (${{ matrix.os }})
20+
runs-on: ${{ matrix.os }}
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
os: [ubuntu-latest, windows-latest, macos-latest]
25+
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v4
29+
30+
- name: Setup Node.js
31+
uses: actions/setup-node@v4
32+
with:
33+
node-version: '20'
34+
cache: 'npm'
35+
36+
- name: Install dependencies
37+
run: npm ci
38+
39+
# Linux: install LinuxBuild dependencies so electron-builder can produce .deb / .AppImage
40+
- name: Install Linux build dependencies
41+
if: matrix.os == 'ubuntu-latest'
42+
run: |
43+
sudo apt-get update
44+
sudo apt-get install -y --no-install-recommends \
45+
fakeroot dpkg rpm \
46+
libgtk-3-0 libnotify4 libnss3 libxss1 libxtst6 \
47+
xauth xvfb libasound2t64 ffmpeg \
48+
python3 python3-venv python3-pip
49+
50+
# Windows: enable long paths for electron-builder
51+
- name: Enable Windows long paths
52+
if: matrix.os == 'windows-latest'
53+
shell: pwsh
54+
run: git config --system core.longpaths true
55+
56+
- name: Build
57+
env:
58+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59+
# macOS code signing (optional)
60+
CSC_LINK: ${{ secrets.CSC_LINK }}
61+
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
62+
# Windows code signing (optional)
63+
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
64+
run: |
65+
if [ "$RUNNER_OS" == "Linux" ]; then
66+
npm run build:linux
67+
elif [ "$RUNNER_OS" == "Windows" ]; then
68+
npm run build:win
69+
elif [ "$RUNNER_OS" == "macOS" ]; then
70+
npm run build:mac
71+
fi
72+
73+
- name: List build output
74+
run: ls -la dist || true
75+
76+
- name: Upload artifacts
77+
uses: actions/upload-artifact@v4
78+
with:
79+
name: opencluely-${{ matrix.os }}
80+
path: |
81+
dist/*.dmg
82+
dist/*.zip
83+
dist/*.exe
84+
dist/*.AppImage
85+
dist/*.deb
86+
if-no-files-found: warn
87+
88+
release:
89+
name: Create GitHub Release
90+
needs: build
91+
if: startsWith(github.ref, 'refs/tags/v')
92+
runs-on: ubuntu-latest
93+
steps:
94+
- name: Download all artifacts
95+
uses: actions/download-artifact@v4
96+
with:
97+
path: artifacts
98+
99+
- name: Flatten artifacts
100+
run: |
101+
mkdir -p release
102+
find artifacts -type f \( -name '*.dmg' -o -name '*.zip' -o -name '*.exe' -o -name '*.AppImage' -o -name '*.deb' \) -exec cp -v {} release/ \;
103+
ls -la release/
104+
105+
- name: Generate SHA256 checksums
106+
working-directory: release
107+
run: |
108+
sha256sum * > SHA256SUMS.txt
109+
cat SHA256SUMS.txt
110+
111+
- name: Create GitHub Release
112+
uses: softprops/action-gh-release@v2
113+
with:
114+
tag_name: ${{ github.ref_name }}
115+
name: OpenCluely ${{ github.ref_name }}
116+
draft: false
117+
prerelease: false
118+
generate_release_notes: true
119+
body: |
120+
## Installation
121+
122+
Choose the artifact for your platform:
123+
124+
| Platform | File | Notes |
125+
|---|---|---|
126+
| **Windows** | `*.exe` | NSIS installer — installs app + adds to Start Menu |
127+
| **Windows** | `*-portable.exe` | Portable, no install required |
128+
| **macOS (Apple Silicon)** | `*-arm64.dmg` | M1/M2/M3 Macs |
129+
| **macOS (Intel)** | `*-x64.dmg` | Older Intel Macs |
130+
| **Linux** | `*.deb` | Debian/Ubuntu — auto-pulls system deps (Python, ffmpeg, GTK) |
131+
| **Linux** | `*.AppImage` | Universal — no install, just chmod +x and run |
132+
133+
## First Run
134+
135+
On first launch, OpenCluely will create `.env` and open the **Settings** window for you to enter your **Google Gemini API key**. You can paste it in the Settings UI or edit `.env` directly.
136+
137+
## Optional: Local Whisper
138+
139+
For offline speech recognition, install **Python 3.10+** and **ffmpeg** on your system, then run from the project folder:
140+
141+
```bash
142+
./setup.sh --skip-install-system-deps
143+
```
144+
145+
See [README.md](https://github.com/TechyCSR/OpenCluely) for full setup details.
146+
147+
## Checksums
148+
149+
See `SHA256SUMS.txt` attached to this release.
150+
files: |
151+
release/*
152+
fail_on_unmatched_files: false

README.md

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -174,34 +174,49 @@ WHISPER_SEGMENT_MS=4000
174174

175175
**Note**: Speech recognition is completely optional. If no configured provider is available, the microphone button will be automatically hidden from all interfaces.
176176

177+
## 📦 Download Pre-Built Installers
178+
179+
Don't want to clone and build? Download a pre-built installer for your platform from the [**Releases page**](https://github.com/TechyCSR/OpenCluely/releases).
180+
181+
| Platform | File | Notes |
182+
|---|---|---|
183+
| **Windows** | `OpenCluely-Setup-*.exe` | NSIS installer; auto-creates Start Menu shortcut |
184+
| **Windows** | `OpenCluely-*-portable.exe` | Portable, no install required |
185+
| **macOS (Apple Silicon)** | `OpenCluely-*-arm64.dmg` | M1 / M2 / M3 / M4 Macs |
186+
| **macOS (Intel)** | `OpenCluely-*-x64.dmg` | Older Intel Macs |
187+
| **Linux (Debian/Ubuntu)** | `OpenCluely-*.deb` | Auto-pulls system deps: Python 3.10+, ffmpeg, GTK, NSS |
188+
| **Linux (Universal)** | `OpenCluely-*.AppImage` | No install — `chmod +x` then run |
189+
190+
Every release is built automatically by GitHub Actions across Windows, macOS, and Linux runners in parallel and uploaded with SHA-256 checksums.
191+
177192
## 🚀 Quick Start & Installation
178193

179194
### ⚡ Three Simple Steps (All Operating Systems)
180195

181-
1. **Clone the repository**
196+
1. **Clone the repository** (skip if you downloaded a pre-built installer above)
182197
```bash
183198
git clone https://github.com/TechyCSR/OpenCluely.git
184199
cd OpenCluely
185200
```
186201

187-
2. **Get your Gemini API key** (Required)
188-
- Visit [Google AI Studio](https://aistudio.google.com/)
189-
- Click "Create API Key"
190-
- Copy the key (you'll need it in step 3)
191-
192-
3. **Run the setup script** (One command does everything!)
202+
2. **Run the setup script** (One command does everything!)
193203
```bash
194204
./setup.sh
195205
```
196-
197-
198-
**That's it!** The setup script will:
199-
- Install all dependencies automatically
200-
- Create your `.env` file from `env.example` if needed
201-
- Set up a local Whisper virtualenv in `.venv-whisper`
202-
- Configure `.env` to use local Whisper by default
203-
- Build the app (if needed)
204-
- Launch OpenCluely ready to use (if not works use npm install & then npm start)
206+
207+
The setup script will:
208+
- Install all Node dependencies automatically
209+
- Create your `.env` file from `env.example` if needed (with safe defaults)
210+
- Set up a local Whisper virtualenv in `.venv-whisper` (optional, 3 GB)
211+
- Configure `.env` to use local Whisper by default
212+
- Launch OpenCluely
213+
214+
3. **First-run onboarding**
215+
- On first launch, if no Gemini API key is configured, the app **automatically opens the Settings window** and walks you through entering it.
216+
- You can paste the key in the Settings UI, or edit `.env` directly — both work.
217+
- Get a free key from [Google AI Studio](https://aistudio.google.com/).
218+
219+
**Note:** Setup will not hard-block if `GEMINI_API_KEY` is missing — the app launches either way and prompts you when needed.
205220

206221
### 💻 Platform-Specific Notes
207222

main.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ app.commandLine.appendSwitch("no-pings");
3333

3434
const logger = require("./src/core/logger").createServiceLogger("MAIN");
3535
const config = require("./src/core/config");
36+
const FirstRunManager = require("./src/core/first-run");
3637

3738
// Services
3839
// Screen capture (image-based)
@@ -53,6 +54,14 @@ class ApplicationController {
5354
this.codingLanguage = "cpp";
5455
this.speechAvailable = false;
5556

57+
// First-run onboarding: detects missing .env / API key and triggers
58+
// a settings-window prompt on first launch so users don't have to
59+
// dig through docs to figure out they need a Gemini API key.
60+
this.firstRunManager = new FirstRunManager({
61+
logger: logger
62+
});
63+
this.isFirstRun = false;
64+
5665
// Window configurations for reference
5766
this.windowConfigs = {
5867
main: { title: "OpenCluely" },
@@ -163,6 +172,36 @@ class ApplicationController {
163172
this.starting = false;
164173
this.isReady = true;
165174

175+
// First-run onboarding: ensure .env exists and prompt the user to
176+
// set their Gemini API key via the Settings window if it isn't
177+
// configured yet. Non-blocking — failure here just logs.
178+
try {
179+
this.firstRunManager.ensureEnv();
180+
const status = this.firstRunManager.getStatus();
181+
this.isFirstRun = status.needsOnboarding;
182+
logger.info("First-run status", status);
183+
if (this.isFirstRun) {
184+
// Defer slightly so all windows finish loading before we pop
185+
// the settings dialog on top of them.
186+
setTimeout(() => {
187+
try {
188+
this.showSettings();
189+
windowManager.broadcastToAllWindows("first-run", status);
190+
logger.info("First-run onboarding: settings window opened");
191+
} catch (e) {
192+
logger.warn("Could not open first-run settings window", {
193+
error: e.message
194+
});
195+
}
196+
}, 800);
197+
} else {
198+
// Already configured — mark completed so we never nag again.
199+
this.firstRunManager.markCompleted();
200+
}
201+
} catch (e) {
202+
logger.warn("First-run check failed", { error: e.message });
203+
}
204+
166205
logger.info("Application initialized successfully", {
167206
windowCount: Object.keys(windowManager.getWindowStats().windows).length,
168207
currentDesktop: "detected",
@@ -560,6 +599,27 @@ class ApplicationController {
560599
return this.getSettings();
561600
});
562601

602+
// First-run onboarding status — renderer can query to know whether
603+
// to show the welcome banner / prompt for API-key entry.
604+
ipcMain.handle("get-first-run-status", () => {
605+
try {
606+
return this.firstRunManager.getStatus();
607+
} catch (e) {
608+
logger.warn("Failed to get first-run status", { error: e.message });
609+
return { needsOnboarding: false, error: e.message };
610+
}
611+
});
612+
613+
ipcMain.handle("complete-first-run", () => {
614+
try {
615+
this.firstRunManager.markCompleted();
616+
this.isFirstRun = false;
617+
return { success: true };
618+
} catch (e) {
619+
return { success: false, error: e.message };
620+
}
621+
});
622+
563623
ipcMain.handle("save-settings", (event, settings) => {
564624
return this.saveSettings(settings);
565625
});

package.json

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,35 @@
130130
}
131131
],
132132
"icon": "assets/icons/app-icon.png",
133-
"category": "Utility"
133+
"category": "Utility",
134+
"deb": {
135+
"depends": [
136+
"libgtk-3-0",
137+
"libnotify4",
138+
"libnss3",
139+
"libxss1",
140+
"libxtst6",
141+
"xauth",
142+
"libasound2t64",
143+
"libgbm1",
144+
"python3 (>= 3.10)",
145+
"python3-venv",
146+
"python3-pip",
147+
"ffmpeg"
148+
],
149+
"fpm": [
150+
"--after-install=scripts/post-install-deb.sh"
151+
]
152+
}
134153
},
135154
"nsis": {
136155
"oneClick": false,
137156
"allowToChangeInstallationDirectory": true,
138157
"createDesktopShortcut": true,
139-
"createStartMenuShortcut": true
158+
"createStartMenuShortcut": true,
159+
"perMachine": false,
160+
"include": "scripts/post-install-nsis.nsh",
161+
"deleteAppDataOnUninstall": false
140162
},
141163
"dmg": {
142164
"title": "OpenCluely Interview Assistant",

preload.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
4040
hideSettings: () => ipcRenderer.invoke('hide-settings'),
4141
getSettings: () => ipcRenderer.invoke('get-settings'),
4242
saveSettings: (settings) => ipcRenderer.invoke('save-settings', settings),
43+
44+
// First-run onboarding
45+
getFirstRunStatus: () => ipcRenderer.invoke('get-first-run-status'),
46+
completeFirstRun: () => ipcRenderer.invoke('complete-first-run'),
4347
updateAppIcon: (iconKey) => ipcRenderer.invoke('update-app-icon', iconKey),
4448
updateActiveSkill: (skill) => ipcRenderer.invoke('update-active-skill', skill),
4549
restartAppForStealth: () => ipcRenderer.invoke('restart-app-for-stealth'),

0 commit comments

Comments
 (0)