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
20 changes: 20 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,14 @@ pub enum DeepLinkAction {
mode: RecordingMode,
},
StopRecording,
PauseRecording,
ResumeRecording,
SwitchMicrophone {
mic_label: Option<String>,
},
SwitchCamera {
camera: Option<DeviceOrModelID>,
},
OpenEditor {
project_path: PathBuf,
},
Expand Down Expand Up @@ -147,6 +155,18 @@ 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::SwitchMicrophone { mic_label } => {
crate::set_mic_input(app.state(), mic_label).await
}
DeepLinkAction::SwitchCamera { camera } => {
crate::set_camera_input(app.clone(), app.state(), camera, None).await
}
DeepLinkAction::OpenEditor { project_path } => {
crate::open_project_from_path(Path::new(&project_path), app.clone())
}
Expand Down
79 changes: 79 additions & 0 deletions apps/raycast/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{
"$schema": "https://www.raycast.com/downloads/extension.schema.json",
"name": "cap-controls",
"title": "Cap Controls",
"description": "Control the Cap screen recording app directly from Raycast",
"icon": "command-icon.png",
"author": "Ojas2095",
"license": "MIT",
"commands": [
{
"name": "start",
"title": "Start Recording",
"description": "Start a Cap recording",
"mode": "no-view"
},
{
"name": "stop",
"title": "Stop Recording",
"description": "Stops the current Cap recording",
"mode": "no-view"
},
{
"name": "pause",
"title": "Pause Recording",
"description": "Pauses the current Cap recording",
"mode": "no-view"
},
{
"name": "resume",
"title": "Resume Recording",
"description": "Resumes the paused Cap recording",
"mode": "no-view"
},
{
"name": "switch-mic",
"title": "Switch Microphone",
"description": "Switch Cap's recording microphone",
"mode": "no-view",
"arguments": [
{
"name": "micLabel",
"type": "text",
"placeholder": "Microphone Name (or 'none')",
"required": true
}
]
},
{
"name": "switch-camera",
"title": "Switch Camera",
"description": "Switch Cap's recording camera",
"mode": "no-view",
"arguments": [
{
"name": "cameraId",
"type": "text",
"placeholder": "Camera ID (or 'none')",
"required": true
}
]
}
],
"dependencies": {
"@raycast/api": "^1.65.0"
},
"devDependencies": {
"@raycast/eslint-config": "^1.0.6",
"@types/node": "20.8.10",
"@types/react": "18.2.27",
"eslint": "^8.51.0",
"prettier": "^3.0.3",
"typescript": "^5.2.2"
},
"scripts": {
"build": "ray build -e dist",
"dev": "ray develop",
"publish": "npx @raycast/api@latest publish"
}
}
5 changes: 5 additions & 0 deletions apps/raycast/src/pause.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { sendCapCommand } from "./utils";

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

export default async function Command() {
await sendCapCommand("resume_recording");
}
11 changes: 11 additions & 0 deletions apps/raycast/src/start.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { sendCapCommand } from "./utils";

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.

camera: null,
mic_label: null,
capture_system_audio: true,
mode: "Screen"
});
}
5 changes: 5 additions & 0 deletions apps/raycast/src/stop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { sendCapCommand } from "./utils";

export default async function Command() {
await sendCapCommand("stop_recording");
}
12 changes: 12 additions & 0 deletions apps/raycast/src/switch-camera.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { LaunchProps } from "@raycast/api";
import { sendCapCommand } from "./utils";

export default async function Command(props: LaunchProps<{ arguments: { cameraId: string } }>) {
// DeviceOrModelID has a few formats depending on Cap's implementation
// Passing it blindly string is difficult because Rust expects the exact Enum layout.
// 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.

});
}
8 changes: 8 additions & 0 deletions apps/raycast/src/switch-mic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { LaunchProps } from "@raycast/api";
import { sendCapCommand } from "./utils";

export default async function Command(props: LaunchProps<{ arguments: { micLabel: string } }>) {
await sendCapCommand("switch_microphone", {
mic_label: props.arguments.micLabel === "none" ? null : props.arguments.micLabel
});
}
19 changes: 19 additions & 0 deletions apps/raycast/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { open, showHUD } from "@raycast/api";

export async function sendCapCommand(action: string, objectValue?: Record<string, unknown>) {
// 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 })
Comment on lines +4 to +8
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!

: `"${action}"`;

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.

} catch (error) {
await showHUD("Failed to communicate with Cap. Is it installed?");
console.error(error);
}
}
14 changes: 14 additions & 0 deletions apps/raycast/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "CommonJS",
"target": "ES2021",
"jsx": "react",
"lib": ["ES2021", "DOM"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node"
},
"include": ["src/**/*"]
}