Skip to content

feat: Raycast Deeplink Extension (#1540)#1727

Open
Ojas2095 wants to merge 1 commit intoCapSoftware:mainfrom
Ojas2095:feat/issue-1540-raycast-deeplinks
Open

feat: Raycast Deeplink Extension (#1540)#1727
Ojas2095 wants to merge 1 commit intoCapSoftware:mainfrom
Ojas2095:feat/issue-1540-raycast-deeplinks

Conversation

@Ojas2095
Copy link
Copy Markdown

@Ojas2095 Ojas2095 commented Apr 10, 2026

Fixes #1540. Adds support for Pause, Resume, Switch Microphone and Camera deeplink commands. Includes official Raycast extension package inside apps/raycast mapped to these endpoints.

Greptile Summary

This PR adds a Raycast extension (apps/raycast) for controlling Cap recordings via deeplinks, and extends the desktop's deeplink_actions.rs with four new action variants: PauseRecording, ResumeRecording, SwitchMicrophone, and SwitchCamera. The Rust side is implemented correctly, but two TypeScript commands have bugs that will cause silent failures at runtime.

  • switch-camera.ts: Sends the camera ID as a bare string, but Rust's DeviceOrModelID enum requires the tagged-object format {\"DeviceID\": \"...\"} — any non-\"none\" input will always fail to deserialize.
  • start.ts: Hardcodes \"Built-in Retina Display\" which only matches a specific subset of Apple displays; this will fail on most machines.

Confidence Score: 4/5

Not safe to merge as-is — two Raycast commands will silently fail at runtime due to incorrect JSON serialization and a hardcoded display name.

Two P1 findings: switch_camera sends the wrong JSON format for DeviceOrModelID, and start_recording hardcodes a display name that won't resolve on most machines. The Rust desktop changes are clean and correct. Fixing the two TypeScript issues is required before this is usable.

apps/raycast/src/switch-camera.ts and apps/raycast/src/start.ts require fixes before merge.

Important Files Changed

Filename Overview
apps/desktop/src-tauri/src/deeplink_actions.rs Adds PauseRecording, ResumeRecording, SwitchMicrophone, and SwitchCamera variants to the DeepLinkAction enum and wires them to existing recording commands; Rust side looks correct.
apps/raycast/src/switch-camera.ts Sends camera ID as a bare string, but Rust's DeviceOrModelID enum requires {"DeviceID": "..."} format; switch_camera will always fail for non-null values.
apps/raycast/src/start.ts Hardcodes "Built-in Retina Display" as the capture target; this exact name only exists on some Apple Macs and will fail on most machines.
apps/raycast/src/utils.ts Correctly builds the JSON payload matching Rust's serde enum format; contains prohibited code comments and the success HUD message is misleading.
apps/raycast/src/switch-mic.ts Correctly serializes mic_label as a string or null, matching the Rust Option expectation.
apps/raycast/src/pause.ts Simple unit-variant deeplink for pause_recording; correct.
apps/raycast/src/resume.ts Simple unit-variant deeplink for resume_recording; correct.
apps/raycast/src/stop.ts Simple unit-variant deeplink for stop_recording; correct.
apps/raycast/package.json Valid Raycast extension manifest; registers all six commands with correct modes and argument definitions.
apps/raycast/tsconfig.json Standard Raycast extension TypeScript config; no issues.

Sequence Diagram

sequenceDiagram
    participant R as Raycast Command
    participant U as utils.sendCapCommand
    participant OS as macOS URL Handler
    participant T as Tauri deeplink_actions
    participant RC as Recording Core

    R->>U: sendCapCommand("pause_recording")
    U->>U: Build valuePayload = '"pause_recording"'
    U->>OS: open("cap://action?value=...")
    OS->>T: handle(urls)
    T->>T: DeepLinkAction::try_from(url) → serde_json::from_str
    T->>RC: pause_recording(app, state)
    RC-->>T: Ok(())
    T-->>OS: done
    U->>R: showHUD("Cap: Action Executed")

    Note over R,U: switch-camera path (broken)
    R->>U: sendCapCommand("switch_camera", {camera: "raw_string"})
    U->>OS: open("cap://action?value=...")
    OS->>T: handle(urls)
    T->>T: serde_json::from_str → ParseFailed (expects {"DeviceID":"..."})
    T-->>OS: eprintln error, silent failure
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/raycast/src/switch-camera.ts
Line: 10

Comment:
**Wrong serialization format for `DeviceOrModelID`**

A plain string will always fail to deserialize on the Rust side. `DeviceOrModelID` is a Rust enum with two tuple variants — serde serializes it as `{"DeviceID": "..."}` or `{"ModelID": {...}}`, not as a bare string. Passing `props.arguments.cameraId` directly means any non-`"none"` input will produce a `ParseFailed` deep-link error and the camera switch will silently do nothing.

```ts
camera: props.arguments.cameraId === "none" ? null : { DeviceID: props.arguments.cameraId }
```

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

---

This is a comment left during a code review.
Path: apps/raycast/src/start.ts
Line: 5

Comment:
**Hardcoded macOS-only display name**

`"Built-in Retina Display"` is only a valid screen name on Apple Retina Macs. On non-Retina Macs the name differs (e.g. `"Built-in Display"`), on external-only setups it won't exist at all, and on Windows the display name format is entirely different. The `StartRecording` handler does an exact string match against `cap_recording::screen_capture::list_displays()`, so this will produce a `"No screen with name"` error on most machines.

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

---

This is a comment left during a code review.
Path: apps/raycast/src/utils.ts
Line: 4-8

Comment:
**Code comments violate project convention**

The project prohibits all code comments (single-line `//`, multi-line `/* */`, JSDoc, etc.) — code should be self-explanatory through naming and types. These comment blocks should be removed. The same pattern appears in `apps/raycast/src/start.ts` (line 5) and `apps/raycast/src/switch-camera.ts` (lines 5–8).

**Context Used:** CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=9a906542-f1fe-42c1-89a2-9f252d96d9f0))

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

