Skip to content

Conversation

@gyanu2507
Copy link

@gyanu2507 gyanu2507 commented Jan 31, 2026

/claim #1540

Description

This PR adds deeplink support for pause/resume recording and device switching, plus a Raycast extension to control Cap via these deeplinks.

Changes

Deeplinks Support

  • Added PauseRecording deeplink action
  • Added ResumeRecording deeplink action
  • Added SwitchMicrophone deeplink action with mic_label parameter
  • Added SwitchCamera deeplink action with camera_id parameter

All new actions follow the same pattern as existing StartRecording and StopRecording actions, using the existing Tauri commands.

Raycast Extension

  • Created new Raycast extension at apps/raycast-extension/
  • Added 6 commands: Start, Stop, Pause, Resume, Switch Microphone, Switch Camera
  • Each command uses the cap-desktop:// deeplink scheme to communicate with the app

Testing

Ready for testing. The implementation wires up existing functionality, so it should work with the current recording system.

Greptile Overview

Greptile Summary

This PR adds deeplink support for pause/resume recording and device switching, plus a new Raycast extension to control Cap via these deeplinks.

Key Changes:

  • Added 4 new DeepLinkAction variants (PauseRecording, ResumeRecording, SwitchMicrophone, SwitchCamera) that call existing Tauri commands
  • Created a Raycast extension with 6 commands for controlling Cap from the Raycast launcher
  • All deeplink actions follow the same pattern as existing StartRecording/StopRecording actions

Issues Found:

  • Missing icon.png file will cause the Raycast extension to fail loading
  • start-recording.ts hardcodes "Main Display" which will fail on systems with different display names
  • Camera/microphone switching commands have placeholder "not implemented" actions in the UI
  • switch-camera.tsx may pass incorrect format for DeviceOrModelID enum (needs {"device_id": "..."} or {"model_id": {...}} instead of plain string)

The Rust deeplink implementation is solid and correctly wires up existing functionality, but the Raycast extension needs polish before it's production-ready.

Confidence Score: 3/5

  • Deeplink backend is safe to merge but Raycast extension has blocking issues
  • The Rust deeplink implementation is well-structured and correctly uses existing commands, but the Raycast extension has 4 critical issues: missing icon file, hardcoded display name that will fail, placeholder "not implemented" actions, and potentially incorrect enum serialization for camera IDs
  • Pay close attention to apps/raycast-extension/src/start-recording.ts (hardcoded display name) and apps/raycast-extension/src/switch-camera.tsx (incorrect enum format)

Important Files Changed

Filename Overview
apps/desktop/src-tauri/src/deeplink_actions.rs Added 4 new deeplink actions (pause, resume, switch mic/camera) following existing pattern
apps/raycast-extension/package.json Raycast extension manifest with 6 commands, missing icon.png file
apps/raycast-extension/src/start-recording.ts Start recording command with hardcoded screen name
apps/raycast-extension/src/switch-camera.tsx Camera switching UI with placeholder implementation, shows not implemented toast
apps/raycast-extension/src/switch-microphone.tsx Microphone switching UI with placeholder implementation, shows not implemented toast

Sequence Diagram

sequenceDiagram
    participant User
    participant Raycast
    participant DeeplinkHandler as Cap Desktop<br/>Deeplink Handler
    participant DeeplinkAction as DeepLinkAction<br/>Executor
    participant RecordingModule as Recording Module<br/>(Rust)
    
    User->>Raycast: Trigger command<br/>(e.g., Pause Recording)
    Raycast->>Raycast: Build deeplink URL<br/>cap-desktop://action?value={...}
    Raycast->>DeeplinkHandler: Open deeplink URL
    DeeplinkHandler->>DeeplinkHandler: Parse URL to DeepLinkAction
    DeeplinkHandler->>DeeplinkAction: execute()
    
    alt PauseRecording
        DeeplinkAction->>RecordingModule: pause_recording()
    else ResumeRecording
        DeeplinkAction->>RecordingModule: resume_recording()
    else SwitchMicrophone
        DeeplinkAction->>RecordingModule: set_mic_input(mic_label)
    else SwitchCamera
        DeeplinkAction->>RecordingModule: set_camera_input(camera_id)
    end
    
    RecordingModule-->>DeeplinkAction: Result<(), String>
    DeeplinkAction-->>DeeplinkHandler: Result
    DeeplinkHandler-->>Raycast: Success/Error
    Raycast-->>User: Show toast notification
