Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/App/ClaudeBarApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ struct ClaudeBarApp: App {
settingsRepository: settingsRepository
),
KiroProvider(probe: KiroUsageProbe(), settingsRepository: settingsRepository),
CursorProvider(probe: CursorUsageProbe(), settingsRepository: settingsRepository),
MiniMaxProvider(
probe: MiniMaxUsageProbe(settingsRepository: settingsRepository),
settingsRepository: settingsRepository
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "CursorIcon.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Comment on lines +1 to +15
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all Contents.json files in Assets.xcassets and check what image formats they reference
find . -path '*/Assets.xcassets/*.imageset/Contents.json' -type f 2>/dev/null | head -20 | while read file; do
  echo "=== $file ==="
  grep '"filename"' "$file" | head -3
done

echo -e "\n--- Summary of image formats used in imagesets ---"
find . -path '*/Assets.xcassets/*.imageset/Contents.json' -type f 2>/dev/null | xargs grep -h '"filename"' | sed 's/.*"\([^"]*\)".*/\1/' | sed 's/.*\.//' | sort | uniq -c | sort -rn

Repository: tddworks/ClaudeBar

Length of output: 2222


🏁 Script executed:

#!/bin/bash
# Search for CursorIcon and AmpCodeIcon files in various formats
echo "=== CursorIcon files ==="
find . -name '*CursorIcon*' -type f 2>/dev/null | sort

echo -e "\n=== AmpCodeIcon files ==="
find . -name '*AmpCodeIcon*' -type f 2>/dev/null | sort

echo -e "\n=== Check if CursorIcon.svg has preserves-vector-representation ==="
grep -r 'preserves-vector-representation' Sources/App/Resources/Assets.xcassets/*.imageset/Contents.json 2>/dev/null | grep -i cursor

echo -e "\n=== Check AmpCodeIcon Contents.json ==="
cat ./Sources/App/Resources/Assets.xcassets/AmpCodeIcon.imageset/Contents.json

Repository: tddworks/ClaudeBar

Length of output: 761


CursorIcon asset deviates from project PNG convention.

The imageset references CursorIcon.svg directly instead of PNG variants, inconsistent with the project pattern where 28 of 30 assets use PNG at runtime (at 1×/2×/3× scales). While the SVG source file is properly maintained at Sources/App/Resources/CursorIcon.svg for regeneration, the runtime asset should follow the established convention by generating and using PNG variants like other icons (e.g., CursorIcon_64.png, CursorIcon_128.png, CursorIcon_192.png or similar). Consider converting the SVG source to PNG variants for consistency.

🤖 Prompt for AI Agents
In `@Sources/App/Resources/Assets.xcassets/CursorIcon.imageset/Contents.json`
around lines 1 - 15, The CursorIcon imageset currently points to CursorIcon.svg
instead of the project's PNG runtime convention; replace the vector reference in
CursorIcon.imageset/Contents.json by generating PNG variants from the SVG source
(e.g., CursorIcon_1x.png, CursorIcon_2x.png, CursorIcon_3x.png or sizes like
CursorIcon_64.png, CursorIcon_128.png, CursorIcon_192.png) and update the
imageset entries to reference those PNG filenames, ensuring
"preserves-vector-representation" is removed or set appropriately so the asset
catalog uses the PNGs at runtime.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions Sources/App/Resources/CursorIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions Sources/App/Views/ProviderVisualIdentity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,34 @@ extension KiroProvider: ProviderVisualIdentity {
}
}

// MARK: - CursorProvider Visual Identity

extension CursorProvider: ProviderVisualIdentity {
public var symbolIcon: String { "cursorarrow.rays" }

public var iconAssetName: String { "CursorIcon" }

public func themeColor(for scheme: ColorScheme) -> Color {
// Cursor brand teal/cyan
scheme == .dark
? Color(red: 0.20, green: 0.78, blue: 0.82)
: Color(red: 0.12, green: 0.62, blue: 0.66)
}

public func themeGradient(for scheme: ColorScheme) -> LinearGradient {
LinearGradient(
colors: [
themeColor(for: scheme),
scheme == .dark
? Color(red: 0.15, green: 0.55, blue: 0.75)
: Color(red: 0.08, green: 0.45, blue: 0.60)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
}
}

// MARK: - MiniMaxProvider Visual Identity

extension MiniMaxProvider: ProviderVisualIdentity {
Expand Down Expand Up @@ -412,6 +440,10 @@ enum ProviderVisualIdentityLookup {
return scheme == .dark
? Color(red: 0.91, green: 0.27, blue: 0.42)
: Color(red: 0.82, green: 0.20, blue: 0.35)
case "cursor":
return scheme == .dark
? Color(red: 0.20, green: 0.78, blue: 0.82)
: Color(red: 0.12, green: 0.62, blue: 0.66)
default:
return BaseTheme.purpleVibrant
}
Expand Down Expand Up @@ -467,6 +499,10 @@ enum ProviderVisualIdentityLookup {
secondaryColor = scheme == .dark
? Color(red: 0.96, green: 0.53, blue: 0.24)
: Color(red: 0.86, green: 0.43, blue: 0.14)
case "cursor":
secondaryColor = scheme == .dark
? Color(red: 0.15, green: 0.55, blue: 0.75)
: Color(red: 0.08, green: 0.45, blue: 0.60)
default:
return LinearGradient(
colors: [BaseTheme.coralAccent, BaseTheme.pinkHot],
Expand Down Expand Up @@ -496,6 +532,7 @@ enum ProviderVisualIdentityLookup {
case "kimi": return "KimiIcon"
case "kiro": return "KiroIcon"
case "minimax": return "MiniMaxIcon"
case "cursor": return "CursorIcon"
default: return "QuestionIcon"
}
}
Expand All @@ -514,6 +551,7 @@ enum ProviderVisualIdentityLookup {
case "kimi": return "Kimi"
case "kiro": return "Kiro"
case "minimax": return "MiniMax"
case "cursor": return "Cursor"
default: return providerId.capitalized
}
}
Expand All @@ -532,6 +570,7 @@ enum ProviderVisualIdentityLookup {
case "kimi": return "k.square.fill"
case "kiro": return "wand.and.stars.inverse"
case "minimax": return "waveform"
case "cursor": return "cursorarrow.rays"
default: return "questionmark.circle.fill"
}
}
Expand Down
83 changes: 83 additions & 0 deletions Sources/Domain/Provider/Cursor/CursorProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Foundation
import Observation

/// Cursor AI provider - a rich domain model.
/// Observable class with its own state (isSyncing, snapshot, error).
@Observable
public final class CursorProvider: AIProvider, @unchecked Sendable {
// MARK: - Identity (Protocol Requirement)

public let id: String = "cursor"
public let name: String = "Cursor"
public let cliCommand: String = "cursor"

public var dashboardURL: URL? {
URL(string: "https://www.cursor.com/settings")
}

public var statusPageURL: URL? {
nil
}

/// Whether the provider is enabled (persisted via settingsRepository)
public var isEnabled: Bool {
didSet {
settingsRepository.setEnabled(isEnabled, forProvider: id)
}
}

// MARK: - State (Observable)

/// Whether the provider is currently syncing data
public private(set) var isSyncing: Bool = false

/// The current usage snapshot (nil if never refreshed or unavailable)
public private(set) var snapshot: UsageSnapshot?

/// The last error that occurred during refresh
public private(set) var lastError: Error?

// MARK: - Internal

/// The probe for fetching usage data via Cursor's API
private let probe: any UsageProbe

/// The settings repository for persisting provider settings
private let settingsRepository: any ProviderSettingsRepository

// MARK: - Initialization

/// Creates a Cursor provider
/// - Parameters:
/// - probe: The probe to use for fetching usage data
/// - settingsRepository: The repository for persisting settings
public init(probe: any UsageProbe, settingsRepository: any ProviderSettingsRepository) {
self.probe = probe
self.settingsRepository = settingsRepository
self.isEnabled = settingsRepository.isEnabled(forProvider: "cursor")
}

// MARK: - AIProvider Protocol

public func isAvailable() async -> Bool {
await probe.isAvailable()
}

/// Refreshes the usage data and updates the snapshot.
/// Sets isSyncing during refresh and captures any errors.
@discardableResult
public func refresh() async throws -> UsageSnapshot {
isSyncing = true
defer { isSyncing = false }

do {
let newSnapshot = try await probe.probe()
snapshot = newSnapshot
lastError = nil
return newSnapshot
} catch {
lastError = error
throw error
}
}
}
Loading