Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions apps/desktop/src-tauri/src/deeplink_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ pub enum DeepLinkAction {
mode: RecordingMode,
},
StopRecording,
PauseRecording,
ResumeRecording,
TogglePauseRecording,
RestartRecording,
SetMicrophone {
label: Option<String>,
},
SetCamera {
id: Option<DeviceOrModelID>,
},
OpenEditor {
project_path: PathBuf,
},
Expand Down Expand Up @@ -146,6 +156,28 @@ 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::RestartRecording => {
crate::recording::restart_recording(app.clone(), app.state())
.await
.map(|_| ())
}
DeepLinkAction::SetMicrophone { label } => {
let state = app.state::<ArcLock<App>>();
crate::set_mic_input(state, label).await
}
DeepLinkAction::SetCamera { id } => {
let state = app.state::<ArcLock<App>>();
crate::set_camera_input(app.clone(), state, id, None).await
}
Comment on lines +173 to +180
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_mic_input and set_camera_input are not pub in lib.rs, but are called here with crate:: prefix. This will fail compilation.

Suggested change
DeepLinkAction::SetMicrophone { label } => {
let state = app.state::<ArcLock<App>>();
crate::set_mic_input(state, label).await
}
DeepLinkAction::SetCamera { id } => {
let state = app.state::<ArcLock<App>>();
crate::set_camera_input(app.clone(), state, id, None).await
}
DeepLinkAction::SetMicrophone { label } => {
let state = app.state::<ArcLock<App>>();
crate::commands::set_mic_input(state, label).await
}
DeepLinkAction::SetCamera { id } => {
let state = app.state::<ArcLock<App>>();
crate::commands::set_camera_input(app.clone(), state, id, None).await
}

Or make set_mic_input and set_camera_input public in lib.rs by adding pub before async fn.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/deeplink_actions.rs
Line: 173-180

Comment:
`set_mic_input` and `set_camera_input` are not `pub` in `lib.rs`, but are called here with `crate::` prefix. This will fail compilation.

```suggestion
            DeepLinkAction::SetMicrophone { label } => {
                let state = app.state::<ArcLock<App>>();
                crate::commands::set_mic_input(state, label).await
            }
            DeepLinkAction::SetCamera { id } => {
                let state = app.state::<ArcLock<App>>();
                crate::commands::set_camera_input(app.clone(), state, id, None).await
            }
```

Or make `set_mic_input` and `set_camera_input` public in `lib.rs` by adding `pub` before `async fn`.

How can I resolve this? If you propose a fix, please make it concise.

DeepLinkAction::OpenEditor { project_path } => {
crate::open_project_from_path(Path::new(&project_path), app.clone())
}
Expand Down
176 changes: 176 additions & 0 deletions extensions/raycast/DEEPLINKS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Cap Deeplinks

Cap supports deeplinks for controlling recordings and other app functionality. This enables integration with tools like Raycast, Alfred, Shortcuts, and custom scripts.

## URL Scheme

Cap uses the `cap-desktop://` URL scheme on macOS and Windows.

## Action Format

Actions are sent as JSON in the `value` query parameter:

```
cap-desktop://action?value=<URL-encoded JSON>
```

## Available Actions

### Recording Controls

#### Stop Recording
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the desktop enum keeps StopRecording/PauseRecording/etc as unit variants, these examples should be JSON strings (not { "stop_recording": {} }). Also the URL examples should encode the quotes.

Suggested change
#### Stop Recording
#### Stop Recording
```json
"stop_recording"

Pause Recording

"pause_recording"

Resume Recording

"resume_recording"

Toggle Pause/Resume

"toggle_pause_recording"

Restart Recording

Stops and immediately restarts with the same settings:

"restart_recording"

```json
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same note about suggestion formatting: avoiding backtick fences makes this apply cleanly.

