-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat: add recording control deeplinks and Raycast extension #1815
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
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,86 @@ | ||
| { | ||
| "name": "cap-raycast", | ||
| "title": "Cap", | ||
| "description": "Control Cap recording from Raycast", | ||
| "icon": "icon.png", | ||
| "author": "CapSoftware", | ||
| "categories": [ | ||
| "Productivity", | ||
| "Media" | ||
| ], | ||
| "license": "MIT", | ||
| "commands": [ | ||
| { | ||
| "name": "start-recording", | ||
| "title": "Start Recording", | ||
| "description": "Start a new recording in Cap", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "stop-recording", | ||
| "title": "Stop Recording", | ||
| "description": "Stop current recording in Cap", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "pause-recording", | ||
| "title": "Pause Recording", | ||
| "description": "Pause current recording in Cap", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "resume-recording", | ||
| "title": "Resume Recording", | ||
| "description": "Resume current recording in Cap", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "toggle-pause", | ||
| "title": "Toggle Pause", | ||
| "description": "Toggle pause/resume for current recording", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "switch-camera", | ||
| "title": "Switch Camera", | ||
| "description": "Switch to a specific camera", | ||
| "mode": "no-view", | ||
| "arguments": [ | ||
| { | ||
| "name": "camera", | ||
| "placeholder": "Camera Name/ID", | ||
| "type": "text", | ||
| "required": true | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "name": "switch-mic", | ||
| "title": "Switch Microphone", | ||
| "description": "Switch to a specific microphone", | ||
| "mode": "no-view", | ||
| "arguments": [ | ||
| { | ||
| "name": "mic", | ||
| "placeholder": "Microphone Label", | ||
| "type": "text", | ||
| "required": true | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "dependencies": { | ||
| "@raycast/api": "^1.88.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "20.8.10", | ||
| "@types/react": "18.2.27", | ||
| "typescript": "^5.2.2" | ||
| }, | ||
| "scripts": { | ||
| "dev": "ray dev", | ||
| "fix-lint": "ray lint --fix", | ||
| "lint": "ray lint", | ||
| "publish": "npx @raycast/api@latest publish" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,7 @@ | ||||||||||||||||||
| import { triggerAction } from "./utils"; | ||||||||||||||||||
| import { showHUD } from "@raycast/api"; | ||||||||||||||||||
|
|
||||||||||||||||||
| export default async function Command() { | ||||||||||||||||||
| await triggerAction("pause_recording"); | ||||||||||||||||||
| await showHUD("Pausing Cap Recording"); | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+4
to
+7
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.
Suggested change
Prompt To Fix With AIThis 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. |
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { triggerAction } from "./utils"; | ||
| import { showHUD } from "@raycast/api"; | ||
|
|
||
| export default async function Command() { | ||
| await triggerAction("resume_recording"); | ||
| await showHUD("Resuming Cap Recording"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { triggerAction } from "./utils"; | ||
| import { showHUD } from "@raycast/api"; | ||
|
|
||
| export default async function Command() { | ||
| // Trigger a standard recording. User can configure devices in the app. | ||
| await triggerAction({ | ||
| start_recording: { | ||
| capture_mode: { screen: "Main" }, | ||
| camera: null, | ||
| mic_label: null, | ||
| capture_system_audio: true, | ||
| mode: "instant" | ||
| } | ||
| }); | ||
|
Comment on lines
+6
to
+14
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.
Prompt To Fix With AIThis 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. |
||
| await showHUD("Starting Cap Recording"); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { triggerAction } from "./utils"; | ||
| import { showHUD } from "@raycast/api"; | ||
|
|
||
| export default async function Command() { | ||
| await triggerAction("stop_recording"); | ||
| await showHUD("Stopping Cap Recording"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { triggerAction } from "./utils"; | ||
| import { showHUD } from "@raycast/api"; | ||
|
|
||
| interface Arguments { | ||
| camera: string; | ||
| } | ||
|
|
||
| export default async function Command(props: { arguments: Arguments }) { | ||
| const { camera } = props.arguments; | ||
| // We assume it's a DeviceID for simplicity in this Raycast command | ||
| await triggerAction({ switch_camera: { camera: { device_id: camera } } }); | ||
|
Comment on lines
+10
to
+11
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.
Prompt To Fix With AIThis 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. |
||
| await showHUD(`Switching Camera to ${camera}`); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { triggerAction } from "./utils"; | ||
| import { showHUD } from "@raycast/api"; | ||
|
|
||
| interface Arguments { | ||
| mic: string; | ||
| } | ||
|
|
||
| export default async function Command(props: { arguments: Arguments }) { | ||
| const { mic } = props.arguments; | ||
| await triggerAction({ switch_mic: { mic_label: mic } }); | ||
| await showHUD(`Switching Microphone to ${mic}`); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { triggerAction } from "./utils"; | ||
| import { showHUD } from "@raycast/api"; | ||
|
|
||
| export default async function Command() { | ||
| await triggerAction("toggle_pause_recording"); | ||
| await showHUD("Toggling Cap Pause"); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,12 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import { open, showHUD } from "@raycast/api"; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export async function triggerAction(action: any) { | ||||||||||||||||||||||||||||||||||||||||||||||
|
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.
Prompt To Fix With AIThis 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. |
||||||||||||||||||||||||||||||||||||||||||||||
| 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); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+12
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.
Suggested change
Prompt To Fix With AIThis 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. |
||||||||||||||||||||||||||||||||||||||||||||||
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.
[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/apiversion instead of@latest.