Add Provider
Add new AI assistant providers to ClaudeBar following test-driven development patterns.
Installation
- Make sure Claude is on your device and in your terminal.
Skills load from
~/.claude/skills/when Claude Code starts up — so you need it on your machine first. If you don't have it yet, install it once with the command below, then runclaudein any terminal to verify.One-time setupnpm i -g @anthropic-ai/claude-codeAlready have it? Skip ahead.
- Paste into Claude Code or into your terminal.
This copies the whole skill folder into
~/.claude/skills/add-provider-tddworks/— the SKILL.md plus any scripts, reference docs, or templates the skill ships with. Safe default: works for every skill.Faster alternative (instruction-only skills)
Skips the clone and grabs only the SKILL.md file. Don't use this if the skill ships Python scripts, reference markdowns, or asset templates — they won't be downloaded and the skill will fail when it tries to load them.
Quick install (SKILL.md only)Sign up to copy - Restart Claude Code.
Quit and reopen Claude Code (or any other agent that loads from
~/.claude/skills/). New skills are picked up on startup. - Just ask Claude.
Skills auto-activate when your request matches the skill's description — no slash command needed. Trigger phrases live in the skill's own frontmatter; you can read them in the “What this skill does” section above.
Prefer to read the source first? Open on GitHub.
When Claude uses it
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"
What this skill does
Add Provider to ClaudeBar
Add new AI providers following established TDD patterns and architecture.
Architecture Overview
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 |
TDD Workflow
Phase 1: Parsing Tests (Red → Green)
Create Tests/InfrastructureTests/CLI/{Provider}UsageProbeParsingTests.swift:
import 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 { /* ... */ }
}
Phase 2: Probe Behavior Tests (Red → Green)
Create Tests/InfrastructureTests/CLI/{Provider}UsageProbeTests.swift:
import 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 { /* ... */ }
}
Phase 3: Implement Probe
Create Sources/Infrastructure/CLI/{Provider}UsageProbe.swift:
import 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
}
}
Phase 4: Create Provider
Choose Repository Type (ISP):
- Simple provider (no special config) → Use base
ProviderSettingsRepository - Provider with config → Create sub-protocol extending base (see ISP section below)
Create Sources/Domain/Provider/{Provider}Provider.swift:
import 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
}
}
}
Phase 5: Register Provider
Add to Sources/App/ClaudeBarApp.swift:
let 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):
// 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:
// 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"
Domain Model Mapping
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 |
Error Handling
Use existing ProbeError enum:
ProbeError.cliNotFound("{Provider}") // Binary/process not found
ProbeError.authenticationRequired // Auth token missing/expired
ProbeError.executionFailed("message") // Runtime errors
ProbeError.parseFailed("message") // Parse errors
ISP: Creating Provider-Specific Repository Sub-Protocols
If your provider needs special configuration or credentials, create a sub-protocol following ISP:
Step 1: Define Sub-Protocol in Domain
Add to Sources/Domain/Provider/ProviderSettingsRepository.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
}
Step 2: Implement in Infrastructure
Add to Sources/Infrastructure/Storage/JSONSettingsRepository.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")
}
}
Step 3: Update Provider and Probe
// 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 credentials
Reference Implementation
See references/antigravity-example.md for a complete working example showing:
- Full parsing test suite
- Probe behavior tests with mocking
- Probe implementation with process detection
- Provider class pattern
Provider Icon
See references/provider-icon-guide.md for creating provider icons:
- SVG template with rounded rectangle background
- PNG generation at 1x/2x/3x sizes
- Asset catalog setup
- ProviderVisualIdentity extension
Checklist
- Parsing tests created and passing
- Probe behavior tests created and passing
- Probe implementation complete
- Provider class created
- Provider registered in ClaudeBarApp
- Visual identity added to Theme.swift (color, name, icons)
- Provider icon SVG created with rounded rect background
- Icon PNGs generated (64, 128, 192px)
- All 300+ existing tests still pass
Related skills
Generative Code Art
anthropics
Create algorithmic art with p5.js using randomness and interactive parameters.
Poster & Visual Design
anthropics
Create original posters and visual art in PNG and PDF formats.
Claude API Helper
anthropics
Build, debug, and optimize Claude API applications with caching and model migration support.
MCP Server Builder
anthropics
Build protocol servers that connect language models to external APIs and services.