Suggested change
```json
#### Stop Recording
~~~json
"stop_recording"
~~~
#### Pause Recording
~~~json
"pause_recording"
~~~
#### Resume Recording
~~~json
"resume_recording"
~~~
#### Toggle Pause/Resume
~~~json
"toggle_pause_recording"
~~~
#### Restart Recording
Stops and immediately restarts with the same settings:
~~~json
"restart_recording"
~~~

{"stop_recording":{}}
```

#### Pause Recording
```json
{"pause_recording":{}}
```

#### Resume Recording
```json
{"resume_recording":{}}
```

#### Toggle Pause/Resume
```json
{"toggle_pause_recording":{}}
```

#### Restart Recording
Stops and immediately restarts with the same settings:
```json
{"restart_recording":{}}
```

#### Start Recording
```json
{
"start_recording": {
"capture_mode": {"screen": "Main Display"},
"camera": null,
"mic_label": null,
"capture_system_audio": false,
"mode": "instant"
}
}
```

**capture_mode options:**
- `{"screen": "Display Name"}` - Record a specific display
- `{"window": "Window Name"}` - Record a specific window

**mode options:**
- `"instant"` - Quick recording with immediate upload
- `"studio"` - Full editing capabilities

### Input Controls

#### Set Microphone
```json
{"set_microphone": {"label": "MacBook Pro Microphone"}}
```

Set to `null` to disable:
```json
{"set_microphone": {"label": null}}
```

#### Set Camera
```json
{"set_camera": {"id": "camera-device-id"}}
```

Set to `null` to disable:
```json
{"set_camera": {"id": null}}
```

### App Controls

#### Open Settings
```json
{"open_settings": {"page": null}}
```

Open a specific settings page:
```json
{"open_settings": {"page": "recordings"}}
```

#### Open Editor
```json
{"open_editor": {"project_path": "/path/to/project.cap"}}
```

## Examples

### Shell Script (macOS)

```bash
#!/bin/bash

# Stop recording
open "cap-desktop://action?value=%7B%22stop_recording%22%3A%7B%7D%7D"

# Toggle pause
open "cap-desktop://action?value=%7B%22toggle_pause_recording%22%3A%7B%7D%7D"
```

### AppleScript

```applescript
tell application "System Events"
open location "cap-desktop://action?value=%7B%22stop_recording%22%3A%7B%7D%7D"
end tell
```

### JavaScript/Node.js

```javascript
const { exec } = require('child_process');

function capAction(action) {
const json = JSON.stringify(action);
const encoded = encodeURIComponent(json);
const url = `cap-desktop://action?value=${encoded}`;
exec(`open "${url}"`);
}

// Stop recording
capAction({ stop_recording: {} });

// Toggle pause
capAction({ toggle_pause_recording: {} });
```

### Python

```python
import subprocess
import json
import urllib.parse

def cap_action(action):
json_str = json.dumps(action)
encoded = urllib.parse.quote(json_str)
url = f"cap-desktop://action?value={encoded}"
subprocess.run(["open", url])

# Stop recording
cap_action({"stop_recording": {}})

# Toggle pause
cap_action({"toggle_pause_recording": {}})
```

## Raycast Extension

A full Raycast extension is included in `extensions/raycast/`. See its README for installation instructions.

## Troubleshooting

1. **Cap must be running** - Deeplinks only work when Cap is open
2. **URL encoding** - Make sure the JSON is properly URL-encoded
3. **Permissions** - Some actions require an active recording session
50 changes: 50 additions & 0 deletions extensions/raycast/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Cap Raycast Extension

Control [Cap](https://cap.so) screen recording from Raycast.

## Features

- **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 on current recording
- **Restart Recording** - Restart the current recording
- **Open Settings** - Open Cap settings

## Requirements

- [Cap](https://cap.so) must be installed and running
- macOS only (Cap deeplinks use the `cap-desktop://` scheme)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DEEPLINKS.md states that Cap deeplinks work on both macOS and Windows, but this README says "macOS only". Update to clarify platform support or remove if Windows is supported.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/raycast/README.md
Line: 18

Comment:
DEEPLINKS.md states that Cap deeplinks work on both macOS and Windows, but this README says "macOS only". Update to clarify platform support or remove if Windows is supported.

