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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,30 @@ npm run dist
```
The built app will be in the `release` folder.

#### Method 3: Global CLI Command

1. open `package.json` and add :
```
"bin": {
"start-cluely": "./start.sh"
}
```
2. create a new file start.sh in the root dir, add this in the file
```
cd "/home/user/Folder/free-cluely"
npm start
```
3. In the terminal, go to folder dir(root) and type :
```
chmod +x start.sh
npm link
```
4. Done! now you can be in any dir in the terminal, you can just type
```
start-cluely
```
to run this application

## 🤖 AI Provider Options

### Ollama (Recommended for Privacy)
Expand Down
30 changes: 24 additions & 6 deletions electron/ScreenshotHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import path from "node:path"
import fs from "node:fs"
import { app } from "electron"
import { app, desktopCapturer } from "electron"
import { v4 as uuidv4 } from "uuid"
import screenshot from "screenshot-desktop"

export class ScreenshotHelper {
private screenshotQueue: string[] = []
Expand All @@ -28,10 +27,10 @@ export class ScreenshotHelper {

// Create directories if they don't exist
if (!fs.existsSync(this.screenshotDir)) {
fs.mkdirSync(this.screenshotDir)
fs.mkdirSync(this.screenshotDir, { recursive: true })
}
if (!fs.existsSync(this.extraScreenshotDir)) {
fs.mkdirSync(this.extraScreenshotDir)
fs.mkdirSync(this.extraScreenshotDir, { recursive: true })
}
}

Expand Down Expand Up @@ -84,11 +83,30 @@ export class ScreenshotHelper {
// Add a small delay to ensure window is hidden
await new Promise(resolve => setTimeout(resolve, 100))

// Use Electron's desktopCapturer to get screen sources
const sources = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: { width: 1920, height: 1080 }
})

if (sources.length === 0) {
throw new Error("No screen sources available")
}

// Get the primary screen (first source)
const primaryScreen = sources[0]
const image = primaryScreen.thumbnail

if (image.isEmpty()) {
throw new Error("Failed to capture screen - image is empty")
}

let screenshotPath = ""
const screenshotBuffer = image.toPNG()

if (this.view === "queue") {
screenshotPath = path.join(this.screenshotDir, `${uuidv4()}.png`)
await screenshot({ filename: screenshotPath })
await fs.promises.writeFile(screenshotPath, screenshotBuffer)

this.screenshotQueue.push(screenshotPath)
if (this.screenshotQueue.length > this.MAX_SCREENSHOTS) {
Expand All @@ -103,7 +121,7 @@ export class ScreenshotHelper {
}
} else {
screenshotPath = path.join(this.extraScreenshotDir, `${uuidv4()}.png`)
await screenshot({ filename: screenshotPath })
await fs.promises.writeFile(screenshotPath, screenshotBuffer)

this.extraScreenshotQueue.push(screenshotPath)
if (this.extraScreenshotQueue.length > this.MAX_SCREENSHOTS) {
Expand Down
33 changes: 33 additions & 0 deletions electron/ipcHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ export function initializeIpcHandlers(appState: AppState): void {
appState.toggleMainWindow()
})

ipcMain.handle("show-window", async () => {
appState.showMainWindow()
})

ipcMain.handle("hide-window", async () => {
appState.hideMainWindow()
})

ipcMain.handle("process-screenshots", async () => {
await appState.processingHelper.processScreenshots()
})

ipcMain.handle("reset-queues", async () => {
try {
appState.clearQueues()
Expand All @@ -70,6 +82,27 @@ export function initializeIpcHandlers(appState: AppState): void {
}
})

ipcMain.handle("reset-view", async () => {
console.log("Reset view requested. Canceling requests and resetting queues...")

// Cancel ongoing API requests
appState.processingHelper.cancelOngoingRequests()

// Clear both screenshot queues
appState.clearQueues()

console.log("Cleared queues.")

// Update the view state to 'queue'
appState.setView("queue")

// Notify renderer process to switch view to 'queue'
const mainWindow = appState.getMainWindow()
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("reset-view")
}
})

// IPC handler for analyzing audio from base64 data
ipcMain.handle("analyze-audio-base64", async (event, data: string, mimeType: string) => {
try {
Expand Down
14 changes: 12 additions & 2 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ interface ElectronAPI {
analyzeAudioFile: (path: string) => Promise<{ text: string; timestamp: number }>
analyzeImageFile: (path: string) => Promise<void>
quitApp: () => Promise<void>

toggleWindow: () => Promise<void>
showWindow: () => Promise<void>
hideWindow: () => Promise<void>
processScreenshots: () => Promise<void>
resetView: () => Promise<void>

// LLM Model Management
getCurrentLlmConfig: () => Promise<{ provider: "ollama" | "gemini"; model: string; isOllama: boolean }>
getAvailableOllamaModels: () => Promise<string[]>
Expand Down Expand Up @@ -179,7 +184,12 @@ contextBridge.exposeInMainWorld("electronAPI", {
analyzeAudioFile: (path: string) => ipcRenderer.invoke("analyze-audio-file", path),
analyzeImageFile: (path: string) => ipcRenderer.invoke("analyze-image-file", path),
quitApp: () => ipcRenderer.invoke("quit-app"),

toggleWindow: () => ipcRenderer.invoke("toggle-window"),
showWindow: () => ipcRenderer.invoke("show-window"),
hideWindow: () => ipcRenderer.invoke("hide-window"),
processScreenshots: () => ipcRenderer.invoke("process-screenshots"),
resetView: () => ipcRenderer.invoke("reset-view"),

// LLM Model Management
getCurrentLlmConfig: () => ipcRenderer.invoke("get-current-llm-config"),
getAvailableOllamaModels: () => ipcRenderer.invoke("get-available-ollama-models"),
Expand Down
Loading