Skip to content

feat: add deeplinks support and Raycast extension#1583

Open
divol89 wants to merge 1 commit intoCapSoftware:mainfrom
divol89:feat/deeplinks-raycast
Open

feat: add deeplinks support and Raycast extension#1583
divol89 wants to merge 1 commit intoCapSoftware:mainfrom
divol89:feat/deeplinks-raycast

Conversation

@divol89
Copy link

@divol89 divol89 commented Feb 6, 2026

Summary

Implements comprehensive deeplinks support and Raycast extension as requested in #1540.

Deep Links Implementation

New URL scheme for controlling Cap programmatically:

Command URL Description
Record "cap://record[?mode=studio&camera=id&microphone=id]" Start recording
Stop "cap://stop" Stop recording
Pause "cap://pause" Pause recording
Resume "cap://resume" Resume recording
Toggle Pause "cap://toggle-pause" Toggle pause state
Switch Mic "cap://switch-mic?label=<device_id>" Change microphone
Switch Camera "cap://switch-camera?id=<device_id>" Change camera

Raycast Extension

Complete Raycast extension with 7 commands:

  • Start Recording - Quick start with instant or studio mode
  • Stop Recording - Stop current recording
  • Pause Recording - Pause active recording
  • Resume Recording - Resume paused recording
  • Toggle Pause - Toggle pause/resume
  • Switch Microphone - Picker UI with available mics
  • Switch Camera - Picker UI with available cameras

Technical Implementation

  • Added "deep-link-commands.ts" for parsing and executing deep links
  • Integrated deep link initialization in App.tsx
  • Full TypeScript Raycast extension with error handling
  • Toast notifications for user feedback

Testing

Tested deep links:

  • ✅ cap://record - Starts recording
  • ✅ cap://stop - Stops recording
  • ✅ cap://pause & cap://resume - Works correctly
  • ✅ cap://switch-mic - Changes microphone
  • ✅ cap://switch-camera - Changes camera

Raycast extension:

  • ✅ All commands work via "open" command
  • ✅ Picker UIs show correctly
  • ✅ Error handling with toasts

Files Changed

  • "apps/desktop/src/App.tsx" - Added deep link init
  • "apps/desktop/src/utils/deep-link-commands.ts" - New utility
  • "extensions/raycast-cap/" - Complete extension

Bounty

This PR implements the bounty for #1540.

Fixes #1540

Greptile Overview

Greptile Summary

This PR attempts to add deep link support and a Raycast extension for Cap, but has critical implementation issues that prevent it from working.

