add-provider
Assists developers in building new AI providers by structuring architecture and test-driven implementation logic.
Install
mkdir -p .claude/skills/add-provider && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/7648" && unzip -o skill.zip -d .claude/skills/add-provider && rm skill.zipInstalls to .claude/skills/add-provider
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
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"Key capabilities
- →Implements new AI provider integrations using TDD
- →Creates usage probes for CLI tools and local APIs
- →Registers providers in the ClaudeBar application
- →Defines visual identity for new providers
How it works
The skill guides the developer through a five-phase TDD process, from parsing tests to registration and visual identity setup.
Inputs & outputs
When to use add-provider
- →Integrate new AI service providers
- →Create usage probes for CLI tools
- →Implement provider integration using TDD
About this skill
Add a Provider to ClaudeBar
A provider is data: one file, Modules/Providers/Resources/Providers/<id>.json.
It says where the key is, how to fetch, and how to read the answer. One
Provider class and one DataSource type run every definition. You write
no Swift for a vendor: no XxxProvider, no XxxUsageProbe, no
XxxCredentialLoader.
Read first: TARGET_ARCHITECTURE.md (how a definition runs), MODULAR_DESIGN.md (which module a file goes in), and CANONICAL_MODEL.md (the words). Worked examples:
codex.jsonandclaude.json, with their tests inModules/Providers/Tests/.
The pieces
| Piece | Where | You touch it when |
|---|---|---|
<id>.json | Modules/Providers/Resources/Providers/ | always |
<id>-*.js mapping script | beside the JSON | only when no mapping rule can read the format (a TUI screen) |
| golden tests | Modules/Providers/Tests/<Name>DefinitionTests.swift | always |
| a generic rule or worker | Modules/DataSources/ (+ DataSourcesTests) | when the definition language can't say what the provider needs |
| registration | Sources/App/ClaudeBarApp.swift → Self.builtIn("<id>", settings:) | always |
| look (name, symbol, colour, icon) | profile.look in <id>.json; the icon image in the asset catalog | always |
| research | docs/providers/<id>/README.md (users), design.md (contributors) | always |
The rules (MODULAR_DESIGN §3–4):
- No vendor's name in a module's Swift. A worker is named for its protocol, format or place (
JSONRPCFetcher,OAuth2Refresher), never a vendor. - No
Probenames. The words are DataSource, Fetch, Mapping, Usage, Quota, Plan and Cost. Modules/*neverimport Domain. The usage model isQuotas; providers, settings and accounts areProviders.
The definition: three closed sums per data source
{
"profile": { // WHO IT IS
"id": "acme", // stable forever: settings and the menu bar key on it
"name": "Acme",
"links": { "dashboard": "https://…", "status": "https://…" },
"look": { "symbol": "bolt.fill", "icon": "AcmeIcon", // SF Symbol, asset name
"color": { "light": [0.2, 0.5, 0.9], "dark": [0.3, 0.6, 1.0] },
"gradientEnd": { "light": [0.1, 0.3, 0.7], "dark": [0.2, 0.4, 0.8] } }
},
"cli": "acme", // optional
"enabledByDefault": true,
"defaultDataSource": "api",
"dataSources": [
{
"kind": "api", // what Settings' Data source picker saves as <id>.probeMode
"label": "API", "summary": "Calls Acme API directly",
"credential": { … }, // CredentialLookup — where the key is
"fetch": { … }, // Fetch — how to ask
"mapping": { … }, // Mapping — how to read the answer
"fallback": "cli" // optional: try this kind when this one fails
}
]
}
| Sum | Cases |
|---|---|
credential | environment · jsonFile (paths $.a.b; ~ and ${VAR:-default}; "…#jwt.claim" reads a JWT claim) · keychain · firstOf · plus "refresh": { "oauth2": … } (form or JSON body, dueWhen, every, onStatus, expiredCodes) |
fetch | http ({{token}} and other credential fields in URL and headers) · jsonRpc (handshake, call, then follow-ups, environment) · cli (args, input, autoResponses, readyWhen, screen: "rendered", environment, workingDirectory: "dedicated") |
mapping | json (below) · text (error phrases, then label + regex for % left/used) · script (a .js file in JavaScriptCore, no I/O, host humanDate()) |
Per data source, also: fallbackOn (hand-off by failure tag), cache.ttl
(also the background-refresh floor), context (JSON files the mapping may
read), recover.patchJSONFile, requiresFiles, identity,
verifyBeforeBackground. Per provider: links.dashboardByPlan and accounts
(added logins, see codex.json).
The JSON mapping
"json": {
"plan": { "path": "$credential.plan", "plans": { "max": "claudeMax" } }, // or "badges"
"email": ["$.account.email", "$credential.email"],
"quotas": [
{ "kind": "session", "at": "$.five_hour", "usedPercent": "utilization",
"resetsAt": { "iso8601": "resets_at" } }, // or epochSeconds / secondsFromNow
{ "kind": "model", "each": "$.limits", "where": { "path": "kind", "equals": "weekly" },
"name": { "firstOf": ["model.name"], "firstWord": true, "lowercase": true },
"usedPercent": "percent", "unique": true, "overLimit": true, "countdown": "hours",
"window": [{ "seconds": "window_seconds" }, { "days": 7 }] }, // the response's word, else the provider's
{ "kind": "time", "name": "Credits", // money, not a percentage:
"left": { "money": "$.data.remaining", "of": "$.data.limit", "currency": "USD" } } // no "of" = a balance
],
"cost": [ // the first shape that answers
{ "kind": "extraUsage", "when": { "path": "$.spend.enabled", "equals": true },
"used": { "amount": "$.spend.used.amount_minor", "decimals": "$.spend.used.exponent" },
"limit": { "amount": "$.spend.limit.amount_minor", "decimals": "$.spend.limit.exponent" } }
],
"whenEmpty": { "if": { "path": "$.plan", "equals": "free" }, "quotas": [ … ], "otherwise": "No data yet" },
"notAnObject": "Failed to parse usage response"
}
Paths: $.a.b from the root, a.b from the current object, $header.x, $key
(the map key inside each), $credential.x (a non-secret credential value).
A list of values means the first that answers; a number is a constant.
TDD workflow (Chicago school)
1 · Research and fixtures
Find where the usage really comes from (CLI command, endpoint, local file) and
capture real responses, redacted, including the failure answers: logged
out, rate limited, a free plan, an empty account. Write what you learned in
docs/providers/<id>/design.md.
2 · Golden tests first (red)
Modules/Providers/Tests/<Name>DefinitionTests.swift runs the real definition
through the real Provider over stubbed connections with StubbedProvider
(Tests/Support/Connections.swift). They fail first because <id>.json doesn't exist.
@MainActor
@Suite
struct AcmeDefinitionTests {
@Test
func `acme json keeps the definition laws`() throws {
let acme = try Providers.builtIn("acme")
#expect(acme.dataSources.map(\.kind) == ["api"])
#expect(acme.defaultDataSource == "api")
}
@Test
func `api reads the session window`() async throws {
let stub = try StubbedProvider(providerId: "acme")
defer { stub.cleanUp() }
stub.environment = ["ACME_API_KEY": "test-key"]
stub.answerHTTP(#"{"session":{"used_percent":30,"reset_at":1735000000}}"#)
let usage = try await stub.make("acme").refresh()
#expect(usage.quota(for: .session)?.percentRemaining == 70)
#expect(usage.quota(for: .session)?.resetsAt == Date(timeIntervalSince1970: 1735000000))
}
@Test
func `api without a key says so at the lookup step`() async throws {
let stub = try StubbedProvider(providerId: "acme")
defer { stub.cleanUp() }
let acme = try stub.make("acme")
await #expect(throws: UsageError.authenticationRequired) { try await acme.refresh() }
#expect(acme.lastFailedStep == .lookup)
}
}
Assert on state: the usage, lastError, lastFailedStep, answeredBy, and
files written back. Don't verify() calls. Cover every fixture from step 1.
3 · Write the definition (green)
Add <id>.json until the golden tests pass. Providers.builtIn validates it:
kinds are unique, and the default and every fallback name an existing kind.
4 · When the language can't say it
Don't write vendor code. Find the generic shape of the need, for example
"a list filtered by a field" or "money in minor units", and add it to
DataSources test-first in Modules/DataSources/Tests/. Then use it from the
JSON. TARGET_ARCHITECTURE §8.1
lists the pieces Claude needed. Add your row there.
Only a format no rule can read, like a terminal UI screen, gets a mapping
script, <id>-<what>.js. Test it through Swift with real captured screens
(see ClaudeUsageScreenTests).
5 · Register and give it a look
// Sources/App/ClaudeBarApp.swift, in the AIProviders list
Self.builtIn("acme", settings: settingsRepository),
Its name, symbol and colours are profile.look in the JSON — no switch id
table to edit. Add the icon image to the asset catalog under look.icon
(references/provider-icon-guide.md).
Settings need no new protocol: the data source choice is
dataSourceKind(forProvider:), and an on/off setting a definition names (for
example fallback.enabledBySetting) is isOn(_:forProvider:).
6 · Docs and release note
docs/providers/<id>/README.md(what users see, setup, errors) anddesign.md(sources, fields, gotchas).- One line under
## [Unreleased]inCHANGELOG.md. python3 scripts/gen-docs.py && python3 scripts/check-docs.py --strict.
Moving a legacy provider
Port the old XxxUsageProbeTests fixtures into golden tests first. They pin
behaviour the definition must keep. Then write the JSON, switch
ClaudeBarApp to Self.builtIn("<id>", …), and delete XxxProvider,
XxxUsageProbe, XxxCredentialLoader and their tests. Keep every saved
settings key: the data source choice is <id>.probeMode, and a definition
setting foo is read from <id>.foo.
Check
Content truncated.
When not to use it
- →When not following the established TDD architecture
Limitations
- →Requires adherence to specific repository patterns
- →Requires manual creation of icon assets
How it compares
It enforces a strict TDD workflow and architectural pattern, ensuring new providers are testable and consistent with existing ones.
Compared to similar skills
add-provider side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| add-provider (this skill) | 1 | 6mo | No flags | Advanced |
| implement-feature | 1 | 9mo | Review | Advanced |
| new-api-support | 1 | 8mo | Review | Intermediate |
| api-test-generator | 1 | 10mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by tddworks
View all by tddworks →You might also like
implement-feature
tddworks
Guide for implementing features in ClaudeBar following architecture-first design, TDD, rich domain models, and Swift 6.2 patterns. Use this skill when: (1) Adding new functionality to the app (2) Creating domain models that follow user's mental model (3) Building SwiftUI views that consume domain models directly (4) User asks "how do I implement X" or "add feature Y" (5) Implementing any feature that spans Domain, Infrastructure, and App layers
new-api-support
nalexn
Add introspection support for a SwiftUI API (view type, modifier, or View extension function). Use when the user wants to add support for a new SwiftUI entity to ViewInspector.
api-test-generator
mikopbx
Генерация полных Python pytest тестов для REST API эндпоинтов с валидацией схемы. Использовать при создании тестов для новых эндпоинтов, добавлении покрытия для CRUD операций или валидации соответствия API с OpenAPI схемами.
ios-simulator-skill
conorluddy
21 production-ready scripts for iOS app testing, building, and automation. Provides semantic UI navigation, build automation, accessibility testing, and simulator lifecycle management. Optimized for AI agents with minimal token output.
build-iphone-apps
glittercowboy
Build professional native iPhone apps in Swift with SwiftUI and UIKit. Full lifecycle - build, debug, test, optimize, ship. CLI-only, no Xcode. Targets iOS 26 with iOS 18 compatibility.
xcodebuildmcp
cameroncooke
Official skill for XcodeBuildMCP. Use when doing iOS/macOS/watchOS/tvOS/visionOS work (build, test, run, debug, log, UI automation).