---

This is a comment left during a code review.
Path: apps/raycast/src/utils.ts
Line: 14

Comment:
**Misleading success HUD**

`showHUD("Cap: Action Executed")` fires immediately after `open(url)` resolves, which only confirms the URL was handed to the OS — not that Cap received or processed it. A recording may be in the wrong state, or Cap may not even be running. A more accurate message like `"Sent to Cap"` would prevent users from assuming the action succeeded when it may not have.

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

Reviews (1): Last reviewed commit: "feat: implement deep links for Pause/Res..." | Re-trigger Greptile

Greptile also left 4 inline comments on this PR.

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

// We will assume "camera" in JSON is null to disable, or an object if specific.
// But for simple use, setting to an exact string from args.
await sendCapCommand("switch_camera", {
camera: props.arguments.cameraId === "none" ? null : props.arguments.cameraId
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Wrong serialization format for DeviceOrModelID

A plain string will always fail to deserialize on the Rust side. DeviceOrModelID is a Rust enum with two tuple variants — serde serializes it as {"DeviceID": "..."} or {"ModelID": {...}}, not as a bare string. Passing props.arguments.cameraId directly means any non-"none" input will produce a ParseFailed deep-link error and the camera switch will silently do nothing.

camera: props.arguments.cameraId === "none" ? null : { DeviceID: props.arguments.cameraId }
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast/src/switch-camera.ts
Line: 10

Comment:
**Wrong serialization format for `DeviceOrModelID`**

A plain string will always fail to deserialize on the Rust side. `DeviceOrModelID` is a Rust enum with two tuple variants — serde serializes it as `{"DeviceID": "..."}` or `{"ModelID": {...}}`, not as a bare string. Passing `props.arguments.cameraId` directly means any non-`"none"` input will produce a `ParseFailed` deep-link error and the camera switch will silently do nothing.

```ts
camera: props.arguments.cameraId === "none" ? null : { DeviceID: props.arguments.cameraId }
```

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


export default async function Command() {
await sendCapCommand("start_recording", {
capture_mode: { screen: "Built-in Retina Display" }, // Fallback default or need to handle better
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Hardcoded macOS-only display name

"Built-in Retina Display" is only a valid screen name on Apple Retina Macs. On non-Retina Macs the name differs (e.g. "Built-in Display"), on external-only setups it won't exist at all, and on Windows the display name format is entirely different. The StartRecording handler does an exact string match against cap_recording::screen_capture::list_displays(), so this will produce a "No screen with name" error on most machines.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast/src/start.ts
Line: 5

Comment:
**Hardcoded macOS-only display name**

`"Built-in Retina Display"` is only a valid screen name on Apple Retina Macs. On non-Retina Macs the name differs (e.g. `"Built-in Display"`), on external-only setups it won't exist at all, and on Windows the display name format is entirely different. The `StartRecording` handler does an exact string match against `cap_recording::screen_capture::list_displays()`, so this will produce a `"No screen with name"` error on most machines.

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

Comment on lines +4 to +8
// If no object value is passed, it represents a simple unit enum variant.
// Rust expects: "stop_recording"
// If an object is passed, Rust expects: {"switch_microphone": {"mic_label": "mic"}}
const valuePayload = objectValue
? JSON.stringify({ [action]: objectValue })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Code comments violate project convention

The project prohibits all code comments (single-line //, multi-line /* */, JSDoc, etc.) — code should be self-explanatory through naming and types. These comment blocks should be removed. The same pattern appears in apps/raycast/src/start.ts (line 5) and apps/raycast/src/switch-camera.ts (lines 5–8).

Context Used: CLAUDE.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast/src/utils.ts
Line: 4-8

Comment:
**Code comments violate project convention**

The project prohibits all code comments (single-line `//`, multi-line `/* */`, JSDoc, etc.) — code should be self-explanatory through naming and types. These comment blocks should be removed. The same pattern appears in `apps/raycast/src/start.ts` (line 5) and `apps/raycast/src/switch-camera.ts` (lines 5–8).

**Context Used:** CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=9a906542-f1fe-42c1-89a2-9f252d96d9f0))

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

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!

const url = `cap://action?value=${encodeURIComponent(valuePayload)}`;
try {
await open(url);
await showHUD(`Cap: Action Executed`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Misleading success HUD

showHUD("Cap: Action Executed") fires immediately after open(url) resolves, which only confirms the URL was handed to the OS — not that Cap received or processed it. A recording may be in the wrong state, or Cap may not even be running. A more accurate message like "Sent to Cap" would prevent users from assuming the action succeeded when it may not have.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast/src/utils.ts
Line: 14

Comment:
**Misleading success HUD**

`showHUD("Cap: Action Executed")` fires immediately after `open(url)` resolves, which only confirms the URL was handed to the OS — not that Cap received or processed it. A recording may be in the wrong state, or Cap may not even be running. A more accurate message like `"Sent to Cap"` would prevent users from assuming the action succeeded when it may not have.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bounty: Deeplinks support + Raycast Extension

1 participant