How can I resolve this? If you propose a fix, please make it concise.


## How It Works

This extension uses Cap's deeplink API to control recordings. Each command sends a URL like:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs here show raw JSON embedded in the URL (and as an object). In practice value needs to be URL-encoded JSON, and unit actions likely need to be a JSON string to match the Rust enum.

Suggested change
This extension uses Cap's deeplink API to control recordings. Each command sends a URL like:
This extension uses Cap's deeplink API to control recordings. Each command sends a URL like (the `value` param must be URL-encoded JSON):

cap-desktop://action?value=%22stop_recording%22


```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting nit: GitHub suggestions don’t handle nested triple-backtick fences well. Here’s the same change without fenced-code nesting.

Suggested change
```
This extension uses Cap's deeplink API to control recordings. Each command sends a URL like (the `value` param must be URL-encoded JSON):
cap-desktop://action?value=%22stop_recording%22

cap-desktop://action?value={"stop_recording":{}}
```

## Installation

1. Clone this repository
2. Run `npm install` in the `extensions/raycast` directory
3. Run `npm run dev` to start development
4. Or `npm run build` to build for production

## Available Deeplinks

| Action | Deeplink |
|--------|----------|
| Stop Recording | `cap-desktop://action?value={"stop_recording":{}}` |
| Pause Recording | `cap-desktop://action?value={"pause_recording":{}}` |
| Resume Recording | `cap-desktop://action?value={"resume_recording":{}}` |
| Toggle Pause | `cap-desktop://action?value={"toggle_pause_recording":{}}` |
| Restart Recording | `cap-desktop://action?value={"restart_recording":{}}` |
| Set Microphone | `cap-desktop://action?value={"set_microphone":{"label":"Microphone Name"}}` |
| Set Camera | `cap-desktop://action?value={"set_camera":{"id":"camera-id"}}` |
| Open Settings | `cap-desktop://action?value={"open_settings":{"page":null}}` |

## License

MIT
Binary file added extensions/raycast/assets/cap-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
80 changes: 80 additions & 0 deletions extensions/raycast/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"$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",
"subtitle": "Cap",
"description": "Start a new screen recording",
"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": "open-settings",
"title": "Open Settings",
"subtitle": "Cap",
"description": "Open Cap settings",
"mode": "no-view"
}
],
"dependencies": {
"@raycast/api": "^1.93.1"
},
"devDependencies": {
"@raycast/eslint-config": "^1.0.11",
"@types/node": "22.13.5",
"@types/react": "19.0.10",
"eslint": "^9.21.0",
"prettier": "^3.5.2",
"typescript": "^5.7.3"
},
"scripts": {
"build": "ray build",
"dev": "ray develop",
"fix-lint": "ray lint --fix",
"lint": "ray lint",
"prepublishOnly": "echo \"\\n\\nIt seems like you are trying to publish the Raycast extension to npm.\\n\\nIf you did intend to publish it to npm, remove the \\`prepublishOnly\\` script and rerun \\`npm publish\\` again.\\nIf you wanted to publish it to the Raycast Store instead, use \\`npm run publish\\` instead.\\n\\n\" && exit 1",
"publish": "npx @raycast/api@latest publish"
}
}
5 changes: 5 additions & 0 deletions extensions/raycast/src/open-settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { openSettings } from "./utils/deeplink";

export default async function Command() {
await openSettings();
}
5 changes: 5 additions & 0 deletions extensions/raycast/src/pause-recording.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { pauseRecording } from "./utils/deeplink";

export default async function Command() {
await pauseRecording();
}
5 changes: 5 additions & 0 deletions extensions/raycast/src/restart-recording.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { restartRecording } from "./utils/deeplink";

export default async function Command() {
await restartRecording();
}
5 changes: 5 additions & 0 deletions extensions/raycast/src/resume-recording.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { resumeRecording } from "./utils/deeplink";

export default async function Command() {
await resumeRecording();
}
Loading