-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat: extend deeplinks for recording control + Raycast extension #1683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Cap for Raycast | ||
|
|
||
| Control [Cap](https://cap.so) screen recorder directly from Raycast. | ||
|
|
||
| ## Commands | ||
|
|
||
| | Command | Description | | ||
| | --- | --- | | ||
| | Start Recording | Start a new screen recording | | ||
| | Stop Recording | Stop the current recording | | ||
| | Pause Recording | Pause the current recording | | ||
| | Resume Recording | Resume a paused recording | | ||
| | Toggle Pause | Toggle pause/resume | | ||
| | Restart Recording | Restart the current recording | | ||
| | Take Screenshot | Take a screenshot | | ||
| | Open Settings | Open Cap settings | | ||
|
|
||
| ## How It Works | ||
|
|
||
| This extension uses Cap's deeplink protocol (`cap-desktop://action?value=<json>`) to communicate with the desktop app. Make sure Cap is running before using the commands. | ||
|
|
||
| ## Deeplink Protocol | ||
|
|
||
| Cap supports the following deeplink actions: | ||
|
|
||
| ``` | ||
| cap-desktop://action?value="stop_recording" | ||
| cap-desktop://action?value="pause_recording" | ||
| cap-desktop://action?value="resume_recording" | ||
| cap-desktop://action?value="toggle_pause_recording" | ||
| cap-desktop://action?value="restart_recording" | ||
| cap-desktop://action?value={"start_recording":{"capture_mode":{"screen":"Main Display"},"camera":null,"mic_label":null,"capture_system_audio":false,"mode":"studio"}} | ||
| cap-desktop://action?value={"take_screenshot":{"capture_mode":{"screen":"Main Display"}}} | ||
| cap-desktop://action?value={"open_editor":{"project_path":"/path/to/project"}} | ||
| cap-desktop://action?value={"open_settings":{"page":null}} | ||
| ``` | ||
|
|
||
| ## Development | ||
|
|
||
| ```bash | ||
| cd extensions/raycast | ||
| npm install | ||
| npm run dev | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| { | ||
| "$schema": "https://www.raycast.com/schemas/extension.json", | ||
| "name": "cap", | ||
| "title": "Cap", | ||
| "description": "Control Cap screen recorder — start/stop recording, pause, resume, take screenshots, and more.", | ||
| "icon": "icon.png", | ||
| "author": "cap", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You need to add a Prompt To Fix With AIThis is a comment left during a code review.
Path: extensions/raycast/package.json
Line: 7
Comment:
**Missing icon file**
`package.json` declares `"icon": "command-icon.png"` but no such file exists anywhere in the `extensions/raycast/` directory (confirmed by inspecting the full commit tree). Raycast validates the icon at build time, so `ray build` and `ray develop` will fail with a missing-asset error until this PNG is added to the extension root.
You need to add a `command-icon.png` (typically 512×512 px) to `extensions/raycast/` before this extension can be built or published.
How can I resolve this? If you propose a fix, please make it concise. |
||
| "categories": ["Productivity", "Applications"], | ||
| "license": "AGPL-3.0", | ||
| "commands": [ | ||
| { | ||
| "name": "start-recording", | ||
| "title": "Start Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Start a new screen recording with Cap", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "stop-recording", | ||
| "title": "Stop Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Stop the current recording", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "pause-recording", | ||
| "title": "Pause Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Pause the current recording", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "resume-recording", | ||
| "title": "Resume Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Resume a paused recording", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "toggle-pause", | ||
| "title": "Toggle Pause", | ||
| "subtitle": "Cap", | ||
| "description": "Toggle pause/resume on the current recording", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "restart-recording", | ||
| "title": "Restart Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Restart the current recording", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "take-screenshot", | ||
| "title": "Take Screenshot", | ||
| "subtitle": "Cap", | ||
| "description": "Take a screenshot with Cap", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "open-settings", | ||
| "title": "Open Settings", | ||
| "subtitle": "Cap", | ||
| "description": "Open Cap settings", | ||
| "mode": "no-view" | ||
| } | ||
| ], | ||
| "dependencies": { | ||
| "@raycast/api": "^1.93.2", | ||
| "@raycast/utils": "^1.19.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@raycast/eslint-config": "^1.0.11", | ||
| "@types/node": "22.13.14", | ||
| "@types/react": "19.0.12", | ||
| "eslint": "^9.23.0", | ||
| "prettier": "^3.5.3", | ||
| "typescript": "^5.8.2" | ||
| }, | ||
| "scripts": { | ||
| "build": "ray build", | ||
| "dev": "ray develop", | ||
| "fix-lint": "ray lint --fix", | ||
| "lint": "ray lint", | ||
| "prepublishOnly": "echo \"Error: no publish\" && exit 1", | ||
| "publish": "npx @raycast/api@latest publish" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { triggerDeepLink } from "./utils"; | ||
|
|
||
| export default async function Command() { | ||
| await triggerDeepLink( | ||
| { open_settings: { page: null } }, | ||
| "⚙️ Opening Cap settings…", | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import { triggerDeepLink } from "./utils"; | ||
|
|
||
| export default async function Command() { | ||
| await triggerDeepLink("pause_recording", "⏸ Pausing Cap recording…"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import { triggerDeepLink } from "./utils"; | ||
|
|
||
| export default async function Command() { | ||
| await triggerDeepLink("restart_recording", "🔄 Restarting Cap recording…"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import { triggerDeepLink } from "./utils"; | ||
|
|
||
| export default async function Command() { | ||
| await triggerDeepLink("resume_recording", "▶️ Resuming Cap recording…"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { triggerDeepLink } from "./utils"; | ||
|
|
||
| export default async function Command() { | ||
| await triggerDeepLink( | ||
| { | ||
| start_recording: { | ||
| capture_mode: null, | ||
| camera: null, | ||
| mic_label: null, | ||
| capture_system_audio: false, | ||
| mode: "studio", | ||
| }, | ||
| }, | ||
| "📹 Starting Cap recording…", | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import { triggerDeepLink } from "./utils"; | ||
|
|
||
| export default async function Command() { | ||
| await triggerDeepLink("stop_recording", "⏹ Stopping Cap recording…"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { triggerDeepLink } from "./utils"; | ||
|
|
||
| export default async function Command() { | ||
| await triggerDeepLink( | ||
| { | ||
| take_screenshot: { | ||
| capture_mode: null, | ||
| }, | ||
| }, | ||
| "📸 Taking screenshot with Cap…", | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import { triggerDeepLink } from "./utils"; | ||
|
|
||
| export default async function Command() { | ||
| await triggerDeepLink("toggle_pause_recording", "⏯ Toggling Cap recording pause…"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { open, showHUD } from "@raycast/api"; | ||
|
|
||
| const SCHEME = "cap-desktop"; | ||
|
|
||
| type DeepLinkAction = string | Record<string, unknown>; | ||
|
|
||
| /** | ||
| * Build a Cap deeplink URL. | ||
| * | ||
| * Actions are sent as: cap-desktop://action?value=<json> | ||
| * | ||
| * Unit variants (e.g. StopRecording) serialize as a plain string: "stop_recording" | ||
| * Struct variants serialize as: {"start_recording": {...}} | ||
| */ | ||
| export function buildDeepLink(action: DeepLinkAction): string { | ||
| const json = JSON.stringify(action); | ||
| return `${SCHEME}://action?value=${encodeURIComponent(json)}`; | ||
| } | ||
|
|
||
| /** | ||
| * Open a Cap deeplink and show a HUD message. | ||
| * Shows an error HUD if Cap is not running or the deeplink fails. | ||
| */ | ||
| export async function triggerDeepLink( | ||
| action: DeepLinkAction, | ||
| hudMessage: string, | ||
| ): Promise<void> { | ||
| const url = buildDeepLink(action); | ||
| try { | ||
| await open(url); | ||
| await showHUD(hudMessage); | ||
| } catch { | ||
| await showHUD("❌ Failed — is Cap running?"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { | ||
| "$schema": "https://json.schemastore.org/tsconfig", | ||
| "display": "Node 22", | ||
| "compilerOptions": { | ||
| "lib": ["ES2023"], | ||
| "module": "Node16", | ||
| "target": "ES2022", | ||
| "strict": true, | ||
| "esModuleInterop": true, | ||
| "skipLibCheck": true, | ||
| "moduleResolution": "Node16", | ||
| "resolveJsonModule": true, | ||
| "isolatedModules": true, | ||
| "jsx": "react-jsx" | ||
| }, | ||
| "include": ["src/**/*"] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
CaptureMode→ScreenCaptureTargetmapping (lines 172–183) is an exact copy of the same block already present in theStartRecordingarm (lines 130–141). If the display/window lookup logic ever changes (error messages, fallback behaviour, etc.) both copies would need to be updated.Consider extracting it into a small helper function:
Then both arms become
let capture_target = resolve_capture_target(capture_mode)?;.Prompt To Fix With AI
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!