From b07c94f052bc139ed19d0f12398ab5f47cc44d55 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:13:22 +0530 Subject: [PATCH 01/13] feat: Add pause/resume, switch camera/mic deeplink actions --- .../desktop/src-tauri/src/deeplink_actions.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index dbd90f667f..e996e9f6df 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -26,6 +26,15 @@ pub enum DeepLinkAction { mode: RecordingMode, }, StopRecording, + PauseRecording, + ResumeRecording, + SwitchCamera { + camera: DeviceOrModelID, + }, + SwitchMicrophone { + mic_label: String, + }, + ToggleSystemAudio, OpenEditor { project_path: PathBuf, }, @@ -146,6 +155,23 @@ impl DeepLinkAction { DeepLinkAction::StopRecording => { crate::recording::stop_recording(app.clone(), app.state()).await } + DeepLinkAction::PauseRecording => { + crate::recording::pause_recording(app.clone(), app.state()).await + } + DeepLinkAction::ResumeRecording => { + crate::recording::resume_recording(app.clone(), app.state()).await + } + DeepLinkAction::SwitchCamera { camera } => { + let state = app.state::>(); + crate::set_camera_input(app.clone(), state, Some(camera)).await + } + DeepLinkAction::SwitchMicrophone { mic_label } => { + let state = app.state::>(); + crate::set_mic_input(state, Some(mic_label)).await + } + DeepLinkAction::ToggleSystemAudio => { + crate::recording::toggle_system_audio(app.clone(), app.state()).await + } DeepLinkAction::OpenEditor { project_path } => { crate::open_project_from_path(Path::new(&project_path), app.clone()) } From 53c7186407de701eb5b7cb4dfa8bc3435216aab9 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:14:23 +0530 Subject: [PATCH 02/13] feat: Update deeplink actions with available functions --- apps/desktop/src-tauri/src/deeplink_actions.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index e996e9f6df..8fd8ae57f1 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -28,13 +28,13 @@ pub enum DeepLinkAction { StopRecording, PauseRecording, ResumeRecording, + TogglePauseRecording, SwitchCamera { camera: DeviceOrModelID, }, SwitchMicrophone { mic_label: String, }, - ToggleSystemAudio, OpenEditor { project_path: PathBuf, }, @@ -161,6 +161,9 @@ impl DeepLinkAction { DeepLinkAction::ResumeRecording => { crate::recording::resume_recording(app.clone(), app.state()).await } + DeepLinkAction::TogglePauseRecording => { + crate::recording::toggle_pause_recording(app.clone(), app.state()).await + } DeepLinkAction::SwitchCamera { camera } => { let state = app.state::>(); crate::set_camera_input(app.clone(), state, Some(camera)).await @@ -169,9 +172,6 @@ impl DeepLinkAction { let state = app.state::>(); crate::set_mic_input(state, Some(mic_label)).await } - DeepLinkAction::ToggleSystemAudio => { - crate::recording::toggle_system_audio(app.clone(), app.state()).await - } DeepLinkAction::OpenEditor { project_path } => { crate::open_project_from_path(Path::new(&project_path), app.clone()) } From 892e336261bc25ef23c4863c5a34e6c93f1de4d6 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:15:13 +0530 Subject: [PATCH 03/13] feat: Add Raycast extension package.json --- extensions/raycast/package.json | 77 +++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 extensions/raycast/package.json diff --git a/extensions/raycast/package.json b/extensions/raycast/package.json new file mode 100644 index 0000000000..2831264953 --- /dev/null +++ b/extensions/raycast/package.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://www.raycast.com/schemas/extension.json", + "name": "cap", + "title": "Cap", + "description": "Control Cap screen recording from Raycast", + "icon": "cap-icon.png", + "author": "cap", + "categories": [ + "Productivity", + "Media" + ], + "license": "MIT", + "commands": [ + { + "name": "start-recording", + "title": "Start Recording", + "description": "Start a new Cap recording", + "mode": "view" + }, + { + "name": "stop-recording", + "title": "Stop Recording", + "description": "Stop the current recording", + "mode": "no-view" + }, + { + "name": "pause-recording", + "title": "Pause Recording", + "description": "Pause the current recording", + "mode": "no-view" + }, + { + "name": "resume-recording", + "title": "Resume Recording", + "description": "Resume the paused recording", + "mode": "no-view" + }, + { + "name": "toggle-pause", + "title": "Toggle Pause Recording", + "description": "Toggle pause/resume for the current recording", + "mode": "no-view" + }, + { + "name": "switch-camera", + "title": "Switch Camera", + "description": "Switch to a different camera", + "mode": "view" + }, + { + "name": "switch-microphone", + "title": "Switch Microphone", + "description": "Switch to a different microphone", + "mode": "view" + } + ], + "dependencies": { + "@raycast/api": "^1.48.0" + }, + "devDependencies": { + "@types/node": "18.8.3", + "@types/react": "18.0.9", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^8.0.0", + "eslint-config-prettier": "^8.3.0", + "prettier": "^2.5.1", + "typescript": "^4.4.3" + }, + "scripts": { + "build": "ray build -e dist", + "dev": "ray develop", + "fix-lint": "ray lint --fix", + "lint": "ray lint", + "publish": "npx @raycast/api@latest publish" + } +} From f6a365d94c31cd2a20d74401acdeffeb17133c18 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:15:19 +0530 Subject: [PATCH 04/13] feat: Add Raycast extension TypeScript config --- extensions/raycast/tsconfig.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 extensions/raycast/tsconfig.json diff --git a/extensions/raycast/tsconfig.json b/extensions/raycast/tsconfig.json new file mode 100644 index 0000000000..fc9372fd42 --- /dev/null +++ b/extensions/raycast/tsconfig.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "jsx": "react", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "moduleResolution": "node", + "outDir": "dist" + }, + "include": ["src/**/*"] +} From ed2badf45695109bd4f3b3abbfde1c635de198c4 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:15:28 +0530 Subject: [PATCH 05/13] feat: Add Raycast extension utilities --- extensions/raycast/src/utils.ts | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 extensions/raycast/src/utils.ts diff --git a/extensions/raycast/src/utils.ts b/extensions/raycast/src/utils.ts new file mode 100644 index 0000000000..a2b4e6d79a --- /dev/null +++ b/extensions/raycast/src/utils.ts @@ -0,0 +1,40 @@ +import { open } from "@raycast/api"; + +export type CaptureMode = + | { screen: string } + | { window: string }; + +export type RecordingMode = "studio" | "instant" | "screenshot"; + +export interface StartRecordingOptions { + capture_mode: CaptureMode; + camera?: string; + mic_label?: string; + capture_system_audio: boolean; + mode: RecordingMode; +} + +export interface SwitchCameraOptions { + camera: string; +} + +export interface SwitchMicrophoneOptions { + mic_label: string; +} + +export type DeepLinkAction = + | { start_recording: StartRecordingOptions } + | { stop_recording: {} } + | { pause_recording: {} } + | { resume_recording: {} } + | { toggle_pause_recording: {} } + | { switch_camera: SwitchCameraOptions } + | { switch_microphone: SwitchMicrophoneOptions }; + +export async function executeCapAction(action: DeepLinkAction): Promise { + const actionJson = JSON.stringify(action); + const encodedAction = encodeURIComponent(actionJson); + const deepLink = `cap://action?value=${encodedAction}`; + + await open(deepLink); +} From 9733c40b965cf589b39736ba2404337c964acf21 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:15:39 +0530 Subject: [PATCH 06/13] feat: Add start recording command --- extensions/raycast/src/start-recording.tsx | 90 ++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 extensions/raycast/src/start-recording.tsx diff --git a/extensions/raycast/src/start-recording.tsx b/extensions/raycast/src/start-recording.tsx new file mode 100644 index 0000000000..086d3a62eb --- /dev/null +++ b/extensions/raycast/src/start-recording.tsx @@ -0,0 +1,90 @@ +import { ActionPanel, Action, List, showToast, Toast } from "@raycast/api"; +import { executeCapAction, RecordingMode } from "./utils"; + +export default function Command() { + const captureOptions = [ + { title: "Full Screen", value: "screen" }, + { title: "Window", value: "window" }, + ]; + + const recordingModes: { title: string; value: RecordingMode }[] = [ + { title: "Studio Mode", value: "studio" }, + { title: "Instant Mode", value: "instant" }, + ]; + + async function startRecording( + captureType: "screen" | "window", + mode: RecordingMode, + captureSystemAudio: boolean + ) { + try { + await showToast({ + style: Toast.Style.Animated, + title: "Starting recording...", + }); + + // For simplicity, using default screen/window + // In a real implementation, you'd want to list available screens/windows + const capture_mode = + captureType === "screen" + ? { screen: "Default Screen" } + : { window: "Default Window" }; + + await executeCapAction({ + start_recording: { + capture_mode, + capture_system_audio: captureSystemAudio, + mode, + }, + }); + + await showToast({ + style: Toast.Style.Success, + title: "Recording started", + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to start recording", + message: String(error), + }); + } + } + + return ( + + {captureOptions.map((captureOption) => + recordingModes.map((mode) => ( + + + startRecording( + captureOption.value as "screen" | "window", + mode.value, + false + ) + } + /> + + startRecording( + captureOption.value as "screen" | "window", + mode.value, + true + ) + } + /> + + } + /> + )) + )} + + ); +} From 82607981ecf4fb8b322b17f016dce9fbaf952ac3 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:15:45 +0530 Subject: [PATCH 07/13] feat: Add stop recording command --- extensions/raycast/src/stop-recording.tsx | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 extensions/raycast/src/stop-recording.tsx diff --git a/extensions/raycast/src/stop-recording.tsx b/extensions/raycast/src/stop-recording.tsx new file mode 100644 index 0000000000..95cce2efa0 --- /dev/null +++ b/extensions/raycast/src/stop-recording.tsx @@ -0,0 +1,26 @@ +import { showToast, Toast, closeMainWindow } from "@raycast/api"; +import { executeCapAction } from "./utils"; + +export default async function Command() { + try { + await closeMainWindow(); + + await showToast({ + style: Toast.Style.Animated, + title: "Stopping recording...", + }); + + await executeCapAction({ stop_recording: {} }); + + await showToast({ + style: Toast.Style.Success, + title: "Recording stopped", + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to stop recording", + message: String(error), + }); + } +} From 4181c3fbe7780f5c34f4ca7282990e76b0c81375 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:15:50 +0530 Subject: [PATCH 08/13] feat: Add pause recording command --- extensions/raycast/src/pause-recording.tsx | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 extensions/raycast/src/pause-recording.tsx diff --git a/extensions/raycast/src/pause-recording.tsx b/extensions/raycast/src/pause-recording.tsx new file mode 100644 index 0000000000..cfa31d9b08 --- /dev/null +++ b/extensions/raycast/src/pause-recording.tsx @@ -0,0 +1,26 @@ +import { showToast, Toast, closeMainWindow } from "@raycast/api"; +import { executeCapAction } from "./utils"; + +export default async function Command() { + try { + await closeMainWindow(); + + await showToast({ + style: Toast.Style.Animated, + title: "Pausing recording...", + }); + + await executeCapAction({ pause_recording: {} }); + + await showToast({ + style: Toast.Style.Success, + title: "Recording paused", + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to pause recording", + message: String(error), + }); + } +} From e2dd0a109bec9ea0596d7df065473569b0f6050d Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:15:58 +0530 Subject: [PATCH 09/13] feat: Add resume recording command --- extensions/raycast/src/resume-recording.tsx | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 extensions/raycast/src/resume-recording.tsx diff --git a/extensions/raycast/src/resume-recording.tsx b/extensions/raycast/src/resume-recording.tsx new file mode 100644 index 0000000000..ab56453468 --- /dev/null +++ b/extensions/raycast/src/resume-recording.tsx @@ -0,0 +1,26 @@ +import { showToast, Toast, closeMainWindow } from "@raycast/api"; +import { executeCapAction } from "./utils"; + +export default async function Command() { + try { + await closeMainWindow(); + + await showToast({ + style: Toast.Style.Animated, + title: "Resuming recording...", + }); + + await executeCapAction({ resume_recording: {} }); + + await showToast({ + style: Toast.Style.Success, + title: "Recording resumed", + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to resume recording", + message: String(error), + }); + } +} From caa1bdceae182842e6440f1afc7c2a21ca6587e3 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:16:03 +0530 Subject: [PATCH 10/13] feat: Add toggle pause command --- extensions/raycast/src/toggle-pause.tsx | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 extensions/raycast/src/toggle-pause.tsx diff --git a/extensions/raycast/src/toggle-pause.tsx b/extensions/raycast/src/toggle-pause.tsx new file mode 100644 index 0000000000..f5a4ea1bd0 --- /dev/null +++ b/extensions/raycast/src/toggle-pause.tsx @@ -0,0 +1,26 @@ +import { showToast, Toast, closeMainWindow } from "@raycast/api"; +import { executeCapAction } from "./utils"; + +export default async function Command() { + try { + await closeMainWindow(); + + await showToast({ + style: Toast.Style.Animated, + title: "Toggling pause...", + }); + + await executeCapAction({ toggle_pause_recording: {} }); + + await showToast({ + style: Toast.Style.Success, + title: "Recording pause toggled", + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to toggle pause", + message: String(error), + }); + } +} From c68fc5281ebc3ed79efd08daa0b77a538c759381 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:16:12 +0530 Subject: [PATCH 11/13] feat: Add switch camera command --- extensions/raycast/src/switch-camera.tsx | 56 ++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 extensions/raycast/src/switch-camera.tsx diff --git a/extensions/raycast/src/switch-camera.tsx b/extensions/raycast/src/switch-camera.tsx new file mode 100644 index 0000000000..395e56ae4b --- /dev/null +++ b/extensions/raycast/src/switch-camera.tsx @@ -0,0 +1,56 @@ +import { ActionPanel, Action, List, showToast, Toast } from "@raycast/api"; +import { executeCapAction } from "./utils"; + +export default function Command() { + // In a real implementation, you would fetch available cameras + // For now, we'll use placeholder camera names + const cameras = [ + { id: "default", name: "Default Camera" }, + { id: "facetime", name: "FaceTime HD Camera" }, + ]; + + async function switchCamera(cameraId: string, cameraName: string) { + try { + await showToast({ + style: Toast.Style.Animated, + title: `Switching to ${cameraName}...`, + }); + + await executeCapAction({ + switch_camera: { + camera: cameraId, + }, + }); + + await showToast({ + style: Toast.Style.Success, + title: `Switched to ${cameraName}`, + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to switch camera", + message: String(error), + }); + } + } + + return ( + + {cameras.map((camera) => ( + + switchCamera(camera.id, camera.name)} + /> + + } + /> + ))} + + ); +} From e16033ee6d9325180ae79ca939837ae84849f5c4 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:16:18 +0530 Subject: [PATCH 12/13] feat: Add switch microphone command --- extensions/raycast/src/switch-microphone.tsx | 56 ++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 extensions/raycast/src/switch-microphone.tsx diff --git a/extensions/raycast/src/switch-microphone.tsx b/extensions/raycast/src/switch-microphone.tsx new file mode 100644 index 0000000000..38a61e2b2d --- /dev/null +++ b/extensions/raycast/src/switch-microphone.tsx @@ -0,0 +1,56 @@ +import { ActionPanel, Action, List, showToast, Toast } from "@raycast/api"; +import { executeCapAction } from "./utils"; + +export default function Command() { + // In a real implementation, you would fetch available microphones + // For now, we'll use placeholder microphone names + const microphones = [ + { label: "default", name: "Default Microphone" }, + { label: "built-in", name: "Built-in Microphone" }, + ]; + + async function switchMicrophone(micLabel: string, micName: string) { + try { + await showToast({ + style: Toast.Style.Animated, + title: `Switching to ${micName}...`, + }); + + await executeCapAction({ + switch_microphone: { + mic_label: micLabel, + }, + }); + + await showToast({ + style: Toast.Style.Success, + title: `Switched to ${micName}`, + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to switch microphone", + message: String(error), + }); + } + } + + return ( + + {microphones.map((mic) => ( + + switchMicrophone(mic.label, mic.name)} + /> + + } + /> + ))} + + ); +} From 0f759387134a14b0787d3ea5ff03205534e649d3 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:16:30 +0530 Subject: [PATCH 13/13] feat: Add Raycast extension README --- extensions/raycast/README.md | 72 ++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 extensions/raycast/README.md diff --git a/extensions/raycast/README.md b/extensions/raycast/README.md new file mode 100644 index 0000000000..cfbcf6268e --- /dev/null +++ b/extensions/raycast/README.md @@ -0,0 +1,72 @@ +# Cap Raycast Extension + +Control Cap screen recording directly from Raycast. + +## Features + +- **Start Recording**: Choose between screen or window capture with Studio or Instant mode +- **Stop Recording**: Quickly stop your current recording +- **Pause Recording**: Pause the recording without stopping +- **Resume Recording**: Resume a paused recording +- **Toggle Pause**: Toggle between pause and resume states +- **Switch Camera**: Change the camera being used during recording +- **Switch Microphone**: Change the microphone being used during recording + +## Installation + +### From Source + +1. Clone the Cap repository +2. Navigate to the extension directory: + ```bash + cd extensions/raycast + ``` +3. Install dependencies: + ```bash + npm install + ``` +4. Build and import to Raycast: + ```bash + npm run dev + ``` + +## Usage + +Once installed, you can access all Cap commands from Raycast: + +- Type "Cap" in Raycast to see all available commands +- Use keyboard shortcuts to quickly control your recordings +- Commands execute instantly without opening the Cap UI + +## Requirements + +- Cap desktop application must be installed and running +- macOS (Cap is currently macOS-only) +- Raycast + +## Deep Link Protocol + +This extension uses Cap's deep link protocol (`cap://action`) to communicate with the desktop application. All commands are executed via URL schemes that trigger the corresponding actions in Cap. + +## Development + +```bash +# Install dependencies +npm install + +# Run in development mode +npm run dev + +# Build for production +npm run build + +# Lint code +npm run lint + +# Fix linting issues +npm run fix-lint +``` + +## License + +MIT