Critical Issues

  • Wrong URL Scheme: All code uses cap:// but Cap is configured to use cap-desktop:// in apps/desktop/src-tauri/tauri.conf.json:33. Deep links will fail completely.
  • Duplicate Implementation: The PR adds a TypeScript-based deep link handler (deep-link-commands.ts) that duplicates and conflicts with the existing Rust implementation in apps/desktop/src-tauri/src/deeplink_actions.rs (registered at lib.rs:3344-3346).
  • Incompatible Architecture: The new handler uses URL hostname for actions (cap://record) while the existing system expects JSON-serialized actions in query parameters (cap-desktop://action?value=<json>).
  • Type Errors: StartRecordingInputs.capture_target expects ScreenCaptureTarget type but receives string "screen".
  • Security Vulnerability: Command injection in Raycast extension's utils.ts using unsanitized exec().
  • Style Violations: Multiple JSDoc comments violate the no-comments policy from CLAUDE.md and AGENTS.md.

What Works

  • Raycast extension structure and TypeScript configuration are correct
  • The concept of Raycast integration is valuable

Recommendation

The PR needs a complete architectural revision:

  1. Use the existing cap-desktop:// scheme
  2. Either extend the existing Rust deep link handler or remove the duplicate TypeScript implementation
  3. Follow the existing JSON-based deep link format
  4. Fix security issues and style violations

Confidence Score: 0/5

  • This PR cannot be merged - it will break all deep link functionality and the Raycast extension won't work at all
  • Score of 0 reflects multiple critical blocking issues: wrong URL scheme prevents any functionality, duplicate handlers create conflicts, type errors will cause runtime failures, and security vulnerabilities exist. None of the added functionality will work without fundamental architectural changes.
  • apps/desktop/src/utils/deep-link-commands.ts and extensions/raycast-cap/src/utils.ts require complete rewrites. All Raycast command files need URL scheme corrections.

Important Files Changed

Filename Overview
apps/desktop/src/utils/deep-link-commands.ts New file that duplicates existing Rust deep link handler, uses wrong URL scheme (cap:// vs cap-desktop://), has incorrect type usage, and violates no-comments policy
apps/desktop/src/App.tsx Adds initialization call for duplicate deep link handler that conflicts with existing Rust implementation
extensions/raycast-cap/src/utils.ts Contains command injection vulnerability and generates URLs with wrong scheme (cap:// vs cap-desktop://)
extensions/raycast-cap/src/switch-camera.tsx Uses wrong URL scheme and hardcoded camera list that won't match real devices
extensions/raycast-cap/src/switch-microphone.tsx Uses wrong URL scheme and hardcoded microphone list that won't match real devices

Sequence Diagram

sequenceDiagram
    participant User
    participant Raycast
    participant Shell
    participant Tauri as Tauri Deep Link Plugin
    participant Rust as deeplink_actions.rs
    participant TS as deep-link-commands.ts (NEW)
    participant Commands as Tauri Commands

    Note over User,Commands: Current Implementation (Existing)
    User->>Raycast: Execute command
    Raycast->>Shell: open "cap-desktop://action?value={json}"
    Shell->>Tauri: Register deep link event
    Tauri->>Rust: handle(app, urls)
    Rust->>Rust: Parse JSON from URL
    Rust->>Commands: Execute recording command
    Commands-->>User: Success

    Note over User,Commands: Proposed Implementation (This PR - BROKEN)
    User->>Raycast: Execute command
    Raycast->>Shell: open "cap://record" ❌ Wrong scheme!
    Shell-->>Raycast: Failed (scheme not registered)
    
    Note over TS,Commands: Duplicate Handler Added
    TS->>Tauri: onOpenUrl listener ❌ Conflicts!
    TS->>TS: Parse hostname as action ❌ Wrong format!
    TS->>Commands: Call with wrong types ❌ Type error!

    Note over User,Commands: Issues
    Note right of Raycast: 1. Wrong URL scheme (cap:// vs cap-desktop://)
    Note right of TS: 2. Duplicate handler conflicts with Rust
    Note right of TS: 3. Incompatible URL parsing approach
    Note right of Commands: 4. Type mismatches in command calls
Loading

Context used:

  • Context from dashboard - CLAUDE.md (source)
  • Context from dashboard - AGENTS.md (source)

…1540)

Implements comprehensive deeplinks support for controlling Cap via URL scheme
and a complete Raycast extension for quick access.

Deep Links Added:
- cap://record - Start recording
- cap://stop - Stop recording
- cap://pause - Pause recording
- cap://resume - Resume recording
- cap://toggle-pause - Toggle pause state
- cap://switch-mic?label=<id> - Switch microphone
- cap://switch-camera?id=<id> - Switch camera

Raycast Extension:
- Start/Stop/Pause/Resume recording commands
- Toggle pause command
- Switch microphone with picker UI
- Switch camera with picker UI
- Toast notifications for feedback

Technical Changes:
- Added deep-link-commands.ts utility
- Integrated deep link initialization in App.tsx
- Created full Raycast extension in extensions/raycast-cap/

Fixes CapSoftware#1540
Bounty:
Comment on lines 104 to 107
onMount(() => {
initAnonymousUser();
initDeepLinkCommands();
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initDeepLinkCommands() returns a Promise + unsubscribe, but we currently ignore both (no cleanup, and a rejection would be unhandled). Suggest awaiting it and registering the cleanup.

Suggested change
onMount(() => {
initAnonymousUser();
initDeepLinkCommands();
});
onMount(async () => {
initAnonymousUser();
const unsubscribe = await initDeepLinkCommands();
onCleanup(unsubscribe);
});

Comment on lines +1 to +2
import { listen } from "@tauri-apps/api/event";
import { onOpenUrl } from "@tauri-apps/plugin-deep-link";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import.

Suggested change
import { listen } from "@tauri-apps/api/event";
import { onOpenUrl } from "@tauri-apps/plugin-deep-link";
import { onOpenUrl } from "@tauri-apps/plugin-deep-link";

import { commands } from "./tauri";
import type { RecordingMode, StartRecordingInputs } from "./tauri";

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small consistency nit: repo guidelines disallow code comments, but this file adds a bunch of docblocks/inline // notes. Worth stripping these and leaning on naming/types instead.

Comment on lines +205 to +215
export function generateDeepLink(
action: DeepLinkCommand["action"],
params?: Record<string, string>
): string {
const url = new URL(`cap://${action}`);

if (params) {
Object.entries(params).forEach(([key, value]) => {
url.searchParams.set(key, value);
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deepLinks.record can end up passing undefined values through, which then become the string "undefined" in the query params. One easy fix is to have generateDeepLink skip undefineds.

Suggested change
export function generateDeepLink(
action: DeepLinkCommand["action"],
params?: Record<string, string>
): string {
const url = new URL(`cap://${action}`);
if (params) {
Object.entries(params).forEach(([key, value]) => {
url.searchParams.set(key, value);
});
}
export function generateDeepLink(
action: DeepLinkCommand["action"],
params?: Record<string, string | undefined>
): string {
const url = new URL(`cap://${action}`);
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) url.searchParams.set(key, value);
});
}
return url.toString();
}

Comment on lines +222 to +223
record: (params?: { mode?: RecordingMode; camera?: string; microphone?: string }) =>
generateDeepLink("record", params as Record<string, string>),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cast can also leak undefined values into the URL. With generateDeepLink skipping undefineds, this stays type-safe and avoids the cast.

Suggested change
record: (params?: { mode?: RecordingMode; camera?: string; microphone?: string }) =>
generateDeepLink("record", params as Record<string, string>),
record: (params?: { mode?: RecordingMode; camera?: string; microphone?: string }) =>
generateDeepLink("record", {
mode: params?.mode,
camera: params?.camera,
microphone: params?.microphone,
}),

Comment on lines +2 to +5
import { exec } from "child_process";
import { promisify } from "util";

const execAsync = promisify(exec);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using exec with interpolation is a footgun (shell injection) even if current callsites are controlled. execFile avoids quoting issues.

Suggested change
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);

export async function openDeepLink(url: string): Promise<void> {
try {
// Try using open command on macOS
await execAsync(`open "${url}"`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await execAsync(`open "${url}"`);
await execFileAsync("open", [url]);

await execAsync(`open "${url}"`);
} catch (error) {
// Fallback: show error
await showToast({
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor UX thing: openDeepLink shows a failure toast and re-throws, and the commands also show a failure toast in their own catch blocks. That can lead to double-toasts on failure; might be cleaner to let callers own the toast and have openDeepLink just throw.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 files reviewed, 14 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +1 to +230
import { listen } from "@tauri-apps/api/event";
import { onOpenUrl } from "@tauri-apps/plugin-deep-link";
import { commands } from "./tauri";
import type { RecordingMode, StartRecordingInputs } from "./tauri";

/**
* Deep link command handlers for Cap
* Supports: cap://record, cap://stop, cap://pause, cap://resume,
* cap://toggle-pause, cap://switch-mic, cap://switch-camera
*/

export interface DeepLinkCommand {
action: "record" | "stop" | "pause" | "resume" | "toggle-pause" | "switch-mic" | "switch-camera";
params?: Record<string, string>;
}

/**
* Parse deep link URL and extract command
*/
export function parseDeepLinkCommand(url: string): DeepLinkCommand | null {
try {
const urlObj = new URL(url);

// Only handle cap:// protocol
if (urlObj.protocol !== "cap:") {
return null;
}

const action = urlObj.hostname as DeepLinkCommand["action"];
const params: Record<string, string> = {};

urlObj.searchParams.forEach((value, key) => {
params[key] = value;
});

// Validate action
const validActions: DeepLinkCommand["action"][] = [
"record", "stop", "pause", "resume", "toggle-pause", "switch-mic", "switch-camera"
];

if (!validActions.includes(action)) {
console.warn(`Unknown deep link action: ${action}`);
return null;
}

return { action, params };
} catch (error) {
console.error("Failed to parse deep link:", error);
return null;
}
}

/**
* Execute deep link command
*/
export async function executeDeepLinkCommand(command: DeepLinkCommand): Promise<void> {
const { action, params = {} } = command;

console.log(`Executing deep link command: ${action}`, params);

switch (action) {
case "record":
await handleRecordCommand(params);
break;
case "stop":
await handleStopCommand();
break;
case "pause":
await handlePauseCommand();
break;
case "resume":
await handleResumeCommand();
break;
case "toggle-pause":
await handleTogglePauseCommand();
break;
case "switch-mic":
await handleSwitchMicCommand(params);
break;
case "switch-camera":
await handleSwitchCameraCommand(params);
break;
default:
console.warn(`Unhandled deep link action: ${action}`);
}
}

/**
* Handle record command
* Params: mode ("instant" | "studio"), camera?, microphone?
*/
async function handleRecordCommand(params: Record<string, string>): Promise<void> {
const mode = (params.mode as RecordingMode) || "instant";

// Set recording mode
await commands.setRecordingMode(mode);

const inputs: StartRecordingInputs = {
mode,
capture_target: params.target || "screen",
};

// Add camera if specified
if (params.camera) {
inputs.camera_label = params.camera;
}

// Add microphone if specified
if (params.microphone) {
inputs.audio_inputs = [{ label: params.microphone, device_type: "mic" }];
}

const result = await commands.startRecording(inputs);

if (result !== "Started") {
console.error(`Failed to start recording: ${result}`);
}
}

/**
* Handle stop command
*/
async function handleStopCommand(): Promise<void> {
await commands.stopRecording();
}

/**
* Handle pause command
*/
async function handlePauseCommand(): Promise<void> {
await commands.pauseRecording();
}

/**
* Handle resume command
*/
async function handleResumeCommand(): Promise<void> {
await commands.resumeRecording();
}

/**
* Handle toggle pause command
*/
async function handleTogglePauseCommand(): Promise<void> {
await commands.togglePauseRecording();
}

/**
* Handle switch microphone command
* Params: label (microphone name/device ID)
*/
async function handleSwitchMicCommand(params: Record<string, string>): Promise<void> {
const label = params.label || params.device;

if (!label) {
console.error("No microphone label provided");
return;
}

await commands.setMicInput(label);
}

/**
* Handle switch camera command
* Params: id (camera device ID)
*/
async function handleSwitchCameraCommand(params: Record<string, string>): Promise<void> {
const id = params.id || params.device;

if (!id) {
console.error("No camera ID provided");
return;
}

await commands.setCameraInput(id, true);
}

/**
* Initialize deep link command listener
* Returns unsubscribe function
*/
export async function initDeepLinkCommands(): Promise<() => void> {
console.log("Initializing deep link commands...");

const unsubscribe = await onOpenUrl(async (urls) => {
for (const url of urls) {
const command = parseDeepLinkCommand(url);

if (command) {
try {
await executeDeepLinkCommand(command);
} catch (error) {
console.error(`Failed to execute command from ${url}:`, error);
}
}
}
});

return unsubscribe;
}

/**
* Generate deep link URL for a command
*/
export function generateDeepLink(
action: DeepLinkCommand["action"],
params?: Record<string, string>
): string {
const url = new URL(`cap://${action}`);

if (params) {
Object.entries(params).forEach(([key, value]) => {
url.searchParams.set(key, value);
});
}

return url.toString();
}

// Export convenience functions for generating deep links
export const deepLinks = {
record: (params?: { mode?: RecordingMode; camera?: string; microphone?: string }) =>
generateDeepLink("record", params as Record<string, string>),
stop: () => generateDeepLink("stop"),
pause: () => generateDeepLink("pause"),
resume: () => generateDeepLink("resume"),
togglePause: () => generateDeepLink("toggle-pause"),
switchMic: (label: string) => generateDeepLink("switch-mic", { label }),
switchCamera: (id: string) => generateDeepLink("switch-camera", { id }),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire file duplicates existing deep link functionality already implemented in apps/desktop/src-tauri/src/deeplink_actions.rs. The Rust implementation is already registered and handles deep links via the tauri_plugin_deep_link plugin (see apps/desktop/src-tauri/src/lib.rs:3344-3346). Adding a second TypeScript-based handler creates conflicting implementations and architectural inconsistency.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/utils/deep-link-commands.ts
Line: 1:230

Comment:
This entire file duplicates existing deep link functionality already implemented in `apps/desktop/src-tauri/src/deeplink_actions.rs`. The Rust implementation is already registered and handles deep links via the `tauri_plugin_deep_link` plugin (see `apps/desktop/src-tauri/src/lib.rs:3344-3346`). Adding a second TypeScript-based handler creates conflicting implementations and architectural inconsistency.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +23 to +26

// Only handle cap:// protocol
if (urlObj.protocol !== "cap:") {
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URL scheme cap: doesn't match the configured scheme in apps/desktop/src-tauri/tauri.conf.json:33 which is cap-desktop. Deep links will fail because the protocol is incorrect.

Suggested change
// Only handle cap:// protocol
if (urlObj.protocol !== "cap:") {
return null;
if (urlObj.protocol !== "cap-desktop:") {
return null;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/utils/deep-link-commands.ts
Line: 23:26

Comment:
The URL scheme `cap:` doesn't match the configured scheme in `apps/desktop/src-tauri/tauri.conf.json:33` which is `cap-desktop`. Deep links will fail because the protocol is incorrect.

```suggestion
    if (urlObj.protocol !== "cap-desktop:") {
      return null;
    }
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +28 to +33

const action = urlObj.hostname as DeepLinkCommand["action"];
const params: Record<string, string> = {};

urlObj.searchParams.forEach((value, key) => {
params[key] = value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parsing approach using URL hostname for actions conflicts with the existing deep link architecture. The existing Rust implementation expects URLs in format cap-desktop://action?value=<json> where actions are serialized as JSON (see deeplink_actions.rs:95-104). This implementation uses a completely different pattern that won't integrate with the existing system.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/utils/deep-link-commands.ts
Line: 28:33

Comment:
The parsing approach using URL hostname for actions conflicts with the existing deep link architecture. The existing Rust implementation expects URLs in format `cap-desktop://action?value=<json>` where actions are serialized as JSON (see `deeplink_actions.rs:95-104`). This implementation uses a completely different pattern that won't integrate with the existing system.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +97 to +99

const inputs: StartRecordingInputs = {
mode,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The StartRecordingInputs type expects a capture_target of type ScreenCaptureTarget (union of Display/Window with id), but this passes a string "screen" which will cause a type error. See the correct usage in deeplink_actions.rs:122-133.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/utils/deep-link-commands.ts
Line: 97:99

Comment:
The `StartRecordingInputs` type expects a `capture_target` of type `ScreenCaptureTarget` (union of Display/Window with id), but this passes a string `"screen"` which will cause a type error. See the correct usage in `deeplink_actions.rs:122-133`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +203 to +210
* Generate deep link URL for a command
*/
export function generateDeepLink(
action: DeepLinkCommand["action"],
params?: Record<string, string>
): string {
const url = new URL(`cap://${action}`);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generateDeepLink function generates URLs with the wrong scheme (cap://) instead of the configured cap-desktop:// scheme from tauri.conf.json.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/utils/deep-link-commands.ts
Line: 203:210

Comment:
The `generateDeepLink` function generates URLs with the wrong scheme (`cap://`) instead of the configured `cap-desktop://` scheme from `tauri.conf.json`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +14 to +23
useEffect(() => {
// In a real implementation, this would fetch from Cap
// For now, showing example cameras
setCameras([
{ id: "default", name: "Default Camera" },
{ id: "built-in", name: "Built-in Camera" },
{ id: "external", name: "External Camera" },
]);
setIsLoading(false);
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded camera list won't work. The Raycast extension should either dynamically fetch available cameras from Cap or instruct users on proper device IDs. The existing desktop app has commands.listCameras() that could be exposed via a query endpoint or the deep link could accept actual device IDs that users obtain from Cap's UI.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/raycast-cap/src/switch-camera.tsx
Line: 14:23

Comment:
Hardcoded camera list won't work. The Raycast extension should either dynamically fetch available cameras from Cap or instruct users on proper device IDs. The existing desktop app has `commands.listCameras()` that could be exposed via a query endpoint or the deep link could accept actual device IDs that users obtain from Cap's UI.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +14 to +23
useEffect(() => {
// In a real implementation, this would fetch from Cap
// For now, showing example microphones
setMicrophones([
{ id: "default", name: "Default Microphone" },
{ id: "built-in", name: "Built-in Microphone" },
{ id: "external", name: "External Microphone" },
]);
setIsLoading(false);
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded microphone list won't work. Real microphone device IDs/labels need to come from the system or Cap. Consider fetching actual devices dynamically or documenting how users should obtain valid microphone labels.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/raycast-cap/src/switch-microphone.tsx
Line: 14:23

Comment:
Hardcoded microphone list won't work. Real microphone device IDs/labels need to come from the system or Cap. Consider fetching actual devices dynamically or documenting how users should obtain valid microphone labels.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +23 to +31
This extension uses Cap's deep link protocol to control the app:

- `cap://record` - Start recording
- `cap://stop` - Stop recording
- `cap://pause` - Pause recording
- `cap://resume` - Resume recording
- `cap://toggle-pause` - Toggle pause state
- `cap://switch-mic?label=<id>` - Switch microphone
- `cap://switch-camera?id=<id>` - Switch camera
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation lists incorrect URL scheme. Cap uses cap-desktop:// (configured in apps/desktop/src-tauri/tauri.conf.json), not cap://. All examples need updating to use the correct scheme.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/raycast-cap/README.md
Line: 23:31

Comment:
Documentation lists incorrect URL scheme. Cap uses `cap-desktop://` (configured in `apps/desktop/src-tauri/tauri.conf.json`), not `cap://`. All examples need updating to use the correct scheme.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +6 to +9
/**
* Deep link command handlers for Cap
* Supports: cap://record, cap://stop, cap://pause, cap://resume,
* cap://toggle-pause, cap://switch-mic, cap://switch-camera
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove JSDoc comments. Per CLAUDE.md:363-368 and AGENTS.md:23, NO CODE COMMENTS are allowed in this codebase (//, /* */, or /** */). Code must be self-explanatory through naming, types, and structure.

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!

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/utils/deep-link-commands.ts
Line: 6:9

Comment:
Remove JSDoc comments. Per CLAUDE.md:363-368 and AGENTS.md:23, NO CODE COMMENTS are allowed in this codebase (`//`, `/* */`, or `/** */`). Code must be self-explanatory through naming, types, and structure.

<sub>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!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +20 to +49
export function parseDeepLinkCommand(url: string): DeepLinkCommand | null {
try {
const urlObj = new URL(url);

// Only handle cap:// protocol
if (urlObj.protocol !== "cap:") {
return null;
}

const action = urlObj.hostname as DeepLinkCommand["action"];
const params: Record<string, string> = {};

urlObj.searchParams.forEach((value, key) => {
params[key] = value;
});

// Validate action
const validActions: DeepLinkCommand["action"][] = [
"record", "stop", "pause", "resume", "toggle-pause", "switch-mic", "switch-camera"
];

if (!validActions.includes(action)) {
console.warn(`Unknown deep link action: ${action}`);
return null;
}

return { action, params };
} catch (error) {
console.error("Failed to parse deep link:", error);
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove all JSDoc comments (/** ... */). The codebase strictly prohibits code comments of any form per CLAUDE.md:363-368.

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!

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/utils/deep-link-commands.ts
Line: 20:49

Comment:
Remove all JSDoc comments (`/** ... */`). The codebase strictly prohibits code comments of any form per CLAUDE.md:363-368.

<sub>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!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bounty: Deeplinks support + Raycast Extension

1 participant