Exploring Windsurf: Creating a New Feature with Cascade

Cascade is a powerful reasoning engine capable of deep, multi-step deliberation, equipped to both edit and explain. This is the definition of Windsurf's Cascade feature, and it can be toggled using Command + L in the editor.

In the previous post, I provided a glimpse of how this agentic IDE works:

Exploring Windsurf: Starting with Agentic IDE for iOS Development
Embrace agentic coding workflow for iOS app development. Learn how I created Whispering, a macOS speech-to-text app using OpenAI’s Whisper, leveraging Windsurf, the agentic IDE from Codeium.

In this post, I will create a new feature for my app Whispering using Cascade and Groq's API for transcription. This way, people who still use Intel machines or prefer extremely fast transcription can utilize it instead of on-device.

Running macOS App via Windsurf

As I mentioned before, I am adamant that I want to run the project via xcodebuild only in the chat window. I started with the prompt:

Run the project using xcodebuild and xcrun

And it provided me with the command to build the project:

xcodebuild -scheme Whispering -configuration Debug -destination platform=macOS

The build succeeded, it proceeded to launch the built app from the Debug directory:

$ xcrun launch-app /Users/rudrankriyam/Library/Developer/Xcode/DerivedData/Whispering-fxqsevpvezgelucnqetechkyhroh/Build/Products/Debug/Whispering.app

However, there was an error:

xcrun: error: sh -c '/Applications/Xcode-beta.app/Contents/Developer/usr/bin/xcodebuild -sdk /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -find launch-app 2> /dev/null' failed with exit code 17664: (null) (errno=No such file or directory)
xcrun: error: unable to find utility "launch-app", not a developer tool or in PATH

Unfortunately, it still insisted that it initiated the launch of the Whispering app. I copied the error from above, and here is the following response:

I apologize for the error. Let's try running the app directly using the open command instead, which is the standard way to launch macOS applications.

$ open /Users/rudrankriyam/Library/Developer/Xcode/DerivedData/Whispering-fxqsevpvezgelucnqetechkyhroh/Build/Products/Debug/Whispering.app

It looks like I got ahead of myself. I did not know that open command is the standard way to launch macOS apps.

Windsurf 1:0 Rudrank.

The best part about accessing the terminal inside the chat itself is that you can provide it with the additional prompt:

Build the project using xcodebuild every time you make changes, and fix any issues or errors if there any.

Accessing External Documentation

As of writing this post, Windsurf cannot access the internet. It is one of the most requested features. Looking at their feature request page:

Feature Requests | Codeium
Give feedback to the Codeium team so we can make more informed product decisions. Powered by Canny.

I will use my favorite service that I found this year, Firecrawl, which turns websites into LLM-ready data. I provided it with the documentation for transcription from Groq and gave Cascade the following prompt:

----

Update the @TranscriptionService.swift with methods for transcription with the given audio file. Use latest async/await Swift syntax and use a keychain library to fetch the API key.

It updated the TranscriptionService.swift file to implement transcription using the Groq API. Then, it added the popular KeychainAccess library for secure API key storage and updated the Package.swift file:

swift package update

It added almost perfect code for the implementation:

// MARK: - Groq API Constants
private enum GroqAPI {
    static let baseURL = "https://api.groq.com/openai/v1/audio/transcriptions"
    static let keychainService = "com.whispering.apikeys"
    static let keychainKey = "groq_api_key"
}

// MARK: - Transcription Models
enum TranscriptionModel: String {
    case whisperLargeV3 = "whisper-large-v3"
    case whisperLargeV3Turbo = "whisper-large-v3-turbo"
    case distilWhisperLargeV3En = "distil-whisper-large-v3-en"
}

enum TranscriptionMode {
    case offline
    case online
}

// MARK: - Transcription Response
struct TranscriptionResponse: Decodable {
    let text: String
}

@MainActor
class TranscriptionService: ObservableObject {
    // MARK: - Published Properties
    @Published var selectedTranscriptionModel: TranscriptionModel = .whisperLargeV3Turbo
    @Published var transcriptionMode: TranscriptionMode = .offline

    private let keychain = Keychain(service: GroqAPI.keychainService)

But, it ended up updating the method that is used for offline transcription. So, I had to ask it again:

Create another method. The current transcribe method is for offline.

So, as you know, it was my poor prompting skill in this case. Or Elden Ring fans like to call it a skill issue.

Updating the View to Add API Key

The next step is to create a UI in the Settings window that can hold the API key and also verify it.

I LOVE reading through how it follows the instructions. Here is a part of it:

  1. I'll help you create a settings window with UI elements to manage and verify the API key. First, let me check the existing codebase structure.
  2. Let me check what's in the Views directory since that's where we'll likely want to add our Settings view.
  3. I see there's already a SettingsView.swift file. Let me check its current contents to see how we can modify it to add the API key management functionality.
  4. Let me also check the TranscriptionService to understand how the API key is currently being used.
  5. Great! I see that the app is already using KeychainAccess for storing the API key securely. I'll now modify the SettingsView to add API key management functionality. I'll add:
    1. A section for API key management
    2. A secure text field for entering the API key
    3. A verify button to test the API key
    4. Proper error handling and success feedback

Then, it made the necessary changes.

The API key is now stored securely in the Keychain and can be verified against the Groq API. When verifying, we check the API response status code:

  • 401: Invalid API key
  • 200/400: Valid API key (400 is acceptable as we're not sending audio data)
  • Other codes: Treated as verification failures

The UI provides clear feedback about the status of operations through color-coded messages (green for success, red for errors) and a loading indicator during verification.

After successfully building the project and adding the API key, it was verified successfully!

Toggle to Switch Between Offline

The next step is to add a toggle between API transcription and offline. I can start a new conversation by pressing Shift + Command + L.

Cascade does some analysis and provides the following output:

I'll add a toggle in the menu bar to switch between API and offline transcription. I'll make the following changes:

Add a toggle in the ContentView
Ensure the TranscriptionService properly handles the mode switch

And with that, I have an API feature for those who want blazing-fast speed!

Moving Forward

While I can feel the friction of waiting for the flow of Cascade while it generates and analyses files, I hope it gets faster as Claude releases a faster model in the future.

One tip I got from Ronak is that if speed is the primary concern, explore switching the model from Sonnet to Cascade Base. It won't be as methodical of an agent, but it should be much faster!

Happy windsurfing!