Skip to content

Conversation

@vladzima
Copy link

@vladzima vladzima commented Jan 9, 2026

Description

This extension simplifies changing bluetooth / built-in audio outputs (and connections as well) on the fly, allowing to quickly select an output or create a preset scenario, including setting the output to connect to / activate, volume, mute and also an input source. For each preset the deeplink is created. Some quality of life logic is here as well: recently used devices are at the top, pairings are saved etc.

The one drawback is that a full experience requires 2 libs to be installed with brew-switchaudio-osx and blueutil-while if user does not utilize bluetooth functions and only uses built-in devices, the second one will not be required for the extension to work. System permission is also required for Raycast to control bluetooth (in the same manner Toothpick Raycast extension does it). All this is presented to the user when needed in form of step-by-step setup guide in the extension itself.

Screencast

2min video.

Checklist

- feat: prepare for store - deeplinks, lazy deps, metadata
- Initial commit: AudioScenes Raycast extension
@raycastbot raycastbot added new extension Label for PRs with new extensions platform: macOS labels Jan 9, 2026
@raycastbot
Copy link
Collaborator

Congratulations on your new Raycast extension! 🚀

Due to our current reduced availability, the initial review may take up to 10-15 business days.

Once the PR is approved and merged, the extension will be available on our Store.

@vladzima vladzima marked this pull request as ready for review January 10, 2026 00:07
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 10, 2026

Greptile Overview

Greptile Summary

This PR introduces a new Raycast extension for managing Bluetooth audio devices and creating audio output presets (scenes). The extension allows users to quickly connect Bluetooth devices, switch audio outputs, and create preset scenarios with custom volume, input device, and app launching.

Key Features

  • Bluetooth Integration: Connects to paired Bluetooth devices and automatically maps them to audio outputs
  • Scene Presets: Create reusable configurations with deeplink support for keyboard shortcuts
  • Smart Caching: Remembers device-to-output mappings and recently used devices
  • Dependency Management: Progressive dependency checking (SwitchAudioSource required, blueutil optional for BT)

Architecture

The extension is well-structured with clear separation:

  • Two main commands: devices.tsx (quick connect) and scenes.tsx (preset management)
  • Library modules for audio, bluetooth, and system operations
  • Safe command execution using execFile to prevent shell injection
  • Proper React patterns with hooks and navigation

Issues Found

4 logic errors were identified where operations could fail silently:

  1. Critical: JSON.parse in bluetooth.ts lacks error handling - will crash if blueutil returns invalid JSON
  2. Missing error handling in 3 locations where setOutputByName could fail but success toasts are shown anyway

These issues would lead to crashes or misleading user feedback but are straightforward to fix.

Positive Observations

  • Excellent UX with empty states, loading indicators, and clear error messages
  • Good security practices: uses execFile with argument arrays (no shell injection risk)
  • Smart features: auto-matching devices to outputs, restore previous output on disconnect
  • Comprehensive documentation and metadata screenshots included
  • Proper TypeScript typing throughout

Confidence Score: 3/5

  • This PR has solid architecture and good practices, but contains 4 logic errors that need fixing before merge - one critical crash bug and three user-facing error handling issues.
  • Score reflects thorough code quality with proper security practices and React patterns, but reduced due to the critical JSON.parse crash bug and three missing error handlers that would show misleading success messages. All issues are straightforward to fix.
  • Pay close attention to src/lib/bluetooth.ts (critical crash bug), src/scenes.tsx line 331, and src/devices.tsx lines 122 and 141 (missing error handling).

Important Files Changed

File Analysis

Filename Score Overview
src/devices.tsx 3/5 Main command for connecting Bluetooth devices and switching audio outputs. Found 2 missing error handlers that could show misleading success messages.
src/scenes.tsx 3/5 Scene presets command with deeplink support. Found 1 missing error handler in toggle function that could mislead users.
src/lib/bluetooth.ts 3/5 Bluetooth device management utilities. Critical issue: JSON.parse without error handling will crash if blueutil returns invalid output.
src/lib/audio.ts 5/5 Audio output/input control using SwitchAudioSource and osascript. Well-implemented with proper input sanitization and safe command execution.
src/lib/exec.ts 5/5 Command execution wrapper with proper PATH setup for Homebrew binaries. Uses execFile for safe command execution without shell injection risks.
src/lib/dependencies.tsx 5/5 Dependency checking UI and logic. Smart caching prevents repeated checks. Clear user guidance for missing dependencies.

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.

4 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 10, 2026

Additional Comments (4)

src/lib/bluetooth.ts
the JSON.parse(out) call will crash the extension if blueutil returns invalid JSON or an error message. This should be wrapped in a try-catch block.

  let raw;
  try {
    raw = JSON.parse(out);
  } catch (e) {
    throw new Error(`Failed to parse blueutil output: ${e instanceof Error ? e.message : String(e)}`);
  }

src/scenes.tsx
the call to setOutputByName(restore) is not wrapped in error handling, but a success toast is shown immediately after. If the audio switch fails, the user will see a misleading success message.

          try {
            await setOutputByName(restore);
            await showToast({
              style: Toast.Style.Success,
              title: `Output: ${restore}`,
            });
          } catch (e) {
            await showToast({
              style: Toast.Style.Failure,
              title: "Failed to restore output",
              message: e instanceof Error ? e.message : String(e),
            });
          }

src/devices.tsx
the call to setOutputByName(autoMatched) is not wrapped in error handling, but a success toast is shown immediately after. If the audio switch fails, the user will see a misleading success message.

      try {
        await setOutputByName(autoMatched);
        await showToast({
          style: Toast.Style.Success,
          title: `Output: ${autoMatched}`,
        });
        revalidate();
        return;
      } catch {
        // Auto-match found a device but setting it failed, fall through to manual selection
      }

src/devices.tsx
the onSelect callback calls setOutputByName(outputName) without error handling, but shows a success toast immediately after. If the audio switch fails, the user will see a misleading success message.

        onSelect={async (outputName) => {
          // Save mapping for future
          await setOutputMap({ ...outputMap, [device.address]: outputName });
          try {
            await setOutputByName(outputName);
            await showToast({
              style: Toast.Style.Success,
              title: `Output: ${outputName}`,
            });
            revalidate();
          } catch (e) {
            await showToast({
              style: Toast.Style.Failure,
              title: "Failed to set output",
              message: e instanceof Error ? e.message : String(e),
            });
          }
        }}

@vladzima
Copy link
Author

Updated the PR based on issues found by greprtile.

@vladzima
Copy link
Author

Added a menubar command (btw Raycast supports svg for a menubar icon - as a pleasant surprise, as it's not clear from the docs).

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

Labels

new extension Label for PRs with new extensions platform: macOS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants