Skip to content
Closed
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
24 changes: 24 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,15 @@ pub enum DeepLinkAction {
mode: RecordingMode,
},
StopRecording,
PauseRecording,
ResumeRecording,
TogglePauseRecording,
SwitchCamera {
camera: DeviceOrModelID,
},
SwitchMic {
mic_label: String,
},
OpenEditor {
project_path: PathBuf,
},
Expand Down Expand Up @@ -147,6 +156,21 @@ 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::SwitchCamera { camera } => {
crate::set_camera_input(app.clone(), app.state(), Some(camera), None).await
}
DeepLinkAction::SwitchMic { mic_label } => {
crate::set_mic_input(app.state(), Some(mic_label)).await
}
DeepLinkAction::OpenEditor { project_path } => {
crate::open_project_from_path(Path::new(&project_path), app.clone())
}
Expand Down
86 changes: 86 additions & 0 deletions packages/raycast/package.json
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"
Copy link
Copy Markdown

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/api version instead of @latest.

}
}
7 changes: 7 additions & 0 deletions packages/raycast/src/pause-recording.ts
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
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 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.

Suggested change
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.

7 changes: 7 additions & 0 deletions packages/raycast/src/resume-recording.ts
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");
}
16 changes: 16 additions & 0 deletions packages/raycast/src/start-recording.ts
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
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 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.

await showHUD("Starting Cap Recording");
}
7 changes: 7 additions & 0 deletions packages/raycast/src/stop-recording.ts
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");
}
13 changes: 13 additions & 0 deletions packages/raycast/src/switch-camera.ts
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
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 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.

await showHUD(`Switching Camera to ${camera}`);
}
12 changes: 12 additions & 0 deletions packages/raycast/src/switch-mic.ts
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}`);
}
7 changes: 7 additions & 0 deletions packages/raycast/src/toggle-pause.ts
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");
}
12 changes: 12 additions & 0 deletions packages/raycast/src/utils.ts
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) {
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 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.

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
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 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.

Suggested change
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.