Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
os: [ubuntu-latest, windows-latest, macos-latest]
include:
- os: ubuntu-latest
script: linux
Expand All @@ -32,6 +32,11 @@ jobs:
script: win
artifact_glob: |
dist/*.exe
- os: macos-latest
script: mac
artifact_glob: |
dist/*.dmg
dist/*-mac.zip

steps:
- name: Checkout
Expand Down Expand Up @@ -83,7 +88,11 @@ jobs:
shell: pwsh
run: git config --system core.longpaths true

- name: Build
# Building x64 and arm64 DMGs in one electron-builder invocation races
# hdiutil on GitHub's Apple Silicon runners. Run each architecture in a
# separate invocation so each disk image is detached before the next.
- name: Build macOS architectures sequentially
if: matrix.os == 'macos-latest'
shell: bash
env:
# Intentionally do NOT set CSC_LINK / WIN_CSC_LINK etc.
Expand All @@ -93,6 +102,15 @@ jobs:
# opt-in: add the secrets to the repo and wire them in here
# only when you have real certificates.
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
run: |
npx electron-builder --mac dmg zip --x64 --publish never
npx electron-builder --mac dmg zip --arm64 --publish never

- name: Build
if: matrix.os != 'macos-latest'
shell: bash
env:
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
run: npm run build:${{ matrix.script }} -- --publish never

- name: Upload artifacts
Expand Down Expand Up @@ -179,15 +197,13 @@ jobs:

| Platform | File | Notes |
|---|---|---|
| **Windows** | `OpenCluely-Setup-*.exe` | NSIS installer — installs app + adds to Start Menu |
| **Linux** | `*.deb` | Debian/Ubuntu — auto-pulls system deps (Python, ffmpeg, GTK) |
| **Linux** | `*.AppImage` | Universal — no install, just `chmod +x` and run |
| **macOS (Intel)** | `OpenCluely-*.dmg` or `*-mac.zip` | Unsigned build; use Finder's **Open** confirmation on first launch. |
| **macOS (Apple Silicon)** | `OpenCluely-*-arm64.dmg` or `*-arm64-mac.zip` | Unsigned build; use Finder's **Open** confirmation on first launch. |
| **Windows** | `OpenCluely-Setup-*.exe` | NSIS installer — installs app + adds to Start Menu. |
| **Linux** | `*.deb` | Debian/Ubuntu package. |
| **Linux** | `*.AppImage` | Universal package — run `chmod +x` then launch. |

> **macOS:** no pre-built build is shipped. The app is unsigned/un-notarized, so macOS Gatekeeper blocks it as "damaged". Run OpenCluely from source instead:
> ```bash
> git clone https://github.com/TechyCSR/OpenCluely && cd OpenCluely && ./setup.sh
> ```
> Requires Node.js 18+. See the [README](https://github.com/TechyCSR/OpenCluely#quick-start) for details.
This beta is for authorised interview preparation and professional development. Do not use it where external assistance is not permitted.

## First Run

Expand Down Expand Up @@ -236,6 +252,10 @@ jobs:
# The order below is the order files appear in the release page.
find artifacts -type f -name 'OpenCluely-Setup-*.exe' \
-exec cp -v {} release/ \;
find artifacts -type f -name 'OpenCluely-*.dmg' \
-exec cp -v {} release/ \;
find artifacts -type f -name 'OpenCluely-*-mac.zip' \
-exec cp -v {} release/ \;
find artifacts -type f -name 'opencluely_*_amd64.deb' \
-exec cp -v {} release/ \;
find artifacts -type f -name '*.AppImage' \
Expand All @@ -255,7 +275,7 @@ jobs:
tag_name: ${{ github.ref_name }}
name: OpenCluely ${{ github.ref_name }}
draft: false
prerelease: false
prerelease: ${{ contains(github.ref_name, '-') }}
body_path: RELEASE_BODY.md
files: |
release/*
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ dist/
bin/
.DS_Store
*.log
knowledge/*.local.md
191 changes: 160 additions & 31 deletions README.md

Large diffs are not rendered by default.

22 changes: 10 additions & 12 deletions chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -1005,23 +1005,17 @@
}

hideListeningAnimation();
addMessage('Stopped Listening', 'system');
}

function handleTranscription(text) {
if (text && typeof text === 'string' && text.trim().length > 0) {
// Hide listening animation first
hideListeningAnimation();

// Show transcribed text with slight delay for smooth transition
setTimeout(() => {
addMessage(text.trim(), 'transcription');

// Show thinking indicator after transcription
setTimeout(() => {
showThinkingIndicator();
}, 300);
}, 200);
// A final Azure result is already stable. Render it immediately;
// cosmetic delays make voice input feel unresponsive.
addMessage(text.trim(), 'transcription');
showThinkingIndicator();
} else {
console.warn('Invalid or empty transcription text - ignoring:', text);
}
Expand Down Expand Up @@ -1170,8 +1164,6 @@
// Listen for speech status
whysperAPI.onSpeechStatus((event, data) => {
if (data && data.status) {
addMessage(data.status, 'system');

if (data.status.includes('started') || data.status.includes('Recording')) {
handleRecordingStarted();
} else if (data.status.includes('stopped') || data.status.includes('ended')) {
Expand All @@ -1180,6 +1172,12 @@
}
});

whysperAPI.onPracticeMuteState?.((event, data) => {
addMessage(data?.muted
? 'Microphone paused — release Space to resume.'
: 'Microphone resumed.', 'system');
});

// Listen for speech errors
whysperAPI.onSpeechError((event, data) => {
if (data && data.error) {
Expand Down
142 changes: 142 additions & 0 deletions docs/V1_IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# OpenCluely V1 Implementation Plan

## Goal

Turn OpenCluely into a reliable local interview-practice and knowledge assistant before expanding it into a full universal skill platform.

V1 is successful when text chat, Azure microphone capture, screenshot capture, streaming responses, diagnostics, and the Amazon DCT profile work reliably through `npm start` on macOS.

## Current Architecture Map

```mermaid
flowchart LR
UI[Main, Chat, Settings, Response Windows] --> PRE[preload IPC bridge]
PRE --> MAIN[ApplicationController]
MAIN --> SPEECH[Azure Speech / Whisper]
MAIN --> CAPTURE[Screen Capture]
MAIN --> LLM[Gemini]
MAIN --> SESSION[Session Manager]
SESSION --> PROMPTS[Profile Prompts]
LLM --> UI
```

## Current Data Flow

```mermaid
sequenceDiagram
participant User
participant Speech as Azure Speech
participant Main as Electron Main
participant AI as Gemini
participant UI as Chat / Overlay
User->>Speech: Speak question
Speech->>Main: Final transcript
Main->>AI: Profile prompt + short history
AI-->>Main: Streamed answer
Main-->>UI: Incremental response chunks
```

## V1 Scope

### 1. Reliability baseline — in progress

- [x] Add Amazon DCT prompt and question classification.
- [x] Add Gemini model selection and visible Gemini quota errors.
- [x] Add a diagnostics location and copy/open-log controls.
- [x] Add log redaction for API keys going forward.
- [x] Correct the macOS microphone audio path to send 16 kHz PCM to Azure Speech.
- [x] Add Azure audio-pipeline diagnostics.
- [x] Add actionable screen-capture permission errors.
- [ ] Validate live microphone transcription using `npm start`.
- [ ] Validate screen capture using `npm start`.
- [ ] Remove legacy logging that can expose credentials and rotate existing keys.

### 2. Performance observability

- [ ] Add one request ID spanning speech, Gemini, and UI rendering.
- [ ] Record STT, first-token, full-response, rendering, and end-to-end latency.
- [ ] Add a compact diagnostics view with recent errors and timings.
- [ ] Prevent duplicate retry paths after quota or network failures.

### 3. Universal skill foundation

- [ ] Replace hard-coded skill arrays with a skill registry.
- [ ] Define a manifest format: prompt, knowledge scope, response modes, language rules, and model preference.
- [ ] Migrate DSA and Amazon DCT to manifests.
- [ ] Add profile switching without restarting the application.
- [ ] Add response modes: Quick, Interview, Detailed, STAR, and Troubleshooting.

### 3A. First profile catalogue — in progress

- [x] Amazon DCT profile.
- [x] DevOps profile.
- [x] SDET profile.
- [x] Backend Engineer profile.
- [x] STAR response mode.
- [x] Leadership Principles mode.
- [ ] Add the remaining profiles only after reliability tests pass.

### 4. Prompt composition

- [ ] Compose prompts from Global Rules + Skill + Interview Profile + Company + User Preferences.
- [ ] Keep quick-answer prompts small enough for low latency.
- [ ] Add safe defaults for context limits and response token limits.
- [ ] Add prompt-version metadata to diagnostics.

### 5. Security and production readiness

- [ ] Remove the custom TLS certificate-verification bypass.
- [ ] Validate every IPC payload at the main-process boundary.
- [ ] Restrict privileged IPC to the minimum necessary surface.
- [ ] Separate session history, diagnostics, and settings storage.
- [ ] Add automated tests for prompt composition, configuration, and speech/capture error handling.

## Explicitly Deferred from V1

- RAG, PDF ingestion, embeddings, and a local vector database.
- A large catalogue of role profiles.
- Full UI redesign.
- Signed and notarized release builds.

These begin only after the V1 reliability baseline is verified.

## Current Test Workflow

Run locally:

```bash
npm start
```

Then verify:

1. Text chat: ask “What is DNS?”
2. Microphone: speak “What is DNS?”, then pause.
3. Screenshot: use `Cmd+Shift+S` while a visible question is on screen.
4. Diagnostics: Settings → Open Logs / Copy Diagnostics.

## Rules for This Development Phase

- Do not create a release build until the three primary workflows pass locally.
- Do not commit or push partial work until the V1 test checklist passes.
- Never put API keys, transcripts, or retrieved private documents into logs.
- Do not expand the skill catalogue while audio and screen capture remain unreliable.
# Phase 8 — UI/UX Redesign

Implemented locally (not yet committed): skill and language selection; interview company selection; Quick, Interview, Detailed, STAR, and Troubleshooting response formats; persisted dark/light appearance; and compact layout density. The active response format is supplied to Gemini as a system-level instruction for every interview profile.

# Phase 5 — Interview Mode Engine

Implemented locally: Amazon DCT, SDET, and DevOps profiles define their knowledge areas and response styles in a central profile registry. Each profile loads its own prompt, can be switched from Settings or the overlay navigation, and is validated before it becomes the active skill.

# Phase 4 — Programming and Platform Expansion

Implemented locally: the DSA language selector supports Python, Java, JavaScript, TypeScript, Go, Rust, C, C++, C#, Kotlin, Swift, PHP, Ruby, Bash, and PowerShell. Settings also provide optional technical-focus selectors for MySQL, PostgreSQL, MSSQL, Oracle, MongoDB, Redis, Elasticsearch, Cassandra, DynamoDB; AWS, Azure, GCP; Docker, Kubernetes, OpenShift; and Terraform, Ansible, Jenkins, and GitHub Actions. Selected focus is persisted and added to interview prompts when relevant.

# Phase 2 — Response Latency Optimization

Implemented locally: streamed Gemini output is incrementally rendered and throttled to avoid excessive IPC/UI updates; repeated short factual questions use a bounded ten-minute in-memory cache; Gemini HTTPS connections are reused; voice coalescing is reduced to 450 ms; and the Settings Performance view reports STT, first-token, full LLM, renderer, cache, and speech-to-answer timings. External Azure and Gemini service time still determines the practical lower bound.

# Phase 3 — Universal Skill System

Implemented locally: a declarative skill catalog now owns skill system prompts, knowledge scopes, response style, display format, latency preferences, language preferences, aliases, and categories. Static prompt files remain supported as overrides. Settings and overlay navigation load their available skills from the same catalog, covering interview, general, education, and general-AI skills.
72 changes: 72 additions & 0 deletions docs/engineering/AUDIT_REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# OpenCluely V3 Beta Engineering Audit

Date: 2026-08-16
Scope: static review of the Electron main process, preload bridge, renderers, services, managers, configuration, dependencies, and existing metrics. This report does not claim runtime measurements that were not collected from a controlled test run.

## Architecture

```mermaid
flowchart LR
UI["Renderer windows\nOverlay · Chat · Settings · Response"] --> PRE["preload.js\ncontextBridge / IPC"]
PRE --> MAIN["main.js\nApplicationController"]
MAIN --> WIN["WindowManager\nBrowserWindow lifecycle"]
MAIN --> SES["SessionManager\nbounded in-memory history"]
MAIN --> CAP["CaptureService\nElectron + macOS fallback"]
MAIN --> SPEECH["SpeechService\nAzure / Whisper"]
MAIN --> LLM["LLMService\nGemini streaming + cache"]
MAIN --> METRICS["PerformanceMetrics\nin-memory rolling sample"]
SPEECH --> AZURE[Azure Speech]
LLM --> GEMINI[Gemini API]
```

## Startup and shutdown flow

1. `main.js` resolves the user-data `.env`, loads configuration, then creates services and managers.
2. Electron readiness creates the configured windows and installs IPC handlers.
3. Settings load the active role, candidate context, job context, provider, and response preferences.
4. Speech and Gemini are initialized lazily or on setting changes.
5. On `will-quit`, global shortcuts are removed, speech shuts down, and windows are destroyed.

Strengths: state persistence is local; the Gemini client is reused; the response cache is bounded; renderer isolation is on.
Gaps: startup duration is not timed; shutdown is not awaited or bounded; global crash guards can leave the process running after corruption.

## IPC flow

```mermaid
sequenceDiagram
participant R as Renderer
participant P as preload
participant M as Main
participant S as Service
R->>P: invoke/send allowlisted action
P->>M: IPC request
M->>S: capture / speech / LLM / settings action
S-->>M: result or streamed delta
M-->>P: IPC event
P-->>R: renderer callback
```

The preload bridge avoids direct Node exposure, but it exposes a large set of privileged actions. Most listener registrations do not return an unsubscribe function, so repeated renderer initialization can accumulate event listeners.

## Findings

| Priority | Finding | Root cause | Impact | Fix / effort |
|---|---|---|---|---|
| High | Global uncaught exception and rejection handlers keep the process alive | Catch-all handlers log but do not transition the app into a known-safe state | A corrupted service can continue running with stale resources | Add health state, stop affected service, show recovery action; 2–3 days |
| High | Broad IPC attack surface | Many privileged handlers; generic renderer receive API; limited per-sender validation | Compromised renderer has more capabilities than needed | Define channel schemas and sender/window checks; 3–5 days |
| High | Recurring overlay enforcement timers | Per-window 3-second interval plus blur/show/focus timer fan-out | Idle CPU wakeups and timer lifecycle complexity | Centralize one scheduler, clear on close; 2–3 days |
| Medium | Incomplete runtime observability | Metrics track durations only, in memory, with no process memory/CPU or startup spans | Cannot prove latency or 8-hour stability targets | Add process/resource snapshots and JSON diagnostics; 2–3 days |
| Medium | Session-manager initialization stores every prompt | All Markdown prompts become system events at startup | Avoidable memory and startup work as catalogue grows | Load only active prompt; fetch others lazily; 1–2 days |
| Medium | No test runner or CI quality gate | No `test` script or automated assertions | Regressions in IPC, speech, and window code reach users | Add Node unit tests then Electron integration smoke tests; 1–2 weeks |
| Medium | Unvalidated renderer IPC payloads | Resize/move/capture/settings handlers accept renderer values | Invalid values can cause errors or unexpected resource use | Strict type/size/range schemas; 2–3 days |
| Low | Duplicate legacy IPC paths | `invoke` and `send` variants exist for several speech/settings actions | Maintenance ambiguity and duplicate events | Deprecate one path after compatibility audit; 1–2 days |
| Low | Static role-name maps remain as fallbacks | Dynamic catalogue is loaded at runtime but legacy maps persist | Display drift for new skills if catalogue fetch fails | Replace fallbacks with a small generic formatter; 0.5 day |

## Low-risk hardening completed

- Config version now reads the root `package.json`, so diagnostics no longer report a stale `1.0.0` version.
- Packaged Electron windows now disable DevTools; local `electron .` development keeps DevTools available.

## Dependency review

The direct dependency tree contains Electron, electron-builder, Gemini SDK, Azure Speech SDK, Whisper capture/worker tooling, Winston logging, Markdown rendering, Prism, and Font Awesome. No direct unused dependency can be proven from static inspection alone; `markdown` and `marked` should be checked for duplicate usage before the next dependency refresh. Dependency vulnerability scanning requires a networked `npm audit` run and is not represented as a completed check here.
Loading