feat: add recording control deeplinks and Raycast extension#1815
feat: add recording control deeplinks and Raycast extension#1815camirian wants to merge 1 commit into
Conversation
| "dev": "ray dev", | ||
| "fix-lint": "ray lint --fix", | ||
| "lint": "ray lint", | ||
| "publish": "npx @raycast/api@latest publish" |
There was a problem hiding this comment.
[LOW] Publish script executes unpinned package via npx @latest
Publish script runs npx @raycast/api@latest, fetching mutable code during release.
Fix: Use the local lockfile-resolved CLI or pin an exact @raycast/api version instead of @latest.
| export async function triggerAction(action: any) { | ||
| try { | ||
| const json = JSON.stringify(action); | ||
| const url = `cap-desktop://action?value=${encodeURIComponent(json)}`; | ||
| await open(url); | ||
| } catch (error) { | ||
| await showHUD("Failed to trigger Cap action"); | ||
| console.error(error); | ||
| } | ||
| } |
There was a problem hiding this comment.
Error is caught and swallowed here without re-throwing, so every caller unconditionally runs its success
showHUD immediately after. When open(url) fails the user sees the failure HUD replaced by the success HUD — the failure is completely masked.
| export async function triggerAction(action: any) { | |
| try { | |
| const json = JSON.stringify(action); | |
| const url = `cap-desktop://action?value=${encodeURIComponent(json)}`; | |
| await open(url); | |
| } catch (error) { | |
| await showHUD("Failed to trigger Cap action"); | |
| console.error(error); | |
| } | |
| } | |
| export async function triggerAction(action: any): Promise<boolean> { | |
| try { | |
| const json = JSON.stringify(action); | |
| const url = `cap-desktop://action?value=${encodeURIComponent(json)}`; | |
| await open(url); | |
| return true; | |
| } catch (error) { | |
| await showHUD("Failed to trigger Cap action"); | |
| console.error(error); | |
| return false; | |
| } | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/raycast/src/utils.ts
Line: 3-12
Comment:
Error is caught and swallowed here without re-throwing, so every caller unconditionally runs its success `showHUD` immediately after. When `open(url)` fails the user sees the failure HUD replaced by the success HUD — the failure is completely masked.
```suggestion
export async function triggerAction(action: any): Promise<boolean> {
try {
const json = JSON.stringify(action);
const url = `cap-desktop://action?value=${encodeURIComponent(json)}`;
await open(url);
return true;
} catch (error) {
await showHUD("Failed to trigger Cap action");
console.error(error);
return false;
}
}
```
How can I resolve this? If you propose a fix, please make it concise.| export default async function Command() { | ||
| await triggerAction("pause_recording"); | ||
| await showHUD("Pausing Cap Recording"); | ||
| } |
There was a problem hiding this comment.
Because
triggerAction silently swallows errors, showHUD here always runs — even when the deeplink failed to open. The same pattern appears in resume-recording.ts, stop-recording.ts, toggle-pause.ts, switch-camera.ts, and switch-mic.ts. Guard the success HUD on the return value of triggerAction.
| export default async function Command() { | |
| await triggerAction("pause_recording"); | |
| await showHUD("Pausing Cap Recording"); | |
| } | |
| export default async function Command() { | |
| const ok = await triggerAction("pause_recording"); | |
| if (ok) await showHUD("Pausing Cap Recording"); | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/raycast/src/pause-recording.ts
Line: 4-7
Comment:
Because `triggerAction` silently swallows errors, `showHUD` here always runs — even when the deeplink failed to open. The same pattern appears in `resume-recording.ts`, `stop-recording.ts`, `toggle-pause.ts`, `switch-camera.ts`, and `switch-mic.ts`. Guard the success HUD on the return value of `triggerAction`.
```suggestion
export default async function Command() {
const ok = await triggerAction("pause_recording");
if (ok) await showHUD("Pausing Cap Recording");
}
```
How can I resolve this? If you propose a fix, please make it concise.| await triggerAction({ | ||
| start_recording: { | ||
| capture_mode: { screen: "Main" }, | ||
| camera: null, | ||
| mic_label: null, | ||
| capture_system_audio: true, | ||
| mode: "instant" | ||
| } | ||
| }); |
There was a problem hiding this comment.
The screen name
"Main" is hardcoded, but the backend matches it with an exact string comparison against list_displays(). On systems where the primary display is named anything other than "Main" the action silently fails with no user-visible feedback — the deeplink error is only logged to stderr in the desktop app.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/raycast/src/start-recording.ts
Line: 6-14
Comment:
The screen name `"Main"` is hardcoded, but the backend matches it with an exact string comparison against `list_displays()`. On systems where the primary display is named anything other than `"Main"` the action silently fails with no user-visible feedback — the deeplink error is only logged to stderr in the desktop app.
How can I resolve this? If you propose a fix, please make it concise.| // We assume it's a DeviceID for simplicity in this Raycast command | ||
| await triggerAction({ switch_camera: { camera: { device_id: camera } } }); |
There was a problem hiding this comment.
Placeholder misleads users about model ID support. The
package.json argument placeholder says "Camera Name/ID", implying a human-readable name or model ID is accepted, but this command always wraps the value as { device_id: camera }. Passing a model ID or display name will send it to the backend as a device ID and silently fail to match any camera. Either restrict the placeholder to "Device ID" or branch on the input to support both device_id and model_id.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/raycast/src/switch-camera.ts
Line: 10-11
Comment:
**Placeholder misleads users about model ID support.** The `package.json` argument placeholder says `"Camera Name/ID"`, implying a human-readable name or model ID is accepted, but this command always wraps the value as `{ device_id: camera }`. Passing a model ID or display name will send it to the backend as a device ID and silently fail to match any camera. Either restrict the placeholder to `"Device ID"` or branch on the input to support both `device_id` and `model_id`.
How can I resolve this? If you propose a fix, please make it concise.| @@ -0,0 +1,12 @@ | |||
| import { open, showHUD } from "@raycast/api"; | |||
|
|
|||
| export async function triggerAction(action: any) { | |||
There was a problem hiding this comment.
Loose
any type removes compile-time safety. Using any for action means malformed payloads (e.g., switch_mic with a typo in the key) pass TypeScript checking and only fail silently at runtime. A typed union matching the backend's DeepLinkAction variants would catch these mistakes before shipping.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/raycast/src/utils.ts
Line: 3
Comment:
**Loose `any` type removes compile-time safety.** Using `any` for `action` means malformed payloads (e.g., `switch_mic` with a typo in the key) pass TypeScript checking and only fail silently at runtime. A typed union matching the backend's `DeepLinkAction` variants would catch these mistakes before shipping.
How can I resolve this? If you propose a fix, please make it concise.|
/attempt #1540 |
|
/attempt |
|
/claim #1540 |
This PR implements the requested deeplink actions (pause, resume, toggle, switch device) and adds a corresponding Raycast extension. Fixes #1540.
Greptile Summary
This PR wires up five new deeplink actions (
PauseRecording,ResumeRecording,TogglePauseRecording,SwitchCamera,SwitchMic) in the Rust backend and ships a new Raycast extension that invokes them over thecap-desktop://actionURL scheme.deeplink_actions.rs): New enum variants andexecute()arms delegate cleanly to the existing recording functions — no issues here.utils.ts:triggerActioncatches errors internally without re-throwing, so every caller unconditionally shows a success HUD even when the deeplink failed to open. The failure HUD is immediately overwritten by the success message.start-recording.ts:capture_modeis hardcoded to{ screen: "Main" }, which silently fails on any machine where the primary display is not named exactly"Main"(backend returns an error that is only logged to stderr).Confidence Score: 3/5
The Rust backend additions are solid, but two defects in the Raycast layer mean that failures show a success message to the user and the start-recording command will silently do nothing on most machines.
The error-swallowing in triggerAction causes the success HUD to overwrite the failure HUD on every command — users get no actionable feedback when a deeplink fails. The hardcoded "Main" display name in start-recording.ts will silently break recording on any system whose primary display has a different name, with no user-visible error. Both are present defects on the current code path.
packages/raycast/src/utils.ts and packages/raycast/src/start-recording.ts need the most attention before this is published to the Raycast store.
Important Files Changed
Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "feat: add recording control deeplinks an..." | Re-trigger Greptile