feat(api): add Anthropic direct API key support

Support native Anthropic API keys (sk-ant-...) via `ccs api create --preset anthropic`.
Profiles use ANTHROPIC_API_KEY env var without BASE_URL, letting Claude CLI
authenticate natively with x-api-key header instead of proxy Bearer token.

- Add anthropic preset to provider catalog
- Branch profile-writer env structure (native vs proxy)
- Add validateAnthropicKey() preflight with x-api-key header
- Auto-detect api.anthropic.com URL in interactive flow
- Fix hasApiKey/isConfigured to recognize ANTHROPIC_API_KEY
- Update delegation-validator for native mode profiles
- Add Droid target API key fallback (ANTHROPIC_API_KEY)

Closes #688

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
BbChip0103andClaude Opus 4.6 committed 2026-03-05 13:13:10 +09:00
1 parent 0c4d5244c8
commit fedb4d4cde
11 files changed
+301 -20

No files matched your search

+1 -1
View File
@@ -49,7 +49,7 @@ export function isApiProfileConfigured(apiName: string): boolean {
if (!fs.existsSync(settingsPath)) return false;
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
const token = settings?.env?.ANTHROPIC_AUTH_TOKEN || '';
const token = settings?.env?.ANTHROPIC_AUTH_TOKEN || settings?.env?.ANTHROPIC_API_KEY || '';
return token.length > 0 && !token.includes('YOUR_') && !token.includes('your-');
} catch {
return false;
+23 -8
View File
@@ -32,6 +32,11 @@ function isOpenRouterUrl(baseUrl: string): boolean {
return baseUrl.toLowerCase().includes('openrouter.ai');
}
/** Detect Anthropic direct API profile (native auth, no proxy) */
function isAnthropicDirect(baseUrl: string, apiKey: string): boolean {
return apiKey.startsWith('sk-ant-') || baseUrl.includes('api.anthropic.com');
}
function resolveProviderFromBaseUrl(baseUrl: string): CLIProxyProvider | null {
if (baseUrl.trim().length === 0) {
return null;
@@ -76,17 +81,23 @@ function createSettingsFile(
model: models.default,
});
const isNative = isAnthropicDirect(baseUrl, apiKey);
const settings = {
env: {
ANTHROPIC_BASE_URL: baseUrl,
ANTHROPIC_AUTH_TOKEN: apiKey,
// Native mode: ANTHROPIC_API_KEY only, no BASE_URL/AUTH_TOKEN
// Proxy mode: ANTHROPIC_BASE_URL + AUTH_TOKEN (existing behavior)
...(isNative
? { ANTHROPIC_API_KEY: apiKey }
: {
ANTHROPIC_BASE_URL: baseUrl,
ANTHROPIC_AUTH_TOKEN: apiKey,
...(isOpenRouterUrl(baseUrl) && { ANTHROPIC_API_KEY: '' }),
}),
ANTHROPIC_MODEL: models.default,
ANTHROPIC_DEFAULT_OPUS_MODEL: models.opus,
ANTHROPIC_DEFAULT_SONNET_MODEL: models.sonnet,
ANTHROPIC_DEFAULT_HAIKU_MODEL: models.haiku,
CCS_DROID_PROVIDER: droidProvider,
// OpenRouter requires explicitly blanking the API key to prevent conflicts
...(isOpenRouterUrl(baseUrl) && { ANTHROPIC_API_KEY: '' }),
},
};
@@ -151,17 +162,21 @@ function createApiProfileUnified(
model: models.default,
});
const isNative = isAnthropicDirect(baseUrl, apiKey);
const settings = {
env: {
ANTHROPIC_BASE_URL: baseUrl,
ANTHROPIC_AUTH_TOKEN: apiKey,
...(isNative
? { ANTHROPIC_API_KEY: apiKey }
: {
ANTHROPIC_BASE_URL: baseUrl,
ANTHROPIC_AUTH_TOKEN: apiKey,
...(isOpenRouterUrl(baseUrl) && { ANTHROPIC_API_KEY: '' }),
}),
ANTHROPIC_MODEL: models.default,
ANTHROPIC_DEFAULT_OPUS_MODEL: models.opus,
ANTHROPIC_DEFAULT_SONNET_MODEL: models.sonnet,
ANTHROPIC_DEFAULT_HAIKU_MODEL: models.haiku,
CCS_DROID_PROVIDER: droidProvider,
// OpenRouter requires explicitly blanking the API key to prevent conflicts
...(isOpenRouterUrl(baseUrl) && { ANTHROPIC_API_KEY: '' }),
},
};