From ad3d7bf2d2fca42d089146049fbe0a45807a2632 Mon Sep 17 00:00:00 2001 From: Louis Beaumont Date: Sat, 22 Feb 2025 14:43:20 -0800 Subject: [PATCH] release-app --- Cargo.toml | 1 - screenpipe-actions/Cargo.toml | 27 - screenpipe-actions/ai-proxy/bun.lockb | Bin 63820 -> 0 bytes screenpipe-actions/src/call_ai.rs | 259 --------- screenpipe-actions/src/lib.rs | 9 - screenpipe-actions/src/main.rs | 11 - .../src/monitor_keystroke_commands.rs | 101 ---- .../src/print_all_attributes.swift | 169 ------ screenpipe-actions/src/run.rs | 491 ------------------ screenpipe-actions/src/type_and_animate.rs | 93 ---- .../components/pipe-store.tsx | 2 +- screenpipe-app-tauri/src-tauri/Cargo.toml | 2 +- screenpipe-audio/Cargo.toml | 2 - screenpipe-core/Cargo.toml | 6 +- screenpipe-core/tests/pipes_test.rs | 255 +-------- screenpipe-events/Cargo.toml | 2 +- .../ai-proxy/.editorconfig | 0 .../ai-proxy/.gitignore | 0 .../ai-proxy/.prettierrc | 0 .../ai-proxy/package-lock.json | 0 .../ai-proxy/package.json | 0 .../ai-proxy/src/index.ts | 2 +- .../ai-proxy/src/providers/anthropic.ts | 0 .../ai-proxy/src/providers/base.ts | 0 .../ai-proxy/src/providers/gemini.ts | 0 .../ai-proxy/src/providers/index.ts | 0 .../ai-proxy/src/providers/openai.ts | 0 .../ai-proxy/src/types.ts | 0 .../ai-proxy/tsconfig.json | 0 .../ai-proxy/worker-configuration.d.ts | 0 .../ai-proxy/wrangler.toml | 0 screenpipe-server/Cargo.toml | 3 - .../src/bin/screenpipe-server.rs | 42 +- screenpipe-server/src/cli.rs | 11 +- screenpipe-vision/Cargo.toml | 6 - 35 files changed, 10 insertions(+), 1484 deletions(-) delete mode 100644 screenpipe-actions/Cargo.toml delete mode 100755 screenpipe-actions/ai-proxy/bun.lockb delete mode 100644 screenpipe-actions/src/call_ai.rs delete mode 100644 screenpipe-actions/src/lib.rs delete mode 100644 screenpipe-actions/src/main.rs delete mode 100644 screenpipe-actions/src/monitor_keystroke_commands.rs delete mode 100644 screenpipe-actions/src/print_all_attributes.swift delete mode 100644 screenpipe-actions/src/run.rs delete mode 100644 screenpipe-actions/src/type_and_animate.rs rename {screenpipe-actions => screenpipe-js}/ai-proxy/.editorconfig (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/.gitignore (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/.prettierrc (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/package-lock.json (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/package.json (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/src/index.ts (99%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/src/providers/anthropic.ts (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/src/providers/base.ts (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/src/providers/gemini.ts (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/src/providers/index.ts (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/src/providers/openai.ts (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/src/types.ts (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/tsconfig.json (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/worker-configuration.d.ts (100%) rename {screenpipe-actions => screenpipe-js}/ai-proxy/wrangler.toml (100%) diff --git a/Cargo.toml b/Cargo.toml index 258914cd70..9e496ec9a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ "screenpipe-audio", "screenpipe-server", "screenpipe-integrations", - "screenpipe-actions", "screenpipe-events", ] exclude = ["screenpipe-app-tauri/src-tauri"] diff --git a/screenpipe-actions/Cargo.toml b/screenpipe-actions/Cargo.toml deleted file mode 100644 index 7f4babb4ba..0000000000 --- a/screenpipe-actions/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "screenpipe-actions" -version = { workspace = true } -authors = { workspace = true } -description = { workspace = true } -repository = { workspace = true } -license = { workspace = true } -edition = { workspace = true } - -[lib] -name = "screenpipe_actions" -path = "src/lib.rs" - -[[bin]] -name = "screenpipe-actions" -path = "src/main.rs" - -[dependencies] -anyhow = "1.0" -reqwest = { version = "0.11", features = ["json"] } -serde_json = "1.0" -tokio = { version = "1.26", features = ["full"] } -rdev = "0.5.3" -enigo = "0.1.3" -serde = { version = "1.0", features = ["derive"] } -tracing = { workspace = true } -tracing-subscriber = { workspace = true } diff --git a/screenpipe-actions/ai-proxy/bun.lockb b/screenpipe-actions/ai-proxy/bun.lockb deleted file mode 100755 index fc36b6d83a9112d3862aab5166180e03af44ae96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63820 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
r!6omBYYyE_RyAXQ>NvP&z@QhaeB8? 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

)qg^hBac8Uzz zO(+08T%g9$Tq2E4!jDRZJ&I*eA6 z){$*75%SZWF)1wq%~A8_jhY2d5&Tpt_^MGsJT2L1xOk-w+YRc?_7wOVWjGEgdM4FL z=P!BLs5cvdAM4eUI-WAYB-lf{wr#uA}FTQW#5G%cm;=n3XGndp?T`bk7x@~~!eq+VMqxd2-1e_uipP-mUOjys8=v!0Cg!q;U zJ^D&~DhpFPaUBm%@dafSJ5$o9t9|xd*_sGWT=9pfc~FbN9<1OnDNJVjK>~Q~4`^-p z>626UVBw$O5T{Ma7=tDRticL|Rdi;>#^idwG$$hgw_#cmSho!cL;n;}_waL{R7P0Qz!1d)1S8Kma!J<#8eA(hQba(y!MD5MF^-IqVcHo=<>L ze6b@7b4s@Qf=TEx-{6JlaY42V>=ac%6NNpa$ks{!;cr|!pH-5fJ8gjFd}8wOWRX`F z9>PhCmAAGjDpG%z(yV6U$X@wrPNXK>ok*N2*;K9xzwlw7Txr*-DKClA*kh}-9?zvT zuT?Y!uUf6Nt7aOZ1qXl?Fp%kFCdpYsx?lEOlz0tL25*H_8>CGoO#Cj17B}^r5kmrk z?oQJe@nyb}Nj5cGsm?~)%N2^iZcr+#Y8z_n6|G)VtEv+4?{23Z9dymjve-#WM582( zWO|(o15B^;39X{AMZWKRU4hkCf*q%vI=FeU?Y9RK0mNM`Ew9}c0N#iQr*IwP$OReg z$&6B0BO$6rx@USSnaOn&;k9W+S=FpTKN{`mn~?|Kfys~Ejb6jk^u+*WR9>t=*0~f=m%xlLS^#I9Jss!Tm*5;rSps> z@w%21Gb#cXU#dt!1`lUqo$e&)OR`*EBfxqE3_tw1SI}b*5Mjg0a|uAmtMR-%SyVxk z3`n%_fG`w#a7NRM+h$HS*j|i%Q&u%h&CF%lAt_mK)9o3KH))uURvh_LD%?=fd!`Uh zNDV+Mb)0@I7UQrIfl&#J&|e|T$>$Yo-b15vOzafuXx zcEAagF^=%v$-{OJyUskg71)0w*Fb7wKUFM?waAWlb|WC!ABd}}?Ob3~*Dc&BsaXVR zk} zbUKrp4u0GP2L8#zWWz@9|64K4m?k@2`&t3QS0c@^yXLDGj^HFRdyN3&6er z=1ywD+)__xkuV-R+mCKV+TVT|NNUoUP)!T@l=5iKjOwz5&ua-Vn}yds%wV`$3^wI=G^Y%a8PD_y`+g5?cZ&Uq~Sg3?=}`yznhXGTx-MYpE1&=&|YPw3WqM6^V$A z#dy$H00Z4n@|^U`$0A%Y-ZLcq)wYy$94D(7>t+9WT<&OH9B7wwpnLo7Gh``Mxggy1?Mz_!;i6EXZ!qpL(i`ou0=ZKMaoq1?1@Wy3V0@)x1@hJ#I9}mcVQe+V zvI52Z?i&bes0+iTz5$61;6jOV-@tf>xlml{8<4YuT`Z@fZ*am)xM-r>H!!hDJe$-v z7-y5%I9%#8kUKEI94m;=Dg(w>>MM{lFbAqnL-U*pgz^fZisDbamkek4jE6(c(LsX+vp(AhxuTF6v zj@NorlK2-~rt&L!z&oEFJf7t0{JviT;I;vR`;9<6JTDZK5$wSVh!LLhz^G{sz%0J_ z-j(z6sKCWDQ6^Xk*E8oAUBKvkVoJgvsroLy!0Icpc7)w|I}v2?3NHV0 z4Zxg{&98@nq4O|iTQhOeMYfWE=|H>#p~vgFtU%^>^6tZES>8g%20>(GmWL0S!B)vk z3!c(RmaV7e1%25ltfykkcVtUqk zGGP9TXY{~YpI^^f7IraPc=>4JRX9EJ0&MUaoEIj{*3vU%aPn*9-ncD)|M@raz{#(I z48Dw!^Q$|OCE-VB{L3{!^9rz*l>e45r(ZT_Dj32yHC)DGB8M%0<%dA+-xT`C`|tk) D1GW9L diff --git a/screenpipe-actions/src/call_ai.rs b/screenpipe-actions/src/call_ai.rs deleted file mode 100644 index e9396349ab..0000000000 --- a/screenpipe-actions/src/call_ai.rs +++ /dev/null @@ -1,259 +0,0 @@ -use anyhow::{Result, Context}; -use reqwest::Client; -use serde_json::{json, Value}; -// use base64::{engine::general_purpose, Engine as _}; -// use image::{DynamicImage, ImageFormat}; -// use std::io::Cursor; - -pub async fn call_ai(prompt: String, context: String, expect_json: bool) -> Result { - let client = Client::new(); - - let messages = vec![ - json!({ - "role": "system", - "content": context - }), - json!({ - "role": "user", - "content": prompt - }) - ]; - - let mut body = json!({ - "model": "gpt-4o", - "messages": messages, - "temperature": 0.2, - "stream": false - }); - - if expect_json { - body["response_format"] = json!({"type": "json_object"}); - } - - let response: Value = client.post("https://ai-proxy.i-f9f.workers.dev/v1/chat/completions") - .json(&body) - .send() - .await? - .json() - .await?; - - // Log the entire response for debugging - // println!("raw api response: {}", serde_json::to_string_pretty(&response)?); - - if response.get("error").is_some() { - // If there's an error, return it as a string - let error_message = response["error"]["message"] - .as_str() - .unwrap_or("Unknown error") - .to_string(); - return Err(anyhow::anyhow!(error_message)); - } - - let content = response["choices"] - .get(0) - .and_then(|choice| choice["message"]["content"].as_str()) - .context("failed to extract content from response")? - .to_string(); - - if expect_json { - let json_value: Value = serde_json::from_str(&content) - .context("response content is not valid JSON")?; - - if let Some(array) = json_value["response"].as_array() { - // If "response" is an array, join its elements with newlines - Ok(array.iter() - .filter_map(|v| v.as_str()) - .collect::>() - .join("\n")) - } else if let Some(response_str) = json_value["response"].as_str() { - // If "response" is a string, return it directly - Ok(response_str.to_string()) - } else { - // If "response" is neither an array nor a string, return the whole JSON - Ok(content) - } - } else { - Ok(content) - } -} - -// Commented out unused enum and functions -// pub enum AIProvider { -// OpenAI, -// Claude, -// } - -// pub async fn call_ai_with_screenshot(provider: AIProvider, prompt: String, expect_json: bool, screenshot: DynamicImage) -> Result { -// match provider { -// AIProvider::OpenAI => call_openai_with_screenshot(prompt, expect_json, screenshot).await, -// AIProvider::Claude => call_claude_with_screenshot(prompt, expect_json, screenshot).await, -// } -// } - -// pub async fn call_openai_with_screenshot(prompt: String, expect_json: bool, screenshot: DynamicImage) -> Result { -// dotenv().ok(); // Load .env file -// let api_key = std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY not set"); - -// let client = Client::new(); - -// // Convert image to base64 -// let mut image_buffer = Vec::new(); -// screenshot.write_to(&mut Cursor::new(&mut image_buffer), ImageFormat::Png) -// .context("failed to write image to buffer")?; -// let base64_image = general_purpose::STANDARD.encode(&image_buffer); - -// let messages = vec![ -// json!({ -// "role": "user", -// "content": [ -// { -// "type": "text", -// "text": prompt -// }, -// { -// "type": "image_url", -// "image_url": { -// "url": format!("data:image/png;base64,{}", base64_image) -// } -// } -// ] -// }) -// ]; - -// let mut body = json!({ -// "model": "gpt-4o", -// "messages": messages, -// "temperature": 0.2 -// }); - -// if expect_json { -// body["response_format"] = json!({"type": "json_object"}); -// } - -// let response: Value = client.post("https://api.openai.com/v1/chat/completions") -// .header("Authorization", format!("Bearer {}", api_key)) -// .json(&body) -// .send() -// .await? -// .json() -// .await?; - -// // Log the entire response for debugging -// // println!("raw api response: {}", serde_json::to_string_pretty(&response)?); - -// if response.get("error").is_some() { -// let error_message = response["error"]["message"] -// .as_str() -// .unwrap_or("unknown error") -// .to_string(); -// return Err(anyhow::anyhow!(error_message)); -// } - -// let choice = response["choices"] -// .get(0) -// .ok_or_else(|| anyhow::anyhow!("no choices in response"))?; - -// if let Some(refusal) = choice["message"]["refusal"].as_str() { -// return Ok(refusal.to_string()); -// } - -// let content = choice["message"]["content"] -// .as_str() -// .context("failed to extract content from response")? -// .to_string(); - -// if expect_json { -// serde_json::from_str::(&content) -// .context("response content is not valid JSON")?; -// } - -// Ok(content) -// } - -// pub async fn call_claude_with_screenshot(prompt: String, expect_json: bool, screenshot: DynamicImage) -> Result { -// dotenv().ok(); // load .env file -// let api_key = std::env::var("ANTHROPIC_API_KEY").context("ANTHROPIC_API_KEY not set")?; - -// let client = Client::new(); - -// // Convert image to base64 -// let mut image_buffer = Vec::new(); -// screenshot.write_to(&mut Cursor::new(&mut image_buffer), ImageFormat::Png) -// .context("failed to write image to buffer")?; -// let base64_image = general_purpose::STANDARD.encode(&image_buffer); - -// let system_message = if expect_json { -// "You must respond in valid JSON format. Always wrap your response in a JSON object with a 'response' key." -// } else { -// "" -// }; - -// let user_message = if expect_json { -// format!("{}\nRemember to format your entire response as a JSON object with a 'response' key.", prompt) -// } else { -// prompt -// }; - -// let body = json!({ -// "model": "claude-3-5-sonnet-20240620", -// "max_tokens": 1024, -// "temperature": 0.2, -// "system": system_message, -// "messages": [ -// { -// "role": "user", -// "content": [ -// { -// "type": "image", -// "source": { -// "type": "base64", -// "media_type": "image/png", -// "data": base64_image -// } -// }, -// { -// "type": "text", -// "text": user_message -// } -// ] -// } -// ] -// }); - -// let response: Value = client.post("https://api.anthropic.com/v1/messages") -// .header("x-api-key", &api_key) -// .header("anthropic-version", "2023-06-01") -// .header("content-type", "application/json") -// .json(&body) -// .send() -// .await? -// .json() -// .await?; - -// // log the entire response for debugging -// println!("raw api response: {}", serde_json::to_string_pretty(&response)?); - -// if response.get("error").is_some() { -// let error_message = response["error"]["message"] -// .as_str() -// .unwrap_or("unknown error") -// .to_string(); -// return Err(anyhow::anyhow!(error_message)); -// } - -// let content = response["content"] -// .get(0) -// .and_then(|content| content["text"].as_str()) -// .context("failed to extract content from response")?; - -// if expect_json { -// // Validate that the content is valid JSON -// serde_json::from_str::(content) -// .context("response content is not valid JSON")?; - -// // Return the full JSON string -// Ok(content.to_string()) -// } else { -// Ok(content.to_string()) -// } -// } diff --git a/screenpipe-actions/src/lib.rs b/screenpipe-actions/src/lib.rs deleted file mode 100644 index b50ed4e496..0000000000 --- a/screenpipe-actions/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod call_ai; -mod monitor_keystroke_commands; -mod run; -pub mod type_and_animate; - -pub use call_ai::call_ai; -pub use monitor_keystroke_commands::{run_keystroke_monitor, KeystrokeCommand}; -pub use run::run; -pub use type_and_animate::{delete_characters, trigger_keyboard_permission, type_slowly}; // Export the run function from the run module diff --git a/screenpipe-actions/src/main.rs b/screenpipe-actions/src/main.rs deleted file mode 100644 index 411757c97a..0000000000 --- a/screenpipe-actions/src/main.rs +++ /dev/null @@ -1,11 +0,0 @@ -use screenpipe_actions::run; -use tracing::Level; - -fn main() -> anyhow::Result<()> { - // try to enable tracing subscriber if not already running - let subscriber = tracing_subscriber::fmt::Subscriber::builder() - .with_max_level(Level::INFO) - .finish(); - tracing::subscriber::set_global_default(subscriber).unwrap(); - tokio::runtime::Runtime::new()?.block_on(run()) -} diff --git a/screenpipe-actions/src/monitor_keystroke_commands.rs b/screenpipe-actions/src/monitor_keystroke_commands.rs deleted file mode 100644 index 9f61173cb5..0000000000 --- a/screenpipe-actions/src/monitor_keystroke_commands.rs +++ /dev/null @@ -1,101 +0,0 @@ -use rdev::{listen, Event, EventType, Key}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use tokio::sync::{mpsc, Mutex}; -use tracing::error; - -pub enum KeystrokeCommand { - DoubleSlash, - Stop, - // Add other commands as needed -} - -pub struct KeystrokeMonitor { - tx: mpsc::Sender, - last_slash: Arc>>, - shift_pressed: Arc, - ctrl_pressed: Arc, -} - -impl KeystrokeMonitor { - pub fn new(tx: mpsc::Sender) -> Self { - KeystrokeMonitor { - tx, - last_slash: Arc::new(Mutex::new(None)), - shift_pressed: Arc::new(AtomicBool::new(false)), - ctrl_pressed: Arc::new(AtomicBool::new(false)), - } - } - - pub async fn start_monitoring(&self) -> anyhow::Result<()> { - let tx = self.tx.clone(); - let last_slash = self.last_slash.clone(); - let shift_pressed = self.shift_pressed.clone(); - let ctrl_pressed = self.ctrl_pressed.clone(); - tokio::task::spawn_blocking(move || { - if let Err(error) = listen(move |event| { - let _ = handle_event(event, &tx, &last_slash, &shift_pressed, &ctrl_pressed); - }) { - error!("error: {:?}", error) - } - }); - - Ok(()) - } -} - -fn handle_event( - event: Event, - tx: &mpsc::Sender, - last_slash: &Arc>>, - shift_pressed: &Arc, - ctrl_pressed: &Arc, -) -> anyhow::Result<()> { - match event.event_type { - EventType::KeyPress(key) => { - match key { - Key::ShiftLeft | Key::ShiftRight => { - shift_pressed.store(true, Ordering::SeqCst); - } - Key::ControlLeft | Key::ControlRight => { - ctrl_pressed.store(true, Ordering::SeqCst); - } - Key::KeyQ => { - tx.blocking_send(KeystrokeCommand::Stop)?; - return Ok(()); - } - Key::Slash | Key::Num7 if shift_pressed.load(Ordering::SeqCst) => { - // Monitoring for slash (/) or Shift+7 combination - let mut last_slash_guard = last_slash.blocking_lock(); - let now = std::time::Instant::now(); - if let Some(last) = *last_slash_guard { - if now.duration_since(last).as_millis() < 500 { - tx.blocking_send(KeystrokeCommand::DoubleSlash)?; - *last_slash_guard = None; - return Ok(()); - } - } - *last_slash_guard = Some(now); - } - _ => {} - } - } - EventType::KeyRelease(key) => match key { - Key::ShiftLeft | Key::ShiftRight => { - shift_pressed.store(false, Ordering::SeqCst); - } - Key::ControlLeft | Key::ControlRight => { - ctrl_pressed.store(false, Ordering::SeqCst); - } - _ => {} - }, - _ => {} - } - Ok(()) -} - -pub async fn run_keystroke_monitor(tx: mpsc::Sender) -> anyhow::Result<()> { - let monitor = KeystrokeMonitor::new(tx); - monitor.start_monitoring().await?; - Ok(()) -} diff --git a/screenpipe-actions/src/print_all_attributes.swift b/screenpipe-actions/src/print_all_attributes.swift deleted file mode 100644 index 4b753e0201..0000000000 --- a/screenpipe-actions/src/print_all_attributes.swift +++ /dev/null @@ -1,169 +0,0 @@ -import Cocoa -import ApplicationServices -import Foundation - -class QueueElement { - let element: AXUIElement - let depth: Int - - init(_ element: AXUIElement, depth: Int) { - self.element = element - self.depth = depth - } -} - -func printAllAttributeValues(_ startElement: AXUIElement) { - var elements: [(CGPoint, CGSize, String)] = [] - var visitedElements = Set() - let unwantedValues = ["0", "", "", "3", ""] - let unwantedLabels = [ - "window", "application", "group", "button", "image", "text", - "pop up button", "region", "notifications", "table", "column", - "html content" - ] - - func traverseHierarchy(_ element: AXUIElement, depth: Int) { - guard !visitedElements.contains(element) else { return } - visitedElements.insert(element) - - var attributeNames: CFArray? - let result = AXUIElementCopyAttributeNames(element, &attributeNames) - - guard result == .success, let attributes = attributeNames as? [String] else { return } - - var position: CGPoint = .zero - var size: CGSize = .zero - - // Get position - if let positionValue = getAttributeValue(element, forAttribute: kAXPositionAttribute) as! AXValue?, - AXValueGetType(positionValue) == .cgPoint { - AXValueGetValue(positionValue, .cgPoint, &position) - } - - // Get size - if let sizeValue = getAttributeValue(element, forAttribute: kAXSizeAttribute) as! AXValue?, - AXValueGetType(sizeValue) == .cgSize { - AXValueGetValue(sizeValue, .cgSize, &size) - } - - for attr in attributes { - if ["AXDescription", "AXValue", "AXLabel", "AXRoleDescription", "AXHelp"].contains(attr) { - if let value = getAttributeValue(element, forAttribute: attr) { - let valueStr = describeValue(value) - if !valueStr.isEmpty && !unwantedValues.contains(valueStr) && valueStr.count > 1 && - !unwantedLabels.contains(valueStr.lowercased()) { - elements.append((position, size, valueStr)) - } - } - } - - // Traverse child elements - if let childrenValue = getAttributeValue(element, forAttribute: attr) { - if let elementArray = childrenValue as? [AXUIElement] { - for childElement in elementArray { - traverseHierarchy(childElement, depth: depth + 1) - } - } else if let childElement = childrenValue as! AXUIElement? { - traverseHierarchy(childElement, depth: depth + 1) - } - } - } - } - - traverseHierarchy(startElement, depth: 0) - - // Sort elements from top to bottom, then left to right - elements.sort { (a, b) in - if a.0.y != b.0.y { - return a.0.y < b.0.y - } else { - return a.0.x < b.0.x - } - } - - // Deduplicate and print sorted elements to stdout, excluding coordinates - var uniqueValues = Set() - for (_, _, valueStr) in elements { - if uniqueValues.insert(valueStr).inserted { - print(valueStr) - } - } -} - -func formatCoordinates(_ position: CGPoint, _ size: CGSize) -> String { - return String(format: "(x:%.0f,y:%.0f,w:%.0f,h:%.0f)", position.x, position.y, size.width, size.height) -} - -func describeValue(_ value: AnyObject?) -> String { - switch value { - case let string as String: - return string - case let number as NSNumber: - return number.stringValue - case let point as NSPoint: - return "(\(point.x), \(point.y))" - case let size as NSSize: - return "w=\(size.width) h=\(size.height)" - case let rect as NSRect: - return "x=\(rect.origin.x) y=\(rect.origin.y) w=\(rect.size.width) h=\(rect.size.height)" - case let range as NSRange: - return "loc=\(range.location) len=\(range.length)" - case let url as URL: - return url.absoluteString - case let array as [AnyObject]: - return array.isEmpty ? "Empty array" : "Array with \(array.count) elements" - case let axValue as AXValue: - return describeAXValue(axValue) - case is AXUIElement: - return "AXUIElement" - case .none: - return "None" - default: - return String(describing: value) - } -} - -func describeAXValue(_ axValue: AXValue) -> String { - let type = AXValueGetType(axValue) - switch type { - case .cgPoint: - var point = CGPoint.zero - AXValueGetValue(axValue, .cgPoint, &point) - return "(\(point.x), \(point.y))" - case .cgSize: - var size = CGSize.zero - AXValueGetValue(axValue, .cgSize, &size) - return "w=\(size.width) h=\(size.height)" - case .cgRect: - var rect = CGRect.zero - AXValueGetValue(axValue, .cgRect, &rect) - return "x=\(rect.origin.x) y=\(rect.origin.y) w=\(rect.size.width) h=\(rect.size.height)" - case .cfRange: - var range = CFRange(location: 0, length: 0) - AXValueGetValue(axValue, .cfRange, &range) - return "loc=\(range.location) len=\(range.length)" - default: - return "Unknown AXValue type" - } -} - -func getAttributeValue(_ element: AXUIElement, forAttribute attr: String) -> AnyObject? { - var value: AnyObject? - let result = AXUIElementCopyAttributeValue(element, attr as CFString, &value) - return result == .success ? value : nil -} - -func printAllAttributeValuesForCurrentApp() { - guard let app = NSWorkspace.shared.frontmostApplication else { - return - } - - let pid = app.processIdentifier - let axApp = AXUIElementCreateApplication(pid) - - print("attribute values for \(app.localizedName ?? "unknown app"):") - printAllAttributeValues(axApp) -} - -// usage -printAllAttributeValuesForCurrentApp() diff --git a/screenpipe-actions/src/run.rs b/screenpipe-actions/src/run.rs deleted file mode 100644 index 9c68ddfbb6..0000000000 --- a/screenpipe-actions/src/run.rs +++ /dev/null @@ -1,491 +0,0 @@ -use crate::type_and_animate::{delete_characters, type_slowly, EnigoCommand}; -use crate::{call_ai, run_keystroke_monitor, KeystrokeCommand}; -use std::path::Path; -use std::string::ToString; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use tokio::io::AsyncWriteExt; -use tokio::process::Command; -use tokio::sync::mpsc; -use tokio::task; -use tokio::time::Instant; -use tracing::{error, info}; - -// TODO: make this function not prevent ctrl+c in the future -pub async fn run() -> anyhow::Result<()> { - info!("starting keystroke monitor. press '//' to print attributes for the current app and call openai. press 'ctrl+q' to stop."); - let (tx, mut rx) = mpsc::channel(100); - - let (_enigo_tx, mut enigo_rx) = mpsc::channel(100); - - // Spawn the Enigo handler thread - task::spawn_blocking(move || { - use enigo::KeyboardControllable; - let mut enigo = enigo::Enigo::new(); - while let Some(command) = enigo_rx.blocking_recv() { - match command { - EnigoCommand::TypeCharacter(c) => { - enigo.key_click(enigo::Key::Layout(c)); - } - EnigoCommand::TypeString(s) => { - enigo.key_sequence(&s); - } - EnigoCommand::DeleteCharacter => { - enigo.key_click(enigo::Key::Backspace); - } - EnigoCommand::Shutdown => { - info!("Shutting down Enigo thread."); - break; - } - } - } - }); - - // Spawn the keystroke monitor - let tx_clone = tx.clone(); - task::spawn(async move { - if let Err(e) = run_keystroke_monitor(tx_clone).await { - eprintln!("Error in keystroke monitoring: {:?}", e); - } - }); - - let stop_signal = Arc::new(AtomicBool::new(false)); - - loop { - tokio::select! { - Some(command) = rx.recv() => { - match command { - KeystrokeCommand::DoubleSlash => { - // Reset the stop signal at the start of a new action - stop_signal.store(false, Ordering::SeqCst); - - info!("double slash detected. calling ai..."); - - type_slowly("thinking".to_string(), stop_signal.clone()).await?; - let swift_output = run_swift_script().await?; - - // Use swift_output directly here - // For example, pass it to the LLM or process it further - - info!("swift output: {}", swift_output); - - let prompt = format!( - r#"Based on the following Swift output, - you need to continue where the "//" is. - Output your response in JSON format as follows: - {{ - "response": "Your response text here" - }} - The response should match the length, tone, and style of the message/prompt we are responding to. - It should not look like it was written by AI. - You are responding on behalf of the user, try to understand what is actually happening. - We also provide you with detailed print out of the entire desktop content for your reference below: - "{}" - "#, - swift_output - ); - - let start = Instant::now(); - let stop_signal_clone = stop_signal.clone(); - match call_ai(prompt, String::new(), true).await { - Ok(response) => { - let duration = start.elapsed(); - info!("{:.1?} - first call_ai", duration); - delete_characters("thinking".len()).await?; - delete_characters(2).await?; // Delete the double slash - info!("ai response: {}", response); - - // Spawn a new task for typing - let typing_task = task::spawn(async move { - let _ = type_slowly("[GENERIC_RESPONSE]".to_string(), stop_signal_clone.clone()).await; - let _ = type_slowly("\n".to_string(), stop_signal_clone.clone()).await; - let _ = type_slowly(response, stop_signal_clone).await; - }); - - // Wait for the typing task to complete or be interrupted - tokio::select! { - _ = typing_task => {}, - _ = rx.recv() => { - // If we receive any command while typing, stop the typing - stop_signal.store(true, Ordering::SeqCst); - } - } - } - Err(e) => { - let duration = start.elapsed(); - error!("{:.1?} - failed first call_ai", duration); - delete_characters("thinking".len()).await?; - error!("failed to get response from ai: {}", e); - continue; // skip the rest of the loop iteration - } - } - type_slowly("\n".to_string(), stop_signal.clone()).await?; - type_slowly("\n".to_string(), stop_signal.clone()).await?; - type_slowly("[RESPONSE_WITH_CONTEXT]".to_string(), stop_signal.clone()).await?; - - let context = format!("Swift output: {}", swift_output); - let prompt = r#"Based on the following Swift output, find double slash '//' to see where users curor is, - Now, provide 3 search queries to find relevant context in user files to continue the conversation. follow these guidelines: - - 1. focus on specificity, avoid generic words - 2. use noun phrases, target key concepts - 3. employ technical terms when appropriate - 4. consider synonyms and related concepts - 5. utilize proper nouns (names, places, products) - 6. incorporate timeframes if relevant - 7. prioritize unusual or unique words - 8. consider file types if applicable - - return a json object with an array of 3 queries, each query should be one to three words max. format: - { - "response": [ - "query1", - "query2", - "query3" - ] - }"#.to_string(); - - let start = Instant::now(); - match call_ai(prompt, context, true).await { - Ok(response) => { - let duration = start.elapsed(); - info!("{:.1?} - second call_ai", duration); - // println!("ai response: {}", response); - - // Split the response by newlines to get individual queries - let queries: Vec = response - .split('\n') - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) - .collect(); - - let mut search_results = Vec::new(); - - for query in &queries { - match search_localhost(query).await { - // used to be &query - Ok(search_result) => { - let truncated_result = - search_result.chars().take(100).collect::(); - let capitalized_query = query.to_uppercase(); - type_slowly(format!("[{}] ", capitalized_query), stop_signal.clone()).await?; - info!("search result for '{}': {}", query, truncated_result); - search_results.push(search_result); - } - Err(e) => { - error!("error searching localhost for '{}': {:?}", query, e) - } - } - } - - type_slowly("\n".to_string(), stop_signal.clone()).await?; - type_slowly("analyzing".to_string(), stop_signal.clone()).await?; - - // Final LLM call - let final_prompt = r#"Based on the desktop text and search results of my computer, draft a concise response. - Provide the most relevant message - Also provide who your message is addressed to ("TO_YOU" or "ON_YOUR_BEHALF") - You might be writing: a response, a follow-up, a suggestion, a clarification, or you might be puzzled - Output your response in JSON format as follows: - { - "response": "[ADDRESSED TO] Your response text here" - } - Ensure the response matches the length and tone of the message we are responding to. - Make the response concise and to the point. - The response should be casual, social media style. - It should not look like it was written by AI. - "#.to_string(); - let final_context = format!( - "swift output: {}\nfirst llm response: {}\nsearch results: {}", - swift_output, - response, - search_results.join("\n") - ); - - let start = Instant::now(); - match call_ai(final_prompt, final_context, true).await { - Ok(final_response) => { - let duration = start.elapsed(); - info!("{:.1?} - final call_ai", duration); - delete_characters("analyzing".len()).await?; - info!("<<>>: {}", final_response); - type_slowly(final_response, stop_signal.clone()).await?; - } - Err(e) => { - error!("error in final openai call: {:?}", e); - continue; - } - } - } - Err(e) => { - error!("error in second openai call: {}", e); - continue; - } - } - }, - KeystrokeCommand::Stop => { - info!("stop command received. stopping current action..."); - stop_signal.store(true, Ordering::SeqCst); - } - } - }, - else => break, - } - } - - // Cleanup code - info!("shutting down enigo thread..."); - if let Err(e) = _enigo_tx.send(EnigoCommand::Shutdown).await { - error!("failed to send shutdown command to enigo thread: {:?}", e); - } - - Ok(()) -} - -async fn run_swift_script() -> anyhow::Result { - let start = Instant::now(); - - let script_content = r#" -import Cocoa -import ApplicationServices -import Foundation - -class QueueElement { - let element: AXUIElement - let depth: Int - - init(_ element: AXUIElement, depth: Int) { - self.element = element - self.depth = depth - } -} - -func printAllAttributeValues(_ startElement: AXUIElement) { - var elements: [(CGPoint, CGSize, String)] = [] - var visitedElements = Set() - let unwantedValues = ["0", "", "", "3", ""] - let unwantedLabels = [ - "window", "application", "group", "button", "image", "text", - "pop up button", "region", "notifications", "table", "column", - "html content" - ] - - func traverseHierarchy(_ element: AXUIElement, depth: Int) { - guard !visitedElements.contains(element) else { return } - visitedElements.insert(element) - - var attributeNames: CFArray? - let result = AXUIElementCopyAttributeNames(element, &attributeNames) - - guard result == .success, let attributes = attributeNames as? [String] else { return } - - var position: CGPoint = .zero - var size: CGSize = .zero - - // Get position - if let positionValue = getAttributeValue(element, forAttribute: kAXPositionAttribute) as! AXValue?, - AXValueGetType(positionValue) == .cgPoint { - AXValueGetValue(positionValue, .cgPoint, &position) - } - - // Get size - if let sizeValue = getAttributeValue(element, forAttribute: kAXSizeAttribute) as! AXValue?, - AXValueGetType(sizeValue) == .cgSize { - AXValueGetValue(sizeValue, .cgSize, &size) - } - - for attr in attributes { - if ["AXDescription", "AXValue", "AXLabel", "AXRoleDescription", "AXHelp"].contains(attr) { - if let value = getAttributeValue(element, forAttribute: attr) { - let valueStr = describeValue(value) - if !valueStr.isEmpty && !unwantedValues.contains(valueStr) && valueStr.count > 1 && - !unwantedLabels.contains(valueStr.lowercased()) { - elements.append((position, size, valueStr)) - } - } - } - - // Traverse child elements - if let childrenValue = getAttributeValue(element, forAttribute: attr) { - if let elementArray = childrenValue as? [AXUIElement] { - for childElement in elementArray { - traverseHierarchy(childElement, depth: depth + 1) - } - } else if let childElement = childrenValue as! AXUIElement? { - traverseHierarchy(childElement, depth: depth + 1) - } - } - } - } - - traverseHierarchy(startElement, depth: 0) - - // Sort elements from top to bottom, then left to right - elements.sort { (a, b) in - if a.0.y != b.0.y { - return a.0.y < b.0.y - } else { - return a.0.x < b.0.x - } - } - - // Deduplicate and print sorted elements to stdout, excluding coordinates - var uniqueValues = Set() - for (_, _, valueStr) in elements { - if uniqueValues.insert(valueStr).inserted { - print(valueStr) - } - } -} - -func formatCoordinates(_ position: CGPoint, _ size: CGSize) -> String { - return String(format: "(x:%.0f,y:%.0f,w:%.0f,h:%.0f)", position.x, position.y, size.width, size.height) -} - -func describeValue(_ value: AnyObject?) -> String { - switch value { - case let string as String: - return string - case let number as NSNumber: - return number.stringValue - case let point as NSPoint: - return "(\(point.x), \(point.y))" - case let size as NSSize: - return "w=\(size.width) h=\(size.height)" - case let rect as NSRect: - return "x=\(rect.origin.x) y=\(rect.origin.y) w=\(rect.size.width) h=\(rect.size.height)" - case let range as NSRange: - return "loc=\(range.location) len=\(range.length)" - case let url as URL: - return url.absoluteString - case let array as [AnyObject]: - return array.isEmpty ? "Empty array" : "Array with \(array.count) elements" - case let axValue as AXValue: - return describeAXValue(axValue) - case is AXUIElement: - return "AXUIElement" - case .none: - return "None" - default: - return String(describing: value) - } -} - -func describeAXValue(_ axValue: AXValue) -> String { - let type = AXValueGetType(axValue) - switch type { - case .cgPoint: - var point = CGPoint.zero - AXValueGetValue(axValue, .cgPoint, &point) - return "(\(point.x), \(point.y))" - case .cgSize: - var size = CGSize.zero - AXValueGetValue(axValue, .cgSize, &size) - return "w=\(size.width) h=\(size.height)" - case .cgRect: - var rect = CGRect.zero - AXValueGetValue(axValue, .cgRect, &rect) - return "x=\(rect.origin.x) y=\(rect.origin.y) w=\(rect.size.width) h=\(rect.size.height)" - case .cfRange: - var range = CFRange(location: 0, length: 0) - AXValueGetValue(axValue, .cfRange, &range) - return "loc=\(range.location) len=\(range.length)" - default: - return "Unknown AXValue type" - } -} - -func getAttributeValue(_ element: AXUIElement, forAttribute attr: String) -> AnyObject? { - var value: AnyObject? - let result = AXUIElementCopyAttributeValue(element, attr as CFString, &value) - return result == .success ? value : nil -} - -func printAllAttributeValuesForCurrentApp() { - guard let app = NSWorkspace.shared.frontmostApplication else { - return - } - - let pid = app.processIdentifier - let axApp = AXUIElementCreateApplication(pid) - - print("attribute values for \(app.localizedName ?? "unknown app"):") - printAllAttributeValues(axApp) -} - -// usage -printAllAttributeValuesForCurrentApp() -"#; - - info!("running swift script"); - - // Check multiple possible Swift paths - let swift_paths = [ - "/usr/bin/swift", // Common path for both Intel and Apple Silicon - "/usr/local/bin/swift", // Possible alternative location - "/opt/homebrew/bin/swift", // Homebrew path on Apple Silicon - ]; - - let swift_path = swift_paths - .iter() - .find(|&path| Path::new(path).exists()) - .ok_or_else(|| { - anyhow::anyhow!("Swift executable not found in any of the expected locations") - })?; - - info!("using swift at: {}", swift_path); - - let mut child = Command::new(swift_path) - .arg("-") // Read from stdin - .stdin(std::process::Stdio::piped()) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn()?; - - // Write to stdin - if let Some(mut stdin) = child.stdin.take() { - stdin.write_all(script_content.as_bytes()).await?; - stdin.flush().await?; - } - - // Wait for the command to complete and get the output - let output = child.wait_with_output().await?; - - let duration = start.elapsed(); - info!("{:.1?} - run_swift_script", duration); - - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - - info!("debug: swift stdout length: {}", stdout.len()); - info!("debug: swift stderr length: {}", stderr.len()); - - if !output.status.success() { - info!( - "debug: swift script failed with status: {:?}", - output.status - ); - anyhow::bail!("swift script failed: {}", stderr); - } - - if stdout.is_empty() { - info!("debug: swift stdout is empty, returning stderr"); - Ok(stderr.into_owned()) - } else { - info!("debug: returning swift stdout"); - Ok(stdout.into_owned()) - } -} - -async fn search_localhost(query: &str) -> anyhow::Result { - let start = Instant::now(); - let client = reqwest::Client::new(); - let url = format!( - "http://localhost:3030/search?q={}&content_type=all&limit=5&offset=0", - query - ); - let response = client.get(&url).send().await?.text().await?; - let duration = start.elapsed(); - info!("{:.1?} - search_localhost for '{}'", duration, query); - Ok(response) -} diff --git a/screenpipe-actions/src/type_and_animate.rs b/screenpipe-actions/src/type_and_animate.rs deleted file mode 100644 index 328029a265..0000000000 --- a/screenpipe-actions/src/type_and_animate.rs +++ /dev/null @@ -1,93 +0,0 @@ -use anyhow::Result; -use enigo::{Enigo, Key, KeyboardControllable}; -use serde::Serialize; -use std::cell::RefCell; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use tokio::time::{sleep, Duration}; - -#[allow(dead_code)] -#[derive(Debug)] -pub enum EnigoCommand { - TypeCharacter(char), - TypeString(String), - DeleteCharacter, - Shutdown, -} - -#[derive(Debug, Serialize)] -pub struct EnigoResponse { - pub success: bool, - pub message: Option, -} - -thread_local! { - static ENIGO: RefCell> = const { RefCell::new(None) }; -} - -fn with_enigo(f: F) -> R -where - F: FnOnce(&mut Enigo) -> R, -{ - ENIGO.with(|cell| { - let mut enigo = cell.borrow_mut(); - if enigo.is_none() { - *enigo = Some(Enigo::new()); - } - f(enigo.as_mut().unwrap()) - }) -} - -pub async fn delete_characters(count: usize) -> Result<()> { - tokio::task::spawn_blocking(move || { - with_enigo(|enigo| { - enigo.key_sequence(&"\u{8}".repeat(count)); - }); - }) - .await?; - Ok(()) -} - -pub async fn type_slowly(text: String, stop_signal: Arc) -> Result<()> { - let text_len = text.len(); - for line in text.split('\n') { - if !line.is_empty() { - for char in line.chars() { - if stop_signal.load(Ordering::SeqCst) { - return Ok(()); - } - tokio::task::spawn_blocking(move || { - with_enigo(|enigo| { - enigo.key_sequence(&char.to_string()); - }); - }) - .await?; - sleep(Duration::from_millis(50)).await; - } - } - if text.contains('\n') { - if stop_signal.load(Ordering::SeqCst) { - return Ok(()); - } - tokio::task::spawn_blocking(move || { - with_enigo(|enigo| { - enigo.key_down(Key::Shift); - enigo.key_click(Key::Return); - enigo.key_up(Key::Shift); - }); - }) - .await?; - } - } - sleep(Duration::from_millis(text_len as u64)).await; - Ok(()) -} - -pub fn trigger_keyboard_permission() -> anyhow::Result<()> { - with_enigo(|enigo| { - // Perform a no-op key press to trigger the permission request - enigo.key_down(enigo::Key::Shift); - enigo.key_up(enigo::Key::Shift); - }); - Ok(()) -} diff --git a/screenpipe-app-tauri/components/pipe-store.tsx b/screenpipe-app-tauri/components/pipe-store.tsx index 1c157711b5..72e315e5a1 100644 --- a/screenpipe-app-tauri/components/pipe-store.tsx +++ b/screenpipe-app-tauri/components/pipe-store.tsx @@ -68,7 +68,7 @@ export const PipeStore: React.FC = () => { return downloadsB - downloadsA; } // Then by creation date - return new Date(b.created_at).getTime() - new Date(a.created_at).getTime(); + return new Date(b.created_at as string).getTime() - new Date(a.created_at as string).getTime(); }); // Add debounced search tracking diff --git a/screenpipe-app-tauri/src-tauri/Cargo.toml b/screenpipe-app-tauri/src-tauri/Cargo.toml index 3c36f89551..b204bd13eb 100644 --- a/screenpipe-app-tauri/src-tauri/Cargo.toml +++ b/screenpipe-app-tauri/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "screenpipe-app" -version = "0.36.5" +version = "0.36.6" description = "" authors = ["you"] license = "" diff --git a/screenpipe-audio/Cargo.toml b/screenpipe-audio/Cargo.toml index 783803e75b..9caa89807c 100644 --- a/screenpipe-audio/Cargo.toml +++ b/screenpipe-audio/Cargo.toml @@ -11,7 +11,6 @@ edition = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] reqwest = { workspace = true } -which = "7.0.0" [dependencies] serde = { version = "1.0", features = ["derive"] } @@ -101,7 +100,6 @@ objc = "0.2.7" tempfile = "3.3.0" infer = "0.15" criterion = { workspace = true } -memory-stats = "1.0" strsim = "0.10.0" futures = "0.3.31" tracing-subscriber = "0.3.16" diff --git a/screenpipe-core/Cargo.toml b/screenpipe-core/Cargo.toml index dba450a18c..fc461ba6da 100644 --- a/screenpipe-core/Cargo.toml +++ b/screenpipe-core/Cargo.toml @@ -22,9 +22,7 @@ hf-hub = { workspace = true, features = [ "tokio", "native-tls", ] } -screenpipe-actions = { path = "../screenpipe-actions", optional = true } http-cache-reqwest = "0.15.0" -reqwest = { workspace = true } reqwest-middleware = "0.4.0" tokio = { workspace = true } @@ -51,11 +49,13 @@ sentry = { workspace = true } zip = "0.6.2" tokio-stream = "0.1.17" +[dev-dependencies] +reqwest = { workspace = true } + [features] default = ["security"] security = ["dep:regex", "dep:lazy_static"] metal = ["candle/metal", "candle-nn/metal", "candle-transformers/metal"] cuda = ["candle/cuda", "candle-nn/cuda", "candle-transformers/cuda"] mkl = ["candle/mkl", "candle-nn/mkl", "candle-transformers/mkl"] -beta = ["dep:screenpipe-actions"] llm = [] diff --git a/screenpipe-core/tests/pipes_test.rs b/screenpipe-core/tests/pipes_test.rs index c3647a11f2..821fddd969 100644 --- a/screenpipe-core/tests/pipes_test.rs +++ b/screenpipe-core/tests/pipes_test.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "pipes")] #[cfg(test)] mod tests { use chrono::{TimeZone, Utc}; @@ -26,123 +25,6 @@ mod tests { }); } - async fn setup_test_pipe(temp_dir: &TempDir, pipe_name: &str, code: &str) -> PathBuf { - init(); - let pipe_dir = temp_dir.path().join(pipe_name); - create_dir_all(&pipe_dir).await.unwrap(); - let file_path = pipe_dir.join("pipe.ts"); - tokio::fs::write(&file_path, code).await.unwrap(); - pipe_dir - } - - #[tokio::test] - #[ignore] - async fn test_simple_pipe() { - let temp_dir = TempDir::new().unwrap(); - let screenpipe_dir = temp_dir.path().to_path_buf(); - - let code = r#" - console.log("Hello from simple pipe!"); - const result = 2 + 3; - console.log(`Result: ${result}`); - "#; - - let pipe_dir = setup_test_pipe(&temp_dir, "simple_pipe", code).await; - - let result = run_pipe(&pipe_dir.to_string_lossy(), screenpipe_dir).await; - assert!(result.is_ok()); - } - - #[tokio::test] - #[ignore] // TODO: fix this test (not implemented yet) - async fn test_pipe_with_http_request() { - let temp_dir = TempDir::new().unwrap(); - let screenpipe_dir = temp_dir.path().to_path_buf(); - - let code = r#" - console.log("Fetching data from API..."); - const response = await pipe.get("https://jsonplaceholder.typicode.com/todos/1"); - console.log(JSON.stringify(response, null, 2)); - "#; - - let pipe_dir = setup_test_pipe(&temp_dir, "http_pipe", code).await; - - let result = run_pipe(&pipe_dir.to_string_lossy(), screenpipe_dir).await; - assert!(result.is_ok()); - } - - #[tokio::test] - #[ignore] // TODO: fix this test (not implemented yet) - async fn test_pipe_with_error() { - let temp_dir = TempDir::new().unwrap(); - let screenpipe_dir = temp_dir.path().to_path_buf(); - - let code = r#" - console.log("This pipe will throw an error"); - throw new Error("Intentional error"); - "#; - - let pipe_dir = setup_test_pipe(&temp_dir, "error_pipe", code).await; - - let result = run_pipe(&pipe_dir.to_string_lossy(), screenpipe_dir).await; - assert!(result.is_err()); - } - - #[tokio::test] - #[ignore] // TODO: fix this test (file operations work but not in this test for some reason) - async fn test_pipe_with_file_operations() { - let temp_dir = TempDir::new().unwrap(); - let screenpipe_dir = temp_dir.path().to_path_buf(); - - let code = r#" - console.log("Writing to a file..."); - await pipe.writeFile("output.txt", "Hello, Screenpipe!"); - const content = await pipe.readFile("output.txt"); - console.log(`File content: ${content}`); - "#; - - let pipe_dir = setup_test_pipe(&temp_dir, "file_pipe", code).await; - - let result = run_pipe(&pipe_dir.to_string_lossy(), screenpipe_dir).await; - assert!(result.is_ok()); - - // Verify that the file was created and contains the expected content - let output_file = pipe_dir.join("output.txt"); - assert!(output_file.exists()); - let content = tokio::fs::read_to_string(output_file).await.unwrap(); - assert_eq!(content, "Hello, Screenpipe!"); - } - - #[tokio::test] - #[ignore] // Github said NO - async fn test_download_pipe_github_folder() { - init(); - let temp_dir = TempDir::new().unwrap(); - let screenpipe_dir = temp_dir.path().to_path_buf(); - - let github_url = - "https://github.com/mediar-ai/screenpipe/tree/main/pipes/pipe-stream-ocr-text"; - let result = download_pipe(github_url, screenpipe_dir.clone()).await; - - assert!( - result.is_ok(), - "Failed to download GitHub folder: {:?}", - result - ); - let pipe_dir = result.unwrap(); - assert!(pipe_dir.exists(), "Pipe directory does not exist"); - - let has_main_or_pipe_file = std::fs::read_dir(&pipe_dir).unwrap().any(|entry| { - let file_name = entry.unwrap().file_name().into_string().unwrap(); - (file_name.starts_with("main") || file_name.starts_with("pipe")) - && (file_name.ends_with(".ts") || file_name.ends_with(".js")) - }); - - assert!( - has_main_or_pipe_file, - "No main.ts, main.js, pipe.ts, or pipe.js file found" - ); - } #[tokio::test] async fn test_download_pipe_invalid_url() { @@ -156,142 +38,7 @@ mod tests { assert!(result.is_err(), "Expected an error for invalid URL"); } - #[tokio::test] - #[ignore] - async fn test_send_email() { - let temp_dir = TempDir::new().unwrap(); - let screenpipe_dir = temp_dir.path().to_path_buf(); - let to = std::env::var("EMAIL_TO").expect("EMAIL_TO not set"); - let from = std::env::var("EMAIL_FROM").expect("EMAIL_FROM not set"); - let password = std::env::var("EMAIL_PASSWORD").expect("EMAIL_PASSWORD not set"); - - println!("to: {}", to); - println!("from: {}", from); - println!("password: {}", password); - - // Test plain text email - let plain_text_code = format!( - r#" - (async () => {{ - const result = await pipe.sendEmail({{ - to: "{to}", - from: "{from}", - password: "{password}", - subject: "screenpipe test - plain text", - body: "yo louis, this is a plain text email test!", - contentType: "text/plain" - }}); - console.log("Plain text email result:", result); - if (!result) {{ - throw new Error("Failed to send plain text email"); - }} - }})(); - "# - ); - - // Test HTML email - let html_code = format!( - r#" - (async () => {{ - const result = await pipe.sendEmail({{ - to: "{to}", - from: "{from}", - password: "{password}", - subject: "screenpipe test - html", - body: ` - - -

yo louis, you absolute madlad!

-

this is an html email test from screenpipe.

-
    -
  • item 1
  • -
  • item 2
  • -
  • item 3
  • -
- - - `, - contentType: "text/html" - }}); - console.log("HTML email result:", result); - if (!result) {{ - throw new Error("Failed to send HTML email"); - }} - }})(); - "# - ); - - let pipe_dir = setup_test_pipe(&temp_dir, "email_test_pipe_plain", &plain_text_code).await; - std::env::set_current_dir(&pipe_dir).unwrap(); - let result = run_pipe(&pipe_dir.to_string_lossy(), screenpipe_dir.clone()).await; - assert!(result.is_ok(), "Plain text email test failed: {:?}", result); - - let pipe_dir = setup_test_pipe(&temp_dir, "email_test_pipe_html", &html_code).await; - std::env::set_current_dir(&pipe_dir).unwrap(); - let result = run_pipe(&pipe_dir.to_string_lossy(), screenpipe_dir).await; - assert!(result.is_ok(), "HTML email test failed: {:?}", result); - } - - #[tokio::test] - #[ignore] // works when run on click in cursor but not in cli so weird haha - async fn test_directory_functions() { - let temp_dir = TempDir::new().unwrap(); - let screenpipe_dir = temp_dir.path().to_path_buf(); - - let code = r#" - (async () => { - // Test mkdir - await fs.mkdir('test_dir'); - console.log('Directory created'); - - // Test writeFile - await fs.writeFile('test_dir/test_file.txt', 'Hello, World!'); - console.log('File written'); - - // Test readFile - const content = await fs.readFile('test_dir/test_file.txt'); - console.log('File content:', content); - if (content !== 'Hello, World!') { - throw new Error('File content mismatch'); - } - - // Test readdir - const files = await fs.readdir('test_dir'); - console.log('Directory contents:', files); - if (!files.includes('test_file.txt')) { - throw new Error('File not found in directory'); - } - - // Test path.join - const joinedPath = path.join('test_dir', 'nested', 'file.txt'); - console.log('Joined path:', joinedPath); - const expectedPath = process.env.OS === 'windows' ? 'test_dir\\nested\\file.txt' : 'test_dir/nested/file.txt'; - if (joinedPath !== expectedPath) { - throw new Error('Path join mismatch'); - } - - console.log('All directory function tests passed'); - })(); - "#; - - let pipe_dir = setup_test_pipe(&temp_dir, "directory_functions_test", code).await; - - // Change the working directory to the pipe directory - std::env::set_current_dir(&pipe_dir).unwrap(); - - let result = run_pipe(&pipe_dir.to_string_lossy(), screenpipe_dir).await; - assert!(result.is_ok(), "Pipe execution failed: {:?}", result); - - // Additional checks - let test_dir = pipe_dir.join("test_dir"); - assert!(test_dir.exists(), "Test directory was not created"); - - let test_file = test_dir.join("test_file.txt"); - assert!(test_file.exists(), "Test file was not created"); - - let file_content = std::fs::read_to_string(test_file).unwrap(); - assert_eq!(file_content, "Hello, World!", "File content mismatch"); - } + #[tokio::test] #[ignore] diff --git a/screenpipe-events/Cargo.toml b/screenpipe-events/Cargo.toml index a67dbfaf76..eec914a108 100644 --- a/screenpipe-events/Cargo.toml +++ b/screenpipe-events/Cargo.toml @@ -17,12 +17,12 @@ tokio-stream = { version = "0.1.17", features = ["sync"] } once_cell = { version = "1.18" } tracing.workspace = true parking_lot = "0.12.3" -serial_test = "3.2.0" chrono = "0.4.39" [dev-dependencies] serde = { version = "1.0", features = ["derive"] } criterion = { version = "0.5", features = ["async_tokio"] } +serial_test = "3.2.0" [[bench]] name = "events" diff --git a/screenpipe-actions/ai-proxy/.editorconfig b/screenpipe-js/ai-proxy/.editorconfig similarity index 100% rename from screenpipe-actions/ai-proxy/.editorconfig rename to screenpipe-js/ai-proxy/.editorconfig diff --git a/screenpipe-actions/ai-proxy/.gitignore b/screenpipe-js/ai-proxy/.gitignore similarity index 100% rename from screenpipe-actions/ai-proxy/.gitignore rename to screenpipe-js/ai-proxy/.gitignore diff --git a/screenpipe-actions/ai-proxy/.prettierrc b/screenpipe-js/ai-proxy/.prettierrc similarity index 100% rename from screenpipe-actions/ai-proxy/.prettierrc rename to screenpipe-js/ai-proxy/.prettierrc diff --git a/screenpipe-actions/ai-proxy/package-lock.json b/screenpipe-js/ai-proxy/package-lock.json similarity index 100% rename from screenpipe-actions/ai-proxy/package-lock.json rename to screenpipe-js/ai-proxy/package-lock.json diff --git a/screenpipe-actions/ai-proxy/package.json b/screenpipe-js/ai-proxy/package.json similarity index 100% rename from screenpipe-actions/ai-proxy/package.json rename to screenpipe-js/ai-proxy/package.json diff --git a/screenpipe-actions/ai-proxy/src/index.ts b/screenpipe-js/ai-proxy/src/index.ts similarity index 99% rename from screenpipe-actions/ai-proxy/src/index.ts rename to screenpipe-js/ai-proxy/src/index.ts index 32666f2da4..eaaec8626b 100644 --- a/screenpipe-actions/ai-proxy/src/index.ts +++ b/screenpipe-js/ai-proxy/src/index.ts @@ -598,7 +598,7 @@ export default Sentry.withSentry( /* terminal 1 -cd screenpipe-actions/ai-proxy +cd screenpipe-js/ai-proxy wrangler dev diff --git a/screenpipe-actions/ai-proxy/src/providers/anthropic.ts b/screenpipe-js/ai-proxy/src/providers/anthropic.ts similarity index 100% rename from screenpipe-actions/ai-proxy/src/providers/anthropic.ts rename to screenpipe-js/ai-proxy/src/providers/anthropic.ts diff --git a/screenpipe-actions/ai-proxy/src/providers/base.ts b/screenpipe-js/ai-proxy/src/providers/base.ts similarity index 100% rename from screenpipe-actions/ai-proxy/src/providers/base.ts rename to screenpipe-js/ai-proxy/src/providers/base.ts diff --git a/screenpipe-actions/ai-proxy/src/providers/gemini.ts b/screenpipe-js/ai-proxy/src/providers/gemini.ts similarity index 100% rename from screenpipe-actions/ai-proxy/src/providers/gemini.ts rename to screenpipe-js/ai-proxy/src/providers/gemini.ts diff --git a/screenpipe-actions/ai-proxy/src/providers/index.ts b/screenpipe-js/ai-proxy/src/providers/index.ts similarity index 100% rename from screenpipe-actions/ai-proxy/src/providers/index.ts rename to screenpipe-js/ai-proxy/src/providers/index.ts diff --git a/screenpipe-actions/ai-proxy/src/providers/openai.ts b/screenpipe-js/ai-proxy/src/providers/openai.ts similarity index 100% rename from screenpipe-actions/ai-proxy/src/providers/openai.ts rename to screenpipe-js/ai-proxy/src/providers/openai.ts diff --git a/screenpipe-actions/ai-proxy/src/types.ts b/screenpipe-js/ai-proxy/src/types.ts similarity index 100% rename from screenpipe-actions/ai-proxy/src/types.ts rename to screenpipe-js/ai-proxy/src/types.ts diff --git a/screenpipe-actions/ai-proxy/tsconfig.json b/screenpipe-js/ai-proxy/tsconfig.json similarity index 100% rename from screenpipe-actions/ai-proxy/tsconfig.json rename to screenpipe-js/ai-proxy/tsconfig.json diff --git a/screenpipe-actions/ai-proxy/worker-configuration.d.ts b/screenpipe-js/ai-proxy/worker-configuration.d.ts similarity index 100% rename from screenpipe-actions/ai-proxy/worker-configuration.d.ts rename to screenpipe-js/ai-proxy/worker-configuration.d.ts diff --git a/screenpipe-actions/ai-proxy/wrangler.toml b/screenpipe-js/ai-proxy/wrangler.toml similarity index 100% rename from screenpipe-actions/ai-proxy/wrangler.toml rename to screenpipe-js/ai-proxy/wrangler.toml diff --git a/screenpipe-server/Cargo.toml b/screenpipe-server/Cargo.toml index 5c487849e6..2d2f22b606 100644 --- a/screenpipe-server/Cargo.toml +++ b/screenpipe-server/Cargo.toml @@ -17,7 +17,6 @@ screenpipe-events = { path = "../screenpipe-events" } screenpipe-vision = { path = "../screenpipe-vision" } screenpipe-audio = { path = "../screenpipe-audio" } screenpipe-core = { path = "../screenpipe-core", features = ["security"] } -screenpipe-actions = { path = "../screenpipe-actions", optional = true } killport = { version = "1.1.0" } # Image processing @@ -131,7 +130,6 @@ tokio-tungstenite = "0.19.0" criterion = { workspace = true } rand = "0.8" -axum-test = "15.3.0" [[bench]] name = "db_benchmarks" @@ -147,7 +145,6 @@ metal = ["candle/metal", "candle-nn/metal", "candle-transformers/metal"] cuda = ["candle/cuda", "candle-nn/cuda", "candle-transformers/cuda"] mkl = ["candle/mkl", "candle-nn/mkl", "candle-transformers/mkl"] llm = [] -beta = ["screenpipe-core/beta", "dep:screenpipe-actions"] experimental = ["enigo"] debug-console = ["console-subscriber"] diff --git a/screenpipe-server/src/bin/screenpipe-server.rs b/screenpipe-server/src/bin/screenpipe-server.rs index d5272b39b5..eff369e43c 100644 --- a/screenpipe-server/src/bin/screenpipe-server.rs +++ b/screenpipe-server/src/bin/screenpipe-server.rs @@ -261,21 +261,7 @@ async fn main() -> anyhow::Result<()> { return Ok(()); } #[allow(unused_variables)] - Command::Setup { enable_beta } => { - #[cfg(feature = "beta")] - if enable_beta { - use screenpipe_actions::type_and_animate::trigger_keyboard_permission; - - // Trigger keyboard permission request - if let Err(e) = trigger_keyboard_permission() { - warn!("failed to trigger keyboard permission: {:?}", e); - warn!("please grant keyboard permission manually in System Preferences."); - } else { - info!( - "keyboard permission requested. please grant permission if prompted." - ); - } - } + Command::Setup {} => { use screenpipe_audio::{ trigger_audio_permission, vad_engine::SileroVad, whisper::WhisperModel, }; @@ -1015,32 +1001,6 @@ async fn main() -> anyhow::Result<()> { let ctrl_c_future = signal::ctrl_c(); pin_mut!(ctrl_c_future); - // only in beta and on macos - #[cfg(feature = "beta")] - { - if cli.enable_beta && cfg!(target_os = "macos") { - use screenpipe_actions::run; - - info!("beta feature enabled, starting screenpipe actions"); - - let shutdown_tx_clone = shutdown_tx.clone(); - tokio::spawn(async move { - let mut shutdown_rx = shutdown_tx_clone.subscribe(); - - tokio::select! { - result = run() => { - if let Err(e) = result { - error!("Error running screenpipe actions: {}", e); - } - } - _ = shutdown_rx.recv() => { - info!("Received shutdown signal, stopping screenpipe actions"); - } - } - }); - } - } - // Start the UI monitoring task #[cfg(target_os = "macos")] if cli.enable_ui_monitoring { diff --git a/screenpipe-server/src/cli.rs b/screenpipe-server/src/cli.rs index b53d3b2a7c..498877e7c9 100644 --- a/screenpipe-server/src/cli.rs +++ b/screenpipe-server/src/cli.rs @@ -241,11 +241,6 @@ pub struct Cli { #[arg(long, default_value_t = false)] pub enable_llm: bool, - /// Enable beta features - #[cfg(feature = "beta")] - #[arg(long, default_value_t = false)] - pub enable_beta: bool, - /// Enable UI monitoring (macOS only) #[arg(long, default_value_t = false)] pub enable_ui_monitoring: bool, @@ -329,11 +324,7 @@ pub enum Command { use_embedding: bool, }, /// Setup screenpipe environment - Setup { - /// Enable beta features - #[arg(long, default_value_t = false)] - enable_beta: bool, - }, + Setup, /// Run database migrations Migrate, /// Generate shell completions diff --git a/screenpipe-vision/Cargo.toml b/screenpipe-vision/Cargo.toml index 67fe9e12ee..365b856e15 100644 --- a/screenpipe-vision/Cargo.toml +++ b/screenpipe-vision/Cargo.toml @@ -53,9 +53,6 @@ reqwest = { workspace = true } [dev-dependencies] tempfile = "3.3.0" criterion = { workspace = true } -assert_cmd = "2.0.14" -predicates = "3.1.0" -assert_fs = "1.1.1" strsim = "0.10.0" memory-stats = "1.2.0" @@ -64,9 +61,6 @@ futures-util = "0.3" tokio-tungstenite = "0.20" serde = "1.0.200" -[build-dependencies] -cc = "1.0" - [package.metadata.osx] framework = ["Vision", "AppKit"]