Skip to content

Conversation

adityash8
Copy link
Owner

Summary

  • Implement MAS updates detection using mas command line tool
  • Add deep link integration for App Store updates page
  • Include MAS app categorization and receipt validation
  • Support automatic mas CLI installation via Homebrew
  • Add App Store ID extraction from app bundles

Test plan

  • Test MAS app detection via receipt validation
  • Verify deep link opens App Store updates page
  • Check mas CLI installation and update detection
  • Test app categorization (MAS vs non-MAS)

🤖 Generated with Claude Code

adityash8 and others added 5 commits September 21, 2025 20:04
- Add Anchoring + Decoy pricing in Pro tab ($3.99 → $2.99, decoy option)
- Implement Loss Aversion for security warnings ("Don't risk unpatched apps")
- Add Social Proof elements (user count, testimonials)
- Create Zeigarnik Effect progress indicators with completion tension
- Set smart defaults (auto-update ON) using Default Effect
- Enhance error states with Actor-Observer bias (situational framing)
- Add Authority signals (industry-standard verification)
- Improve Privacy tab with Reciprocity bias ("helps improve reliability")

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add SecurityChecks for codesign verification and quarantine handling
- Create Installer for ZIP/DMG/PKG with automatic backup before install
- Implement Rollback system for one-click version restoration
- Include cleanup utilities for managing backup storage
- All installs verify signatures before replacing apps

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Implement SparkleHints for SUFeedURL discovery from app bundles
- Add GitHub API client for releases with rate limiting and auth
- Create RepoHints mapping for 30+ popular apps to GitHub repos
- Add Brew cask integration for Homebrew-managed apps
- Include repository validation and fallback discovery patterns

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Replace BGTaskScheduler with macOS LaunchAgent for better reliability
- Add install/uninstall functionality with proper plist generation
- Include status checking and schedule updating capabilities
- Configure for low-priority background execution
- Handle loading/unloading with proper error handling

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Implement MAS updates detection using mas command line tool
- Add deep link integration for App Store updates page
- Include MAS app categorization and receipt validation
- Support automatic mas CLI installation via Homebrew
- Add App Store ID extraction from app bundles

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@gemini-code-assist
Copy link

Summary of Changes

Hello @adityash8, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the application's capabilities by integrating with the Mac App Store for updates, enhancing app installation and management, and introducing robust background update mechanisms. It also refines the user interface with improved feedback and introduces telemetry for better product development and user experience.

Highlights

  • Mac App Store Integration: Introduced comprehensive support for Mac App Store applications, including update detection using the 'mas' command-line tool, deep linking to the App Store updates page, app categorization, and receipt validation. It also includes automatic installation of the 'mas' CLI via Homebrew.
  • Enhanced App Management Core: Added new core functionalities for managing applications, such as a robust installer for ZIP, DMG, and PKG files, backup and rollback capabilities, and security checks for code signing and quarantine attributes. Utilities for version comparison and Sparkle update detection were also implemented.
  • GitHub Release Integration: Implemented a new module to interact with the GitHub API for fetching release information, allowing the application to track updates for apps distributed via GitHub. A curated list of bundle IDs to GitHub repositories helps in discovering relevant update sources.
  • Background Update Scheduling: A new LaunchAgent service has been added to enable scheduled background tasks, facilitating automatic update checks and installations without requiring the main application to be constantly running.
  • Improved User Experience and Telemetry: The user interface has been refined with more engaging update status messages, animated scanning progress indicators, and social proof elements. Anonymous telemetry (via PostHog) was integrated to track usage and improve reliability, with clear privacy disclosures.
  • Updated Minimum macOS Version: The minimum macOS deployment target has been updated to 13.3 in the Package.swift file.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a substantial amount of new functionality, including Mac App Store integration, update detection via various sources, and core utilities for installation and security. The code is generally well-structured. My review focuses on improving robustness, correctness, and maintainability. I've identified several areas for improvement, such as using safer methods for URL construction and command-line tool interaction, ensuring atomic file operations during installation, and addressing a few logical bugs in update and backup detection. I've also pointed out opportunities to reduce code duplication and adhere more closely to Swift idioms. Overall, this is a great addition, and addressing these points will make the new features more reliable and easier to maintain.

}

