feat: extend deeplinks with pause/resume + Raycast extension#1687
feat: extend deeplinks with pause/resume + Raycast extension#1687TateLyman wants to merge 2 commits intoCapSoftware:mainfrom
Conversation
- Add PauseRecording, ResumeRecording, TogglePauseRecording deeplink actions - Create Raycast extension with start/stop/toggle-pause/settings commands - Extension uses cap:// deeplink protocol to control recording
extensions/raycast/src/utils.ts
Outdated
| @@ -0,0 +1,7 @@ | |||
| import { open } from "@raycast/api"; | |||
|
|
|||
| export async function sendDeepLink(action: object) { | |||
There was a problem hiding this comment.
object type excludes strings — TypeScript compile error
stop-recording.ts and toggle-pause.ts both call sendDeepLink("stop_recording") and sendDeepLink("toggle_pause_recording") passing string literals. TypeScript's built-in object type explicitly excludes all primitive types (strings, numbers, booleans), so both call sites are type errors that will prevent the extension from building.
The fix is to widen the parameter type to accept primitives:
| export async function sendDeepLink(action: object) { | |
| export async function sendDeepLink(action: string | Record<string, unknown>) { |
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/raycast/src/utils.ts
Line: 3
Comment:
**`object` type excludes strings — TypeScript compile error**
`stop-recording.ts` and `toggle-pause.ts` both call `sendDeepLink("stop_recording")` and `sendDeepLink("toggle_pause_recording")` passing string literals. TypeScript's built-in `object` type explicitly excludes all primitive types (strings, numbers, booleans), so both call sites are type errors that will prevent the extension from building.
The fix is to widen the parameter type to accept primitives:
```suggestion
export async function sendDeepLink(action: string | Record<string, unknown>) {
```
How can I resolve this? If you propose a fix, please make it concise.| export default async function Command() { | ||
| await sendDeepLink({ | ||
| start_recording: { | ||
| capture_mode: { screen: "Main Display" }, |
There was a problem hiding this comment.
Hard-coded display name will silently fail on most systems
"Main Display" is matched by name against cap_recording::screen_capture::list_displays() in Rust. On non-English macOS locales and on any machine whose primary display doesn't carry that exact name, find returns None, which produces an Err(...) that is only printed to stderr. The user will still see the "Recording started" HUD but nothing will actually record.
Consider either documenting this limitation clearly or falling back to the first available display in the Rust handler. At minimum, a comment here explaining that this name must match the OS display name would help future contributors.
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/raycast/src/start-recording.ts
Line: 7
Comment:
**Hard-coded display name will silently fail on most systems**
`"Main Display"` is matched by name against `cap_recording::screen_capture::list_displays()` in Rust. On non-English macOS locales and on any machine whose primary display doesn't carry that exact name, `find` returns `None`, which produces an `Err(...)` that is only printed to stderr. The user will still see the `"Recording started"` HUD but nothing will actually record.
Consider either documenting this limitation clearly or falling back to the first available display in the Rust handler. At minimum, a comment here explaining that this name must match the OS display name would help future contributors.
How can I resolve this? If you propose a fix, please make it concise.| "title": "Cap", | ||
| "description": "Control Cap screen recording from Raycast", | ||
| "icon": "command-icon.png", | ||
| "author": "TateLyman", |
There was a problem hiding this comment.
command-icon.png is referenced but not included in the PR
package.json declares "icon": "command-icon.png", but no such file appears in extensions/raycast/. Raycast validates the icon at build and submission time — the extension will fail to build or be rejected from the store without it. The icon must be a 512×512 PNG placed at the root of the extension directory.
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/raycast/package.json
Line: 7
Comment:
**`command-icon.png` is referenced but not included in the PR**
`package.json` declares `"icon": "command-icon.png"`, but no such file appears in `extensions/raycast/`. Raycast validates the icon at build and submission time — the extension will fail to build or be rejected from the store without it. The icon must be a `512×512` PNG placed at the root of the extension directory.
How can I resolve this? If you propose a fix, please make it concise.
extensions/raycast/src/utils.ts
Outdated
| const value = encodeURIComponent(JSON.stringify(action)); | ||
| const url = `cap://action?value=${value}`; | ||
| await open(url); |
There was a problem hiding this comment.
No error handling when Cap is not running
open(url) will silently succeed even when Cap is not installed or not running — the OS URL handler simply won't find a target and does nothing. The user will see the success HUD (e.g., "Recording started") with no indication that the action was never delivered.
Wrapping the call in a try/catch and showing an error HUD would significantly improve discoverability:
export async function sendDeepLink(action: string | Record<string, unknown>) {
const value = encodeURIComponent(JSON.stringify(action));
const url = `cap://action?value=${value}`;
try {
await open(url);
} catch (e) {
await showHUD("Failed to communicate with Cap — is it running?");
throw e;
}
}Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/raycast/src/utils.ts
Line: 4-6
Comment:
**No error handling when Cap is not running**
`open(url)` will silently succeed even when Cap is not installed or not running — the OS URL handler simply won't find a target and does nothing. The user will see the success HUD (e.g., "Recording started") with no indication that the action was never delivered.
Wrapping the call in a try/catch and showing an error HUD would significantly improve discoverability:
```ts
export async function sendDeepLink(action: string | Record<string, unknown>) {
const value = encodeURIComponent(JSON.stringify(action));
const url = `cap://action?value=${value}`;
try {
await open(url);
} catch (e) {
await showHUD("Failed to communicate with Cap — is it running?");
throw e;
}
}
```
How can I resolve this? If you propose a fix, please make it concise.| { | ||
| "$schema": "https://www.raycast.com/schemas/extension.json", | ||
| "name": "cap", | ||
| "title": "Cap", | ||
| "description": "Control Cap screen recording from Raycast", | ||
| "icon": "command-icon.png", | ||
| "author": "TateLyman", | ||
| "categories": ["Productivity"], | ||
| "license": "MIT", | ||
| "commands": [ | ||
| { "name": "start-recording", "title": "Start Recording", "description": "Start a new screen recording", "mode": "no-view" }, | ||
| { "name": "stop-recording", "title": "Stop Recording", "description": "Stop the current recording", "mode": "no-view" }, | ||
| { "name": "toggle-pause", "title": "Toggle Pause", "description": "Pause or resume the current recording", "mode": "no-view" }, | ||
| { "name": "open-settings", "title": "Open Settings", "description": "Open Cap settings", "mode": "no-view" } | ||
| ], | ||
| "dependencies": { | ||
| "@raycast/api": "^1.50.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@raycast/eslint-config": "^1.0.6", | ||
| "typescript": "^5.0.0" | ||
| } | ||
| } |
There was a problem hiding this comment.
Missing
tsconfig.json and lock file
A standard Raycast TypeScript extension requires both a tsconfig.json (Raycast's scaffolder ships one that sets "moduleResolution": "bundler" and "jsx": "react-jsx") and a lock file (package-lock.json or yarn.lock) so that CI and reviewers can reproduce the exact dependency tree. Neither is present in this PR.
Without tsconfig.json the TypeScript compiler will use loose defaults, which can mask type errors. Without a lock file, @raycast/api will float to whatever ^1.50.0 resolves to at install time, potentially breaking the build later.
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/raycast/package.json
Line: 1-23
Comment:
**Missing `tsconfig.json` and lock file**
A standard Raycast TypeScript extension requires both a `tsconfig.json` (Raycast's scaffolder ships one that sets `"moduleResolution": "bundler"` and `"jsx": "react-jsx"`) and a lock file (`package-lock.json` or `yarn.lock`) so that CI and reviewers can reproduce the exact dependency tree. Neither is present in this PR.
Without `tsconfig.json` the TypeScript compiler will use loose defaults, which can mask type errors. Without a lock file, `@raycast/api` will float to whatever `^1.50.0` resolves to at install time, potentially breaking the build later.
How can I resolve this? If you propose a fix, please make it concise.
Summary
PauseRecording,ResumeRecording,TogglePauseRecordingdeeplink actions to the existing deeplink systemChanges
Deeplink Actions (
deeplink_actions.rs)DeepLinkActionenumpause_recording,resume_recording,toggle_pause_recordingfunctionsRaycast Extension (
extensions/raycast/)start-recording— Starts screen recording viacap://actiondeeplinkstop-recording— Stops current recordingtoggle-pause— Pauses or resumes recordingopen-settings— Opens Cap settings windowTest plan
open cap://action?value=...in terminalCloses #1540
Greptile Summary
This PR extends Cap's deeplink system with three new pause/resume actions and introduces a Raycast extension providing four no-view commands (Start, Stop, Toggle Pause, Open Settings). The Rust side is clean and correct; the primary concerns are all in the new Raycast extension.
Key changes:
deeplink_actions.rs:PauseRecording,ResumeRecording, andTogglePauseRecordingvariants added toDeepLinkActionand correctly wired to existingrecording::functions — no issues.extensions/raycast/src/utils.ts:sendDeepLinkis typed asaction: object, which excludes string primitives.stop-recording.tsandtoggle-pause.tsboth pass string literals, producing TypeScript compile errors that will prevent the extension from building.extensions/raycast/src/start-recording.ts: The display name"Main Display"is hard-coded. The Rust handler performs an exact string match against OS display names and returns an error (printed to stderr) when no match is found — the user sees the success HUD regardless.extensions/raycast/package.json: Referencescommand-icon.pngwhich is absent from the PR; the extension also lacks atsconfig.jsonand a lock file, making it non-buildable out of the box.Issues found:
sendDeepLink(action: object)called withstringin two files; fix by widening tostring | Record<string, unknown>.command-icon.pngmissing; extension cannot be built or submitted to the Raycast store without it."Main Display"will silently fail on non-English macOS or any machine with a differently-named primary display.sendDeepLinkwhen Cap is not running; success HUD is shown unconditionally.tsconfig.jsonand lock file for reproducible builds.Confidence Score: 2/5
Not safe to merge — the Raycast extension has a TypeScript compile error and is missing required assets, so it cannot be built in its current state.
The Rust deeplink additions are correct and complete, but the TypeScript extension has a blocking compile error (string passed where
objectis expected), a missing required icon asset, and no tsconfig/lock file. These issues make the extension non-functional out of the box and would fail Raycast's store validation. The hard-coded display name also creates a silent failure path for most users. At least the type error and missing icon need to be resolved before this is ready to ship.extensions/raycast/src/utils.ts(type error),extensions/raycast/package.json(missing icon + config files),extensions/raycast/src/start-recording.ts(hard-coded display name)Important Files Changed
action: object, which excludes string primitives — callers in stop-recording.ts and toggle-pause.ts pass strings, causing a TypeScript compile error; also missing error handling.Sequence Diagram
sequenceDiagram participant User participant Raycast participant macOS as macOS URL Handler participant Cap as Cap (Tauri) participant Recording as recording.rs User->>Raycast: Invoke command (e.g. Toggle Pause) Raycast->>Raycast: sendDeepLink("toggle_pause_recording") Note over Raycast: JSON.stringify → encodeURIComponent Raycast->>macOS: open("cap://action?value=%22toggle_pause_recording%22") macOS->>Cap: URL scheme handler Cap->>Cap: DeepLinkAction::try_from(url) Note over Cap: serde_json::from_str → TogglePauseRecording Cap->>Recording: toggle_pause_recording(app, state) Recording-->>Cap: Ok(()) / Err(String) Cap-->>macOS: (fire-and-forget) Raycast->>User: showHUD("Recording pause toggled") Note over Raycast,User: HUD shown before action completesPrompt To Fix All With AI
Reviews (1): Last reviewed commit: "feat: extend deeplinks with pause/resume..." | Re-trigger Greptile
(2/5) Greptile learns from your feedback when you react with thumbs up/down!