-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat: Raycast Deeplink Extension (#1540) #1727
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
base: main
Are you sure you want to change the base?
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,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" | ||
| } | ||
| } |
| 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"); | ||
| } |
| 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"); | ||
| } |
| 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 | ||
| camera: null, | ||
| mic_label: null, | ||
| capture_system_audio: true, | ||
| mode: "Screen" | ||
| }); | ||
| } | ||
| 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"); | ||
| } |
| 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 | ||
|
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.
A plain string will always fail to deserialize on the Rust side. camera: props.arguments.cameraId === "none" ? null : { DeviceID: props.arguments.cameraId }Prompt To Fix With AIThis 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. |
||
| }); | ||
| } | ||
| 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 | ||
| }); | ||
| } |
| 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
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.
The project prohibits all code comments (single-line Context Used: CLAUDE.md (source) Prompt To Fix With AIThis 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`); | ||
|
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: 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); | ||
| } | ||
| } | ||
| 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/**/*"] | ||
| } |
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.
"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. TheStartRecordinghandler does an exact string match againstcap_recording::screen_capture::list_displays(), so this will produce a"No screen with name"error on most machines.Prompt To Fix With AI