static func latest(owner: String, repo: String, token: String? = nil) async throws -> Release {
var request = URLRequest(url: URL(string: "https://api.github.com/repos/\(owner)/\(repo)/releases/latest")!)

Choose a reason for hiding this comment

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

critical

Force-unwrapping the URL with URL(string: ...)! is unsafe and can cause a runtime crash if the owner or repo strings contain characters that are invalid in a URL. You should use URLComponents to construct the URL safely, which will handle proper encoding and prevent crashes.

Suggested change
var request = URLRequest(url: URL(string: "https://api.github.com/repos/\(owner)/\(repo)/releases/latest")!)
guard var urlComponents = URLComponents(string: "https://api.github.com/repos/\(owner)/\(repo)/releases/latest") else {
throw GitHubError.invalidRepo
}
var request = URLRequest(url: urlComponents.url!)

let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/bin/codesign")
task.arguments = ["--verify", "--deep", "--strict", appPath]
try? task.run()

Choose a reason for hiding this comment

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

high

Using try? task.run() silently ignores potential errors during process launch (e.g., if the codesign binary is not found or there are permission issues). This is dangerous for a security-critical function. The function should be marked as throws and you should use try task.run() to allow callers to handle any errors. This applies to getQuarantineStatus and removeQuarantine as well.

Suggested change
try? task.run()
try task.run()

Comment on lines +141 to +156
for line in lines {
let components = line.components(separatedBy: .whitespaces)
if components.count >= 2 {
let appID = components[0]
let name = components[1...].joined(separator: " ")

let appInfo = AppInfo(
name: name,
bundleIdentifier: "mas.\(appID)",
version: "unknown",
path: nil,
iconPath: nil
)
apps.append(appInfo)
}
}

Choose a reason for hiding this comment

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

high

The parsing logic for mas outdated is incorrect. The output of mas outdated includes the version information (e.g., AppName (1.0 -> 2.0)). The current code joins all components after the ID, which incorrectly includes the version string in the app's name. You need to parse the line more carefully to extract only the app name.

        for line in lines where !line.isEmpty {
            let components = line.split(separator: " ")
            guard components.count >= 2 else { continue }

            let appID = String(components[0])
            // Find where the version string starts
            let versionStartIndex = line.range(of: " (")?.lowerBound ?? line.endIndex
            let namePart = line[line.index(after: line.firstIndex(of: " ") ?? line.startIndex)..<versionStartIndex]
            let name = String(namePart).trimmingCharacters(in: .whitespaces)

            if !name.isEmpty {
                let appInfo = AppInfo(
                    name: name,
                    bundleIdentifier: "mas.\(appID)",
                    version: "unknown",
                    path: nil,
                    iconPath: nil
                )
                apps.append(appInfo)
            }

Comment on lines +4 to +6
static func caskIsOutdated(_ cask: String) -> Bool {
let command = "brew outdated --cask --greedy --quiet | grep -x \(shellQuote(cask))"
return run(command).exitCode == 0

Choose a reason for hiding this comment

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

high

This implementation is inefficient as it fetches all outdated casks and then uses grep to find a match. A more robust and efficient approach would be to use brew outdated --cask --json, parse the JSON output, and check if the target cask is present in the resulting list. This avoids the overhead of grep and is less prone to breaking if the command's text output format changes.

    static func caskIsOutdated(_ cask: String) -> Bool {
        let result = run("brew outdated --cask --json")
        guard result.exitCode == 0, let data = result.output.data(using: .utf8) else {
            return false
        }

        struct OutdatedCasks: Decodable {
            let casks: [CaskInfo]
        }
        struct CaskInfo: Decodable {
            let name: String
        }

        if let outdated = try? JSONDecoder().decode(OutdatedCasks.self, from: data) {
            return outdated.casks.contains { $0.name == cask }
        }

        return false
    }

// Create backup first
let currentAppPath = "/Applications/\(name).app"
if FileManager.default.fileExists(atPath: currentAppPath) {
_ = try? SecurityChecks.backup(appPath: currentAppPath, bundleID: bundleID, version: currentVersion)

Choose a reason for hiding this comment

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

high

Silently ignoring a backup failure with try? is risky. The backup mechanism is critical for the rollback feature. If a backup fails, the installation should be aborted to prevent leaving the system in a state where rollback is not possible. You should handle the error explicitly. This also applies to the backup logic in installDMG.

Suggested change
_ = try? SecurityChecks.backup(appPath: currentAppPath, bundleID: bundleID, version: currentVersion)
try SecurityChecks.backup(appPath: currentAppPath, bundleID: bundleID, version: currentVersion)

throw GitHubError.apiError(httpResponse.statusCode)
}

return try JSONDecoder().decode(Release.self, from: data)

Choose a reason for hiding this comment

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

medium

To make your Decodable structs more idiomatic in Swift, you should use camelCase for property names (e.g., tagName, publishedAt). You can achieve this by setting the keyDecodingStrategy on your JSONDecoder instance to .convertFromSnakeCase.

        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        return try decoder.decode(Release.self, from: data)

Comment on lines +30 to +36
for key in sparkleKeys {
if let value = bundle.object(forInfoDictionaryKey: key) {
print("Found Sparkle key \(key) for \(bundle.bundleIdentifier ?? "unknown"): \(value)")
}
}

return nil

Choose a reason for hiding this comment

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

medium

This block of code appears to be leftover debugging code. It iterates through sparkleKeys, prints any found values to the console, and then the function unconditionally returns nil. This code serves no functional purpose and should be removed.

        return nil

Comment on lines +104 to +120
private static func shell(_ command: String) -> Int32 {
let process = Process()
process.launchPath = "/bin/zsh"
process.arguments = ["-lc", command]

do {
try process.run()
process.waitUntilExit()
return process.terminationStatus
} catch {
return -1
}
}

private static func shellQuote(_ string: String) -> String {
return "'\(string.replacingOccurrences(of: "'", with: "'\\''"))'"
}

Choose a reason for hiding this comment

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

medium

The shell and shellQuote helper functions are duplicated across multiple files (LaunchAgent.swift, Brew.swift, MAS.swift), with slightly different implementations. This increases maintenance overhead. These should be consolidated into a single, shared utility file to ensure consistency and make future changes easier.

_ = try? SecurityChecks.backup(appPath: fullPath, bundleID: bundleID, version: currentVersion)
}

let (code, out) = try run("/usr/bin/hdiutil", ["attach", "-nobrowse", "-quiet", dmgURL.path])

Choose a reason for hiding this comment

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

medium

Parsing the plain text output of hdiutil is fragile and can break if the output format changes in a future macOS version. You can make this more robust by using the -plist flag (hdiutil attach -plist ...), which provides a structured XML output that can be safely parsed.

Comment on lines +39 to +50
struct CaskInfo: Decodable {
let token: String
let full_name: String
let tap: String
let version: String
let installed: String?
let outdated: Bool
let homepage: String?
let url: String
let name: [String]
let desc: String?
}

Choose a reason for hiding this comment

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

medium

The property names in CaskInfo use snake_case, which is common in JSON APIs but not idiomatic in Swift. For better code style and maintainability, you should use camelCase for property names and configure the JSONDecoder to convert from snake case automatically. This makes your Swift code cleaner and more consistent.1

    struct CaskInfo: Decodable {
        let token: String
        let fullName: String
        let tap: String
        let version: String
        let installed: String?
        let outdated: Bool
        let homepage: String?
        let url: String
        let name: [String]
        let desc: String?
    }

Style Guide References

Footnotes

  1. Swift API Design Guidelines recommend using camelCase for property names to maintain consistency with Swift's naming conventions.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on October 16

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

}

static func track(_ name: String, props: [String: Any] = [:]) {
guard UserDefaults.standard.bool(forKey: "telemetry_enabled") else { return }
Copy link

Choose a reason for hiding this comment

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

Bug: Telemetry Tracking Fails Due to Key Mismatch

The track function in Telemetry.swift checks for the telemetry_enabled key in UserDefaults, but the corresponding setting is stored using telemetryEnabled. This key mismatch means telemetry tracking will never activate, even when a user opts in.

Fix in Cursor Fix in Web

Button("Clear All Data") {
Button("Clear Cache (\(cacheSize))") {
// TODO: Implement data clearing
cacheSize = "0 MB"
Copy link

Choose a reason for hiding this comment

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

Bug: Cache Clear Button Misleading Display

The "Clear Cache" button in PrivacySettingsView updates its display to "0 MB" without actually clearing any data. This provides misleading user feedback, as the cache clearing logic is not yet implemented.

Fix in Cursor Fix in Web

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.

1 participant