Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Guide for adding new AI providers to ClaudeBar using TDD patterns. Use this skill when: (1) Adding a new AI assistant provider (like Antigravity, Cursor, etc.) (2) Creating a usage probe for a CLI tool or local API (3) Following TDD to implement provider integration (4) User asks "how do I add a new provider" or "create a provider for X"
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 45% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 7% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 33% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 53% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 59% | 0% |
Add new AI providers following established TDD patterns and architecture.
> Full architecture: docs/ARCHITECTURE.md
| Component | Location | Purpose | |-----------|----------|---------| | AIProvider | Sources/Domain/Provider/ | Rich domain model with isEnabled state | | UsageProbe | Sources/Infrastructure/CLI/ | Fetches quota from CLI/API | | Tests | Tests/InfrastructureTests/CLI/ | Parsing + behavior tests |
Create Tests/InfrastructureTests/CLI/{Provider}UsageProbeParsingTests.swift:
swiftimport Testing import Foundation @testable import Infrastructure @testable import Domain @Suite struct {Provider}UsageProbeParsingTests { static let sampleResponse = """ { /* sample API/CLI response */ } """ @Test func `parses quota into UsageQuota`() throws { let data = Data(Self.sampleResponse.utf8) let snapshot = try {Provider}UsageProbe.parseResponse(data, providerId: "{provider-id}") #expect(snapshot.quotas.count > 0) } @Test func `maps percentage correctly`() throws { /* ... */ } @Test func `parses reset time`() throws { /* ... */ } @Test func `extracts account email`() throws { /* ... */ } @Test func `handles missing data gracefully`() throws { /* ... */ } }
Create Tests/InfrastructureTests/CLI/{Provider}UsageProbeTests.swift:
swiftimport Testing import Foundation import Mockable @testable import Infrastructure @testable import Domain @Suite struct {Provider}UsageProbeTests { @Test func `isAvailable returns false when not detected`() async { let mockExecutor = MockCLIExecutor() given(mockExecutor).execute(...).willReturn(CLIResult(output: "", exitCode: 1)) let probe = {Provider}UsageProbe(cliExecutor: mockExecutor) #expect(await probe.isAvailable() == false) } @Test func `isAvailable returns true when detected`() async { /* ... */ } @Test func `probe throws appropriate error when unavailable`() async { /* ... */ } @Test func `probe returns UsageSnapshot on success`() async { /* ... */ } }
Create Sources/Infrastructure/CLI/{Provider}UsageProbe.swift:
swiftimport Foundation import Domain public struct {Provider}UsageProbe: UsageProbe { private let cliExecutor: any CLIExecutor private let networkClient: any NetworkClient private let timeout: TimeInterval public init( cliExecutor: (any CLIExecutor)? = nil, networkClient: (any NetworkClient)? = nil, timeout: TimeInterval = 8.0 ) { self.cliExecutor = cliExecutor ?? DefaultCLIExecutor() self.networkClient = networkClient ?? URLSession.shared self.timeout = timeout } public func isAvailable() async -> Bool { // Detect if provider is available (binary exists, process running, etc.) } public func probe() async throws -> UsageSnapshot { // 1. Detect/authenticate // 2. Fetch quota data // 3. Parse and return UsageSnapshot } // Static parsing for testability static func parseResponse(_ data: Data, providerId: String) throws -> UsageSnapshot { // Parse response into domain models } }
Choose Repository Type (ISP):
ProviderSettingsRepositoryCreate Sources/Domain/Provider/{Provider}Provider.swift:
swiftimport Foundation import Observation @Observable public final class {Provider}Provider: AIProvider, @unchecked Sendable { public let id: String = "{provider-id}" public let name: String = "{Provider Name}" public let cliCommand: String = "{cli-command}" public var dashboardURL: URL? { URL(string: "https://...") } public var statusPageURL: URL? { nil } /// Whether the provider is enabled (persisted via settingsRepository) public var isEnabled: Bool { didSet { settingsRepository.setEnabled(isEnabled, forProvider: id) } } public private(set) var isSyncing: Bool = false public private(set) var snapshot: UsageSnapshot? public private(set) var lastError: Error? private let probe: any UsageProbe private let settingsRepository: any ProviderSettingsRepository // Or your sub-protocol public init(probe: any UsageProbe, settingsRepository: any ProviderSettingsRepository) { self.probe = probe self.settingsRepository = settingsRepository // Default to enabled for most providers (set defaultValue: false for opt-in providers) self.isEnabled = settingsRepository.isEnabled(forProvider: "{provider-id}") } public func isAvailable() async -> Bool { await probe.isAvailable() } @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 } } }
Add to Sources/App/ClaudeBarApp.swift:
swiftlet settingsRepository = JSONSettingsRepository.shared let repository = AIProviders(providers: [ ClaudeProvider(probe: ClaudeUsageProbe(), settingsRepository: settingsRepository), // ... existing providers {Provider}Provider(probe: {Provider}UsageProbe(), settingsRepository: settingsRepository), ])
For providers with special settings (ISP pattern):
swift// ZaiProvider uses ZaiSettingsRepository (sub-protocol) ZaiProvider( probe: ZaiUsageProbe(settingsRepository: settingsRepository), settingsRepository: settingsRepository // Same instance, casted to ZaiSettingsRepository ) // CopilotProvider uses CopilotSettingsRepository (sub-protocol with credentials) CopilotProvider( probe: CopilotUsageProbe(settingsRepository: settingsRepository), settingsRepository: settingsRepository // Same instance, casted to CopilotSettingsRepository )
Add visual identity in Sources/App/Views/Theme.swift:
swift// In AppTheme.providerColor(for:scheme:) case "{provider-id}": return /* your color */ // In AppTheme.providerName(for:) case "{provider-id}": return "{Provider Name}" // In AppTheme.providerSymbolIcon(for:) case "{provider-id}": return "/* SF Symbol name */" // In AppTheme.providerIconAssetName(for:) case "{provider-id}": return "{Provider}Icon"
Map provider responses to existing domain models:
| Source Data | Domain Model | |-------------|--------------| | Quota percentage | UsageQuota.percentRemaining (0-100) | | Model/tier name | QuotaType.modelSpecific("name") | | Reset time | UsageQuota.resetsAt (Date) | | Account email | UsageSnapshot.accountEmail |
Use existing ProbeError enum:
swiftProbeError.cliNotFound("{Provider}") // Binary/process not found ProbeError.authenticationRequired // Auth token missing/expired ProbeError.executionFailed("message") // Runtime errors ProbeError.parseFailed("message") // Parse errors
If your provider needs special configuration or credentials, create a sub-protocol following ISP:
Add to Sources/Domain/Provider/ProviderSettingsRepository.swift:
swift/// {Provider}-specific settings repository, extending base ProviderSettingsRepository. public protocol {Provider}SettingsRepository: ProviderSettingsRepository { // Configuration func {provider}ConfigPath() -> String func set{Provider}ConfigPath(_ path: String) // Credentials (if needed) func save{Provider}Token(_ token: String) func get{Provider}Token() -> String? func has{Provider}Token() -> Bool }
Add to Sources/Infrastructure/Storage/JSONSettingsRepository.swift:
swift// MARK: - {Provider}SettingsRepository extension JSONSettingsRepository: {Provider}SettingsRepository { public func {provider}ConfigPath() -> String { store.read(key: "{provider}.configPath") ?? "" } public func set{Provider}ConfigPath(_ path: String) { store.write(value: path, key: "{provider}.configPath") } }
swift// Provider private let settingsRepository: any {Provider}SettingsRepository // Probe (if needs settings) public init(settingsRepository: any {Provider}SettingsRepository) { self.settingsRepository = settingsRepository }
Existing Examples:
ZaiSettingsRepository - config path + env varCopilotSettingsRepository - env var + GitHub credentialsSee references/antigravity-example.md for a complete working example showing:
See references/provider-icon-guide.md for creating provider icons:
Other measured skills in the registry, with their headline benchmark lift.