Loading

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

…aycast extension

- Add PauseRecording deeplink action
- Add ResumeRecording deeplink action
- Add SwitchMicrophone deeplink action with mic_label parameter
- Add SwitchCamera deeplink action with camera_id parameter
- Create Raycast extension with commands for all recording controls
- Add commands: start, stop, pause, resume, switch microphone, switch camera

Fixes CapSoftware#1540
export default async function Command() {
const action = {
start_recording: {
capture_mode: { screen: "Main Display" },
Copy link

Choose a reason for hiding this comment

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

Hardcoding "Main Display" seems brittle (the app looks up displays by name and will error if it doesn't match). Might be worth picking a safer default or making this configurable in the command.

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, 5 comments

Edit Code Review Agent Settings | Greptile

gyanu2507 and others added 5 commits January 31, 2026 10:02
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
gyanu2507 and others added 9 commits January 31, 2026 10:23
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
}
}

export default async function Command() {
Copy link

Choose a reason for hiding this comment

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

This file exports default twice (lines 3 and 20), which will fail TypeScript compilation. Dropping the second export default block should fix it.

gyanu2507 and others added 9 commits January 31, 2026 10:33
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
gyanu2507 and others added 2 commits January 31, 2026 10:35
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
- Add enable_deeplink_actions setting (default: false) to gate sensitive deeplink operations
- Require opt-in permission for recording control actions (start, stop, pause, resume, switch devices)
- Allow OpenEditor and OpenSettings without permission (less sensitive)
- Add UI toggle in Settings → General → App section
- Document security implications in Raycast extension README

This addresses the security concern that any app/website could trigger recording state changes via URL schemes. Users must explicitly enable this feature in settings.
Comment on lines 1 to 25
import { open, showToast, Toast } from "@raycast/api";

export default async function Command() {
const action = { pause_recording: null };

const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;

try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Recording paused" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to pause recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}

export default async function Command() {
const action = { pause_recording: null };

const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
}
Copy link

Choose a reason for hiding this comment

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

This file currently has two export default functions; TypeScript will error. Removing the second copy should fix it.

Suggested change
import { open, showToast, Toast } from "@raycast/api";
export default async function Command() {
const action = { pause_recording: null };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Recording paused" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to pause recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}
export default async function Command() {
const action = { pause_recording: null };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
}
import { open, showToast, Toast } from "@raycast/api";
export default async function Command() {
const action = { pause_recording: null };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Recording paused" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to pause recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}

Comment on lines 1 to 25
import { open, showToast, Toast } from "@raycast/api";

export default async function Command() {
const action = { resume_recording: null };

const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;

try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Recording resumed" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to resume recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}

export default async function Command() {
const action = { resume_recording: null };

const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
}
Copy link

Choose a reason for hiding this comment

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

Same here: there are two export default functions, so this won’t compile. Dropping the second copy should fix it.

Suggested change
import { open, showToast, Toast } from "@raycast/api";
export default async function Command() {
const action = { resume_recording: null };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Recording resumed" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to resume recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}
export default async function Command() {
const action = { resume_recording: null };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
}
import { open, showToast, Toast } from "@raycast/api";
export default async function Command() {
const action = { resume_recording: null };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Recording resumed" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to resume recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}

Comment on lines 28 to 41
export default async function Command() {
const action = {
start_recording: {
capture_mode: { screen: "Main Display" },
camera: null,
mic_label: null,
capture_system_audio: false,
mode: "studio",
},
};

const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
}
Copy link

Choose a reason for hiding this comment

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

There are two export default async function Command() blocks in this file; the second one overrides the intended behavior and will break TypeScript. I'd remove the duplicate block.

Suggested change
export default async function Command() {
const action = {
start_recording: {
capture_mode: { screen: "Main Display" },
camera: null,
mic_label: null,
capture_system_audio: false,
mode: "studio",
},
};
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
}

export default async function Command() {
const action = {
start_recording: {
capture_mode: { screen: "Main Display" },
Copy link

Choose a reason for hiding this comment

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

Minor: hardcoding "Main Display" will fail on many machines because Cap matches by exact display name. Might be worth switching this command to mode: "view" and letting the user pick/enter the screen name (or at least documenting that it may need editing).

Comment on lines +23 to +26
```bash
cd apps/raycast-extension
pnpm install
ray dev
Copy link

Choose a reason for hiding this comment

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

The fenced code block here is missing the closing backticks.

Suggested change
```bash
cd apps/raycast-extension
pnpm install
ray dev
```bash
cd apps/raycast-extension
pnpm install
ray dev

}
}

export default async function Command() {
Copy link

Choose a reason for hiding this comment

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

Looks like this file accidentally includes two export default async function Command() blocks. TypeScript will error on duplicate default exports; I'd drop the second one (starting at line 28) and keep the try/catch + toast version.

}
}

export default async function Command() {
Copy link

Choose a reason for hiding this comment

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

Same issue here: there are two export default async function Command() blocks in one file. Dropping the second export keeps this as a valid no-view command and avoids a duplicate-default-export error.

});
}
}
import { open, showToast, Toast } from "@raycast/api";
Copy link

Choose a reason for hiding this comment

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

This file currently has a duplicated import and multiple export default command definitions. I’d trim it down to a single command export (keeping the toast/try-catch version) so it compiles and behaves consistently.

Comment on lines +117 to +118
// Check if deeplink actions are enabled for sensitive operations
let requires_permission = matches!(
Copy link

Choose a reason for hiding this comment

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

Repo policy is no inline code comments; can we drop this one?

Suggested change
// Check if deeplink actions are enabled for sensitive operations
let requires_permission = matches!(
let requires_permission = matches!(

}
}

export default async function Command() {
Copy link

Choose a reason for hiding this comment

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

This file has two export default commands now, which will fail to compile in Raycast. Suggest collapsing to a single implementation.

Suggested change
export default async function Command() {
import { open, showToast, Toast } from "@raycast/api";
export default async function Command() {
const action = { pause_recording: null };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Recording paused" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to pause recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}

gyanu2507 and others added 4 commits January 31, 2026 12:41
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
gyanu2507 and others added 2 commits January 31, 2026 16:06
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>

impl DeepLinkAction {
pub async fn execute(self, app: &AppHandle) -> Result<(), String> {
// Check if deeplink actions are enabled for sensitive operations
Copy link

Choose a reason for hiding this comment

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

Repo-wide nit: we try to keep the codebase comment-free. This line can probably go since requires_permission already explains intent.

Comment on lines +1 to +35
import { open, showToast, Toast } from "@raycast/api";

export default async function Command() {
const action = { resume_recording: null };

const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;

try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Recording resumed" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to resume recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}
import { open, showToast, Toast } from "@raycast/api";

export default async function Command() {
const action = { resume_recording: null };

const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;

try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Recording resumed" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to resume recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
Copy link

Choose a reason for hiding this comment

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

Looks like a copy/paste duplication slipped in here (second import + second Command()). Suggest trimming it back to a single implementation.

Suggested change
import { open, showToast, Toast } from "@raycast/api";
export default async function Command() {
const action = { resume_recording: null };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Recording resumed" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to resume recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}
import { open, showToast, Toast } from "@raycast/api";
export default async function Command() {
const action = { resume_recording: null };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Recording resumed" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to resume recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
import { open, showToast, Toast } from "@raycast/api";
export default async function Command() {
const action = { resume_recording: null };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Resume command sent" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to resume recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}

Comment on lines +1 to +66
import { open, showToast, Toast } from "@raycast/api";

export default async function Command() {
const action = {
start_recording: {
capture_mode: { screen: "Main Display" },
camera: null,
mic_label: null,
capture_system_audio: false,
mode: "studio",
},
};

const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;

try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Started recording" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to start recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}

import { open, showToast, Toast } from "@raycast/api";

export default async function Command() {
const action = {
start_recording: {
capture_mode: { screen: "Main Display" },
camera: null,
mic_label: null,
capture_system_audio: false,
mode: "studio",
},
};

const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;

try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Started recording" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to start recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}
const action = {
start_recording: {
capture_mode: { screen: "Main Display" },
camera: null,
mic_label: null,
capture_system_audio: false,
mode: "studio",
},
};

const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
}
Copy link

Choose a reason for hiding this comment

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

This file currently has duplicated blocks + stray statements at the end, so it won't typecheck. Also, since we can't confirm Cap executed the action, the success toast is a bit optimistic.

Suggested change
import { open, showToast, Toast } from "@raycast/api";
export default async function Command() {
const action = {
start_recording: {
capture_mode: { screen: "Main Display" },
camera: null,
mic_label: null,
capture_system_audio: false,
mode: "studio",
},
};
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Started recording" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to start recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}
import { open, showToast, Toast } from "@raycast/api";
export default async function Command() {
const action = {
start_recording: {
capture_mode: { screen: "Main Display" },
camera: null,
mic_label: null,
capture_system_audio: false,
mode: "studio",
},
};
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Started recording" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to start recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}
const action = {
start_recording: {
capture_mode: { screen: "Main Display" },
camera: null,
mic_label: null,
capture_system_audio: false,
mode: "studio",
},
};
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
}
import { open, showToast, Toast } from "@raycast/api";
export default async function Command() {
const action = {
start_recording: {
capture_mode: { screen: "Main Display" },
camera: null,
mic_label: null,
capture_system_audio: false,
mode: "studio",
},
};
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(url);
await showToast({ style: Toast.Style.Success, title: "Start command sent" });
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to start recording",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}

Minor: hardcoding "Main Display" is pretty brittle if display names differ; a small view command to let users pick/enter the name might be safer.

Comment on lines +1 to +95
import { List, ActionPanel, Action, showToast, Toast, open } from "@raycast/api";

export default function Command() {
const isLoading = false;


const handleSwitchCamera = async (cameraId: string | null) => {
try {
// Camera ID can be either a model string or a device ID object
// For simplicity, we'll use model string format
const action = {
switch_camera: {
const action = {
switch_camera: {
camera_id: cameraId ? { DeviceID: cameraId } : null,
},
};
},
};
import { Action, ActionPanel, Form, List, Toast, open, showToast } from "@raycast/api";
import { useState } from "react";

async function switchCamera(cameraId: string | null) {
const action = {
switch_camera: {
camera_id: cameraId ? { DeviceID: cameraId } : null,
},
};

const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;

try {
await open(url);
await showToast({
style: Toast.Style.Success,
title: "Camera switched",
message: cameraId ? `Switched to ${cameraId}` : "Camera disabled",
});
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to switch camera",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}

function CameraIdForm() {
const [cameraId, setCameraId] = useState("");

return (
<Form
actions={
<ActionPanel>
<Action.SubmitForm
title="Switch Camera"
onSubmit={() => switchCamera(cameraId.trim() || null)}
/>
</ActionPanel>
}
>
<Form.TextField
id="cameraId"
title="Camera Device ID"
value={cameraId}
onChange={setCameraId}
/>
</Form>
);
}

export default function Command() {
return (
<List searchBarPlaceholder="Camera...">
<List.Item
title="Disable Camera"
subtitle="Turn off camera input"
actions={
<ActionPanel>
<Action title="Disable Camera" onAction={() => switchCamera(null)} />
</ActionPanel>
}
/>
<List.Item
title="Switch Camera"
subtitle="Enter a camera device ID"
actions={
<ActionPanel>
<Action.Push title="Enter Camera ID" target={<CameraIdForm />} />
</ActionPanel>
}
/>
</List>
);
}
Copy link

Choose a reason for hiding this comment

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

This looks like an unfinished merge/edit (duplicate imports/exports + partial function body) and currently won't compile. Also, we try to keep the repo comment-free.

Suggested change
import { List, ActionPanel, Action, showToast, Toast, open } from "@raycast/api";
export default function Command() {
const isLoading = false;
const handleSwitchCamera = async (cameraId: string | null) => {
try {
// Camera ID can be either a model string or a device ID object
// For simplicity, we'll use model string format
const action = {
switch_camera: {
const action = {
switch_camera: {
camera_id: cameraId ? { DeviceID: cameraId } : null,
},
};
},
};
import { Action, ActionPanel, Form, List, Toast, open, showToast } from "@raycast/api";
import { useState } from "react";
async function switchCamera(cameraId: string | null) {
const action = {
switch_camera: {
camera_id: cameraId ? { DeviceID: cameraId } : null,
},
};
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(url);
await showToast({
style: Toast.Style.Success,
title: "Camera switched",
message: cameraId ? `Switched to ${cameraId}` : "Camera disabled",
});
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to switch camera",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}
function CameraIdForm() {
const [cameraId, setCameraId] = useState("");
return (
<Form
actions={
<ActionPanel>
<Action.SubmitForm
title="Switch Camera"
onSubmit={() => switchCamera(cameraId.trim() || null)}
/>
</ActionPanel>
}
>
<Form.TextField
id="cameraId"
title="Camera Device ID"
value={cameraId}
onChange={setCameraId}
/>
</Form>
);
}
export default function Command() {
return (
<List searchBarPlaceholder="Camera...">
<List.Item
title="Disable Camera"
subtitle="Turn off camera input"
actions={
<ActionPanel>
<Action title="Disable Camera" onAction={() => switchCamera(null)} />
</ActionPanel>
}
/>
<List.Item
title="Switch Camera"
subtitle="Enter a camera device ID"
actions={
<ActionPanel>
<Action.Push title="Enter Camera ID" target={<CameraIdForm />} />
</ActionPanel>
}
/>
</List>
);
}
import { Action, ActionPanel, Form, List, Toast, open, showToast } from "@raycast/api";
import { useState } from "react";
function buildActionUrl(action: unknown) {
return `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
}
async function switchCamera(cameraId: string | null) {
const action = {
switch_camera: {
camera_id: cameraId ? { DeviceID: cameraId } : null,
},
};
try {
await open(buildActionUrl(action));
await showToast({
style: Toast.Style.Success,
title: "Camera switch command sent",
message: cameraId ?? "Disable camera",
});
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to switch camera",
message: error instanceof Error ? error.message : "Unknown error",
});
}
}
function CameraIdForm() {
const [cameraId, setCameraId] = useState("");
return (
<Form
actions={
<ActionPanel>
<Action.SubmitForm title="Switch Camera" onSubmit={() => switchCamera(cameraId.trim() || null)} />
</ActionPanel>
}
>
<Form.TextField id="cameraId" title="Camera Device ID" value={cameraId} onChange={setCameraId} />
</Form>
);
}
export default function Command() {
return (
<List searchBarPlaceholder="Camera...">
<List.Item
title="Disable Camera"
subtitle="Turn off camera input"
actions={
<ActionPanel>
<Action title="Disable Camera" onAction={() => switchCamera(null)} />
</ActionPanel>
}
/>
<List.Item
title="Switch Camera"
subtitle="Enter a camera device ID"
actions={
<ActionPanel>
<Action.Push title="Enter Camera ID" target={<CameraIdForm />} />
</ActionPanel>
}
/>
</List>
);
}

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant