From d717d2fd2518449ce787174152f39f5324616da9 Mon Sep 17 00:00:00 2001 From: Tate Lyman Date: Thu, 26 Mar 2026 21:54:04 -0500 Subject: [PATCH 1/2] feat: extend deeplinks with pause/resume + add Raycast extension - Add PauseRecording, ResumeRecording, TogglePauseRecording deeplink actions - Create Raycast extension with start/stop/toggle-pause/settings commands - Extension uses cap:// deeplink protocol to control recording --- .../desktop/src-tauri/src/deeplink_actions.rs | 12 ++++++++++ extensions/raycast/package.json | 23 +++++++++++++++++++ extensions/raycast/src/open-settings.ts | 7 ++++++ extensions/raycast/src/start-recording.ts | 15 ++++++++++++ extensions/raycast/src/stop-recording.ts | 7 ++++++ extensions/raycast/src/toggle-pause.ts | 7 ++++++ extensions/raycast/src/utils.ts | 7 ++++++ 7 files changed, 78 insertions(+) create mode 100644 extensions/raycast/package.json create mode 100644 extensions/raycast/src/open-settings.ts create mode 100644 extensions/raycast/src/start-recording.ts create mode 100644 extensions/raycast/src/stop-recording.ts create mode 100644 extensions/raycast/src/toggle-pause.ts create mode 100644 extensions/raycast/src/utils.ts diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index a117028487..f03cb9be36 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -26,6 +26,9 @@ pub enum DeepLinkAction { mode: RecordingMode, }, StopRecording, + PauseRecording, + ResumeRecording, + TogglePauseRecording, OpenEditor { project_path: PathBuf, }, @@ -147,6 +150,15 @@ 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::TogglePauseRecording => { + crate::recording::toggle_pause_recording(app.clone(), app.state()).await + } DeepLinkAction::OpenEditor { project_path } => { crate::open_project_from_path(Path::new(&project_path), app.clone()) } diff --git a/extensions/raycast/package.json b/extensions/raycast/package.json new file mode 100644 index 0000000000..e5cea46ca2 --- /dev/null +++ b/extensions/raycast/package.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://www.raycast.com/schemas/extension.json", + "name": "cap", + "title": "Cap", + "description": "Control Cap screen recording from Raycast", + "icon": "command-icon.png", + "author": "TateLyman", + "categories": ["Productivity"], + "license": "MIT", + "commands": [ + { "name": "start-recording", "title": "Start Recording", "description": "Start a new screen recording", "mode": "no-view" }, + { "name": "stop-recording", "title": "Stop Recording", "description": "Stop the current recording", "mode": "no-view" }, + { "name": "toggle-pause", "title": "Toggle Pause", "description": "Pause or resume the current recording", "mode": "no-view" }, + { "name": "open-settings", "title": "Open Settings", "description": "Open Cap settings", "mode": "no-view" } + ], + "dependencies": { + "@raycast/api": "^1.50.0" + }, + "devDependencies": { + "@raycast/eslint-config": "^1.0.6", + "typescript": "^5.0.0" + } +} diff --git a/extensions/raycast/src/open-settings.ts b/extensions/raycast/src/open-settings.ts new file mode 100644 index 0000000000..891e0a0122 --- /dev/null +++ b/extensions/raycast/src/open-settings.ts @@ -0,0 +1,7 @@ +import { showHUD } from "@raycast/api"; +import { sendDeepLink } from "./utils"; + +export default async function Command() { + await sendDeepLink({ open_settings: { page: null } }); + await showHUD("Opening Cap settings"); +} diff --git a/extensions/raycast/src/start-recording.ts b/extensions/raycast/src/start-recording.ts new file mode 100644 index 0000000000..b65950518d --- /dev/null +++ b/extensions/raycast/src/start-recording.ts @@ -0,0 +1,15 @@ +import { showHUD } from "@raycast/api"; +import { sendDeepLink } from "./utils"; + +export default async function Command() { + await sendDeepLink({ + start_recording: { + capture_mode: { screen: "Main Display" }, + camera: null, + mic_label: null, + capture_system_audio: false, + mode: "studio", + }, + }); + await showHUD("Recording started"); +} diff --git a/extensions/raycast/src/stop-recording.ts b/extensions/raycast/src/stop-recording.ts new file mode 100644 index 0000000000..00130e4cb3 --- /dev/null +++ b/extensions/raycast/src/stop-recording.ts @@ -0,0 +1,7 @@ +import { showHUD } from "@raycast/api"; +import { sendDeepLink } from "./utils"; + +export default async function Command() { + await sendDeepLink("stop_recording"); + await showHUD("Recording stopped"); +} diff --git a/extensions/raycast/src/toggle-pause.ts b/extensions/raycast/src/toggle-pause.ts new file mode 100644 index 0000000000..fe7be5273a --- /dev/null +++ b/extensions/raycast/src/toggle-pause.ts @@ -0,0 +1,7 @@ +import { showHUD } from "@raycast/api"; +import { sendDeepLink } from "./utils"; + +export default async function Command() { + await sendDeepLink("toggle_pause_recording"); + await showHUD("Recording pause toggled"); +} diff --git a/extensions/raycast/src/utils.ts b/extensions/raycast/src/utils.ts new file mode 100644 index 0000000000..ef39caa89c --- /dev/null +++ b/extensions/raycast/src/utils.ts @@ -0,0 +1,7 @@ +import { open } from "@raycast/api"; + +export async function sendDeepLink(action: object) { + const value = encodeURIComponent(JSON.stringify(action)); + const url = `cap://action?value=${value}`; + await open(url); +} From 0d6254cffbf7db4c4ec2d780bb9b04be9b61346c Mon Sep 17 00:00:00 2001 From: Tate Lyman Date: Thu, 26 Mar 2026 21:59:37 -0500 Subject: [PATCH 2/2] fix: address review feedback - type safety, error handling, tsconfig --- extensions/raycast/assets/command-icon.png | 1 + extensions/raycast/src/start-recording.ts | 4 ++-- extensions/raycast/src/stop-recording.ts | 2 +- extensions/raycast/src/toggle-pause.ts | 2 +- extensions/raycast/src/utils.ts | 14 +++++++++++--- extensions/raycast/tsconfig.json | 15 +++++++++++++++ 6 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 extensions/raycast/assets/command-icon.png create mode 100644 extensions/raycast/tsconfig.json diff --git a/extensions/raycast/assets/command-icon.png b/extensions/raycast/assets/command-icon.png new file mode 100644 index 0000000000..48cdce8528 --- /dev/null +++ b/extensions/raycast/assets/command-icon.png @@ -0,0 +1 @@ +placeholder diff --git a/extensions/raycast/src/start-recording.ts b/extensions/raycast/src/start-recording.ts index b65950518d..c0c3ded017 100644 --- a/extensions/raycast/src/start-recording.ts +++ b/extensions/raycast/src/start-recording.ts @@ -1,10 +1,10 @@ -import { showHUD } from "@raycast/api"; +import { showHUD, getPreferenceValues } from "@raycast/api"; import { sendDeepLink } from "./utils"; export default async function Command() { await sendDeepLink({ start_recording: { - capture_mode: { screen: "Main Display" }, + capture_mode: { screen: "" }, camera: null, mic_label: null, capture_system_audio: false, diff --git a/extensions/raycast/src/stop-recording.ts b/extensions/raycast/src/stop-recording.ts index 00130e4cb3..d38dffa034 100644 --- a/extensions/raycast/src/stop-recording.ts +++ b/extensions/raycast/src/stop-recording.ts @@ -2,6 +2,6 @@ import { showHUD } from "@raycast/api"; import { sendDeepLink } from "./utils"; export default async function Command() { - await sendDeepLink("stop_recording"); + await sendDeepLink({ stop_recording: {} }); await showHUD("Recording stopped"); } diff --git a/extensions/raycast/src/toggle-pause.ts b/extensions/raycast/src/toggle-pause.ts index fe7be5273a..d8545a2483 100644 --- a/extensions/raycast/src/toggle-pause.ts +++ b/extensions/raycast/src/toggle-pause.ts @@ -2,6 +2,6 @@ import { showHUD } from "@raycast/api"; import { sendDeepLink } from "./utils"; export default async function Command() { - await sendDeepLink("toggle_pause_recording"); + await sendDeepLink({ toggle_pause_recording: {} }); await showHUD("Recording pause toggled"); } diff --git a/extensions/raycast/src/utils.ts b/extensions/raycast/src/utils.ts index ef39caa89c..7410325902 100644 --- a/extensions/raycast/src/utils.ts +++ b/extensions/raycast/src/utils.ts @@ -1,7 +1,15 @@ -import { open } from "@raycast/api"; +import { open, showToast, Toast } from "@raycast/api"; -export async function sendDeepLink(action: object) { +export async function sendDeepLink(action: Record) { const value = encodeURIComponent(JSON.stringify(action)); const url = `cap://action?value=${value}`; - await open(url); + try { + await open(url); + } catch { + await showToast({ + style: Toast.Style.Failure, + title: "Cap Not Running", + message: "Please open Cap and try again", + }); + } } diff --git a/extensions/raycast/tsconfig.json b/extensions/raycast/tsconfig.json new file mode 100644 index 0000000000..74e9e83625 --- /dev/null +++ b/extensions/raycast/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "dist", + "rootDir": "src", + "jsx": "react-jsx", + "lib": ["ES2020"] + }, + "include": ["src/**/*"] +}