Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion apps/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ app.use('/api/auto-mode', createAutoModeRoutes(autoModeService));
app.use('/api/enhance-prompt', createEnhancePromptRoutes(settingsService));
app.use('/api/worktree', createWorktreeRoutes());
app.use('/api/git', createGitRoutes());
app.use('/api/setup', createSetupRoutes());
app.use('/api/setup', createSetupRoutes(settingsService));
app.use('/api/suggestions', createSuggestionsRoutes(events, settingsService));
app.use('/api/models', createModelsRoutes());
app.use('/api/spec-regeneration', createSpecRegenerationRoutes(events, settingsService));
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/providers/claude-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {
// Only these vars are passed - nothing else from process.env leaks through.
const ALLOWED_ENV_VARS = [
'ANTHROPIC_API_KEY',
'ANTHROPIC_BASE_URL',
'PATH',
'HOME',
'SHELL',
Expand Down
5 changes: 3 additions & 2 deletions apps/server/src/routes/setup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import { Router } from 'express';
import type { SettingsService } from '../../services/settings-service.js';
import { createClaudeStatusHandler } from './routes/claude-status.js';
import { createInstallClaudeHandler } from './routes/install-claude.js';
import { createAuthClaudeHandler } from './routes/auth-claude.js';
Expand All @@ -24,13 +25,13 @@ import {
createGetExampleConfigHandler,
} from './routes/cursor-config.js';

export function createSetupRoutes(): Router {
export function createSetupRoutes(settingsService: SettingsService): Router {
const router = Router();

router.get('/claude-status', createClaudeStatusHandler());
router.post('/install-claude', createInstallClaudeHandler());
router.post('/auth-claude', createAuthClaudeHandler());
router.post('/store-api-key', createStoreApiKeyHandler());
router.post('/store-api-key', createStoreApiKeyHandler(settingsService));
router.post('/delete-api-key', createDeleteApiKeyHandler());
router.get('/api-keys', createApiKeysHandler());
router.get('/platform', createPlatformHandler());
Expand Down
28 changes: 26 additions & 2 deletions apps/server/src/routes/setup/routes/store-api-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
import type { Request, Response } from 'express';
import { setApiKey, persistApiKeyToEnv, getErrorMessage, logError } from '../common.js';
import { createLogger } from '@automaker/utils';
import type { SettingsService } from '../../../services/settings-service.js';

const logger = createLogger('Setup');

export function createStoreApiKeyHandler() {
export function createStoreApiKeyHandler(settingsService: SettingsService) {
return async (req: Request, res: Response): Promise<void> => {
try {
const { provider, apiKey } = req.body as {
const { provider, apiKey, baseUrl } = req.body as {
provider: string;
apiKey: string;
baseUrl?: string;
};

if (!provider || !apiKey) {
Expand All @@ -29,6 +31,28 @@ export function createStoreApiKeyHandler() {
process.env.ANTHROPIC_API_KEY = apiKey;
await persistApiKeyToEnv('ANTHROPIC_API_KEY', apiKey);
logger.info('[Setup] Stored API key as ANTHROPIC_API_KEY');

// Handle custom base URL if provided
if (baseUrl && baseUrl.trim()) {
process.env.ANTHROPIC_BASE_URL = baseUrl.trim();
await persistApiKeyToEnv('ANTHROPIC_BASE_URL', baseUrl.trim());
logger.info('[Setup] Stored custom base URL as ANTHROPIC_BASE_URL');
} else if (process.env.ANTHROPIC_BASE_URL) {
// Clear existing base URL if not provided
delete process.env.ANTHROPIC_BASE_URL;
// Note: we don't remove from .env file as that's more complex
logger.info('[Setup] Cleared ANTHROPIC_BASE_URL from environment');
}
Comment on lines +40 to +45
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Clearing the ANTHROPIC_BASE_URL from process.env is not sufficient as it won't persist across server restarts. The value will be reloaded from the .env file, causing the old base URL to be used again. This is likely not the intended behavior.

A function removeApiKeyFromEnv already exists in apps/server/src/routes/setup/routes/delete-api-key.ts which can remove a key from the .env file. It would be best to move this function to common.ts and use it here to properly remove the key from the .env file.

Here's how you could modify this block after making removeApiKeyFromEnv available:

} else if (process.env.ANTHROPIC_BASE_URL) {
  // Clear existing base URL if not provided
  delete process.env.ANTHROPIC_BASE_URL;
  await removeApiKeyFromEnv('ANTHROPIC_BASE_URL');
  logger.info('[Setup] Cleared ANTHROPIC_BASE_URL from environment and .env file');
}

This ensures that clearing the base URL is a persistent change.


// Persist base URL to settings service
if (baseUrl !== undefined) {
await settingsService.updateCredentials({
baseUrls: {
anthropic: baseUrl.trim() || undefined,
},
});
logger.info('[Setup] Persisted base URL to settings service');
}
Comment on lines +35 to +55
Copy link

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:

# First, find and examine the file mentioned in the review
find . -type f -name "store-api-key.ts" -path "*/routes/setup/*"

Repository: AutoMaker-Org/automaker

Length of output: 121


🏁 Script executed:

# Check the actual implementation of the code in question
cat -n apps/server/src/routes/setup/routes/store-api-key.ts | head -100

Repository: AutoMaker-Org/automaker

Length of output: 3113


🏁 Script executed:

# Find persistApiKeyToEnv implementation
rg -n "persistApiKeyToEnv" -A 10 -B 2

Repository: AutoMaker-Org/automaker

Length of output: 4326


🏁 Script executed:

# Check how environment variables are loaded at startup
rg -n "ANTHROPIC_BASE_URL" -A 5 -B 5

Repository: AutoMaker-Org/automaker

Length of output: 3373


🏁 Script executed:

# Check secureFs.writeEnvKey implementation to see if it can delete keys
rg -n "writeEnvKey" -A 15 -B 5

Repository: AutoMaker-Org/automaker

Length of output: 4236


🏁 Script executed:

# Check how settings service initializes and uses baseUrl
rg -n "class SettingsService|updateCredentials|getCredentials" apps/server/src/services/settings-service.ts -A 10 -B 2

Repository: AutoMaker-Org/automaker

Length of output: 3273


🏁 Script executed:

# Check how ANTHROPIC_BASE_URL is used when initializing the client
rg -n "ANTHROPIC_BASE_URL" apps/server/src/providers/claude-provider.ts -A 5 -B 5

Repository: AutoMaker-Org/automaker

Length of output: 369


🏁 Script executed:

# Check the initialization order in the main app file
find . -type f -name "main.ts" -o -name "index.ts" -o -name "app.ts" | xargs grep -l "SettingsService\|setupRoutes" 2>/dev/null | head -3

Repository: AutoMaker-Org/automaker

Length of output: 198


🏁 Script executed:

# Check claude-provider to see where ANTHROPIC_BASE_URL is used and how it's initialized
cat -n apps/server/src/providers/claude-provider.ts | head -150

Repository: AutoMaker-Org/automaker

Length of output: 5968


🏁 Script executed:

# Check how settings service baseUrls are loaded and used
rg -n "baseUrl|baseUrls" apps/server/src/providers/claude-provider.ts -A 3 -B 3

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Look at app initialization to understand the order of precedence
rg -n "SettingsService\|setupRoutes\|createClaudeProvider" apps/server/src/index.ts -A 5 -B 5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Look for where settings service baseUrls are applied to process.env
rg -n "baseUrl|baseUrls" apps/server/src -A 2 -B 2 | head -100

Repository: AutoMaker-Org/automaker

Length of output: 4110


🏁 Script executed:

# Check if there's any initialization code that applies settings service values to process.env
rg -n "updateCredentials|getCredentials" apps/server/src/index.ts -A 10 -B 5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check the updateEnvContent function to understand what it does
cat -n libs/platform/src/secure-fs.ts | grep -A 30 "function updateEnvContent"

Repository: AutoMaker-Org/automaker

Length of output: 1194


🏁 Script executed:

# Check if there's any startup code that loads settings service baseUrl into process.env
rg -n "process.env.ANTHROPIC_BASE_URL\s*=" apps/server/src -B 5 -A 5

Repository: AutoMaker-Org/automaker

Length of output: 1268


🏁 Script executed:

# Search for any initialization that reads baseUrls from settings service
rg -n "getCredentials|baseUrl" apps/server/src/index.ts -A 5 -B 5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Look for any middleware or initialization that syncs settings service to process.env
rg -n "settingsService|baseUrl" apps/server/src/middleware -A 3 -B 3

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check the main server file for initialization logic
find . -type f -path "*/apps/server/src/index.ts" -o -path "*/apps/server/src/main.ts" | xargs cat

Repository: AutoMaker-Org/automaker

Length of output: 22471


Call persistApiKeyToEnv with an empty value when clearing the base URL to ensure .env is updated

When clearing the base URL (lines 40-45), the code removes it from process.env but doesn't update the .env file. Since dotenv.config() runs at startup and reloads the old baseUrl from .env, the cleared baseUrl will reappear after a server restart.

The simplest fix is to persist an empty value to .env when clearing, which the existing updateEnvContent() function already supports:

} else if (process.env.ANTHROPIC_BASE_URL) {
  // Clear existing base URL if not provided
  delete process.env.ANTHROPIC_BASE_URL;
  await persistApiKeyToEnv('ANTHROPIC_BASE_URL', '');
  logger.info('[Setup] Cleared ANTHROPIC_BASE_URL from environment');
}

This way, the .env file will be updated to an empty value, preventing the old baseUrl from being restored on restart.

🤖 Prompt for AI Agents
In @apps/server/src/routes/setup/routes/store-api-key.ts around lines 35 - 55,
When clearing process.env.ANTHROPIC_BASE_URL in the else branch, also call and
await persistApiKeyToEnv('ANTHROPIC_BASE_URL', '') so the .env file is updated
to an empty value (preventing dotenv from restoring the old value on restart);
update the block around process.env.ANTHROPIC_BASE_URL to delete the env var,
await persistApiKeyToEnv with an empty string, and keep the logger.info message
(referencing persistApiKeyToEnv and process.env.ANTHROPIC_BASE_URL).

} else {
res.status(400).json({
success: false,
Expand Down
40 changes: 39 additions & 1 deletion apps/server/src/routes/setup/routes/verify-claude-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,33 @@ import { getApiKey } from '../common.js';

const logger = createLogger('Setup');

// Allowed environment variables to pass to the SDK
// Must be passed explicitly - SDK doesn't inherit from process.env
const ALLOWED_ENV_VARS = [
'ANTHROPIC_API_KEY',
'ANTHROPIC_BASE_URL',
'PATH',
'HOME',
'SHELL',
'TERM',
'USER',
'LANG',
'LC_ALL',
];

/**
* Build environment for the SDK with only explicitly allowed variables
*/
function buildEnv(): Record<string, string | undefined> {
const env: Record<string, string | undefined> = {};
for (const key of ALLOWED_ENV_VARS) {
if (process.env[key]) {
env[key] = process.env[key];
}
}
return env;
}

// Known error patterns that indicate auth failure
const AUTH_ERROR_PATTERNS = [
'OAuth token revoked',
Expand Down Expand Up @@ -91,13 +118,16 @@ export function createVerifyClaudeAuthHandler() {

// Save original env values
const originalAnthropicKey = process.env.ANTHROPIC_API_KEY;
const originalBaseUrl = process.env.ANTHROPIC_BASE_URL;

try {
// Configure environment based on auth method
if (authMethod === 'cli') {
// For CLI verification, remove any API key so it uses CLI credentials only
delete process.env.ANTHROPIC_API_KEY;
logger.info('[Setup] Cleared API key environment for CLI verification');
// CLI auth only works with standard Anthropic API, not custom endpoints
delete process.env.ANTHROPIC_BASE_URL;
logger.info('[Setup] Cleared API key and base URL for CLI verification');
} else if (authMethod === 'api_key') {
// For API key verification, use provided key, stored key, or env var (in order of priority)
if (apiKey) {
Expand Down Expand Up @@ -128,6 +158,7 @@ export function createVerifyClaudeAuthHandler() {
maxTurns: 1,
allowedTools: [],
abortController,
env: buildEnv(),
Copy link

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

🌐 Web query:

@anthropic-ai/claude-agent-sdk 0.1.76 env option query configuration documentation

💡 Result:

Short answer: In @anthropic-ai/claude-agent-sdk v0.1.76 the "env" option is a dictionary of environment-variable name → string value that the SDK passes to the child/CLI/MCP processes it launches (used to provide API keys, credentials, feature flags, etc.). Example usage: env: { "ANTHROPIC_API_KEY": "sk-...", "DEBUG": "true" }. You can also combine this with extra_args for CLI flags. [1][2][3]

Sources:

  • Package/version listing (v0.1.76). [1]
  • Options docs showing env (dict[str,str]) and example. [2]
  • Anthropic/Claude Agent SDK docs describing MCP/CLI env usage and API-key patterns. [3]

🏁 Script executed:

# Find and examine the actual file context
cat -n apps/server/src/routes/setup/routes/verify-claude-auth.ts | sed -n '150,175p'

Repository: AutoMaker-Org/automaker

Length of output: 1098


🏁 Script executed:

# Search for buildEnv() function definition
fd -t f "\.ts$" -x grep -l "buildEnv" {} \;

Repository: AutoMaker-Org/automaker

Length of output: 173


🏁 Script executed:

# Check for SDK version specification
cat -n package.json | head -50

Repository: AutoMaker-Org/automaker

Length of output: 3748


🏁 Script executed:

# Find and examine buildEnv() definition
rg "export\s+function\s+buildEnv|function\s+buildEnv|const\s+buildEnv" -A 10

Repository: AutoMaker-Org/automaker

Length of output: 1788


🏁 Script executed:

# Search for query function definition and its parameters
rg "function\s+query|const\s+query|export.*query" -B 2 -A 15 apps/server/src/routes/setup/routes/verify-claude-auth.ts

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check imports in the file to understand where query and buildEnv come from
head -30 apps/server/src/routes/setup/routes/verify-claude-auth.ts

Repository: AutoMaker-Org/automaker

Length of output: 881


🏁 Script executed:

# Search for query function type definition
rg "query.*options" -A 5 apps/server/src/routes/setup/routes/verify-claude-auth.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check if there are TypeScript type definitions available
find . -name "*.d.ts" -type f | xargs grep -l "query" 2>/dev/null | head -5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Look at how buildEnv is used - check if undefined values could cause issues
rg "for.*const.*key.*of.*ALLOWED_ENV_VARS" -A 5 apps/server/src/routes/setup/routes/verify-claude-auth.ts

Repository: AutoMaker-Org/automaker

Length of output: 193


🏁 Script executed:

# Check if the env object actually contains any undefined values at runtime
# by looking at how the allowed vars are accessed
cat -n apps/server/src/routes/setup/routes/verify-claude-auth.ts | sed -n '25,60p'

Repository: AutoMaker-Org/automaker

Length of output: 1198


SDK support confirmed; tighten the return type annotation.

The env option is supported by @anthropic-ai/claude-agent-sdk v0.1.76 and the usage here is correct. However, buildEnv() should return Record<string, string> instead of Record<string, string | undefined> since the function only assigns keys when values are truthy (line 33: if (process.env[key])), meaning the object never actually contains undefined values.

🤖 Prompt for AI Agents
In @apps/server/src/routes/setup/routes/verify-claude-auth.ts at line 161, The
buildEnv() function is typed too loosely; change its return type from
Record<string, string | undefined> to Record<string, string> and adjust the
implementation so the compiler knows no undefined values are present (e.g., keep
the existing conditional that only assigns when process.env[key] is truthy or
explicitly filter/guard values before assignment), so callers like the env
option passed to the Claude SDK match the stricter Record<string, string> type.

},
});

Expand Down Expand Up @@ -285,6 +316,13 @@ export function createVerifyClaudeAuthHandler() {
// If we cleared it and there was no original, keep it cleared
delete process.env.ANTHROPIC_API_KEY;
}
// Restore base URL
if (originalBaseUrl !== undefined) {
process.env.ANTHROPIC_BASE_URL = originalBaseUrl;
} else if (authMethod === 'cli') {
// If we cleared it and there was no original, keep it cleared
delete process.env.ANTHROPIC_BASE_URL;
}
}

logger.info('[Setup] Verification result:', {
Expand Down
13 changes: 11 additions & 2 deletions apps/server/src/services/settings-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ export class SettingsService {
/**
* Update credentials with partial changes
*
* Updates individual API keys. Uses deep merge for apiKeys object.
* Updates individual API keys. Uses deep merge for apiKeys and baseUrls objects.
* Creates dataDir if needed. Credentials are written atomically.
* WARNING: Use only in secure contexts - keys are unencrypted.
*
Expand All @@ -360,6 +360,14 @@ export class SettingsService {
};
}

// Deep merge base URLs if provided
if (updates.baseUrls) {
updated.baseUrls = {
...current.baseUrls,
...updates.baseUrls,
};
}

await atomicWriteJson(credentialsPath, updated);
logger.info('Credentials updated');

Expand All @@ -376,7 +384,7 @@ export class SettingsService {
* @returns Promise resolving to masked credentials object with each provider's status
*/
async getMaskedCredentials(): Promise<{
anthropic: { configured: boolean; masked: string };
anthropic: { configured: boolean; masked: string; baseUrl?: string };
}> {
const credentials = await this.getCredentials();

Expand All @@ -389,6 +397,7 @@ export class SettingsService {
anthropic: {
configured: !!credentials.apiKeys.anthropic,
masked: maskKey(credentials.apiKeys.anthropic),
baseUrl: credentials.baseUrls?.anthropic,
},
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function useTokenSave({ provider, onSuccess }: UseTokenSaveOptions) {
const [isSaving, setIsSaving] = useState(false);

const saveToken = useCallback(
async (tokenValue: string) => {
async (tokenValue: string, baseUrl?: string) => {
if (!tokenValue.trim()) {
toast.error('Please enter a valid token');
return false;
Expand All @@ -26,11 +26,11 @@ export function useTokenSave({ provider, onSuccess }: UseTokenSaveOptions) {
const setupApi = api.setup;

if (setupApi?.storeApiKey) {
const result = await setupApi.storeApiKey(provider, tokenValue);
const result = await setupApi.storeApiKey(provider, tokenValue, baseUrl);
logger.info(`Store result for ${provider}:`, result);

if (result.success) {
const tokenType = provider.includes('oauth') ? 'subscription token' : 'API key';
const tokenType = provider.includes('oauth') ? 'subscription token' : 'configuration';
toast.success(`${tokenType} saved successfully`);
onSuccess?.();
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
const { setApiKeys, apiKeys } = useAppStore();

const [apiKey, setApiKey] = useState('');
const [baseUrl, setBaseUrl] = useState('');

// CLI Verification state
const [cliVerificationStatus, setCliVerificationStatus] = useState<VerificationStatus>('idle');
Expand Down Expand Up @@ -111,7 +112,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
apiKeyValid: true,
});
setApiKeys({ ...apiKeys, anthropic: apiKey });
toast.success('API key saved successfully!');
toast.success('Anthropic configuration saved successfully!');
},
});

Expand Down Expand Up @@ -220,6 +221,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
if (result.success) {
// Clear local state
setApiKey('');
setBaseUrl('');
setApiKeys({ ...apiKeys, anthropic: '' });
setApiKeyVerificationStatus('idle');
setApiKeyVerificationError(null);
Expand All @@ -228,12 +230,13 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
method: 'none',
hasCredentialsFile: claudeAuthStatus?.hasCredentialsFile || false,
});
toast.success('API key deleted successfully');
toast.success('Anthropic configuration deleted successfully');
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The toast message now correctly refers to 'Anthropic configuration'. However, the backend delete-api-key endpoint, which is called when the user deletes the configuration, only seems to delete the API key (ANTHROPIC_API_KEY). It should be updated to also delete the base URL (ANTHROPIC_BASE_URL) from both the .env file and the credentials.json settings to ensure the entire configuration is removed consistently.

} else {
toast.error(result.error || 'Failed to delete API key');
toast.error(result.error || 'Failed to delete configuration');
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to delete API key';
const errorMessage =
error instanceof Error ? error.message : 'Failed to delete configuration';
toast.error(errorMessage);
} finally {
setIsDeletingApiKey(false);
Expand Down Expand Up @@ -531,7 +534,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
</div>
</AccordionTrigger>
<AccordionContent className="pt-4 space-y-4">
{/* API Key Input */}
{/* API Key Configuration */}
<div className="space-y-4 p-4 rounded-lg bg-muted/30 border border-border">
<div className="space-y-2">
<Label htmlFor="anthropic-key" className="text-foreground">
Expand Down Expand Up @@ -560,9 +563,28 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
</p>
</div>

<div className="space-y-2">
<Label htmlFor="anthropic-base-url" className="text-foreground">
API Base URL
</Label>
<Input
id="anthropic-base-url"
type="url"
placeholder="https://api.anthropic.com (leave empty for default)"
value={baseUrl}
onChange={(e) => setBaseUrl(e.target.value)}
className="bg-input border-border text-foreground"
data-testid="anthropic-base-url-input"
/>
<p className="text-xs text-muted-foreground">
Optional: Set a custom API base URL for enterprise deployments or custom
endpoints
</p>
</div>

<div className="flex gap-2">
<Button
onClick={() => saveApiKeyToken(apiKey)}
onClick={() => saveApiKeyToken(apiKey, baseUrl)}
disabled={isSavingApiKey || !apiKey.trim()}
className="flex-1 bg-brand-500 hover:bg-brand-600 text-white"
data-testid="save-anthropic-key-button"
Expand All @@ -573,7 +595,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
Saving...
</>
) : (
'Save API Key'
'Save Configuration'
)}
</Button>
{hasApiKey && (
Expand Down
12 changes: 9 additions & 3 deletions apps/ui/src/lib/electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,8 @@ export interface ElectronAPI {
}>;
storeApiKey: (
provider: string,
apiKey: string
apiKey: string,
baseUrl?: string
) => Promise<{ success: boolean; error?: string }>;
deleteApiKey: (
provider: string
Expand Down Expand Up @@ -1219,7 +1220,11 @@ interface SetupAPI {
message?: string;
output?: string;
}>;
storeApiKey: (provider: string, apiKey: string) => Promise<{ success: boolean; error?: string }>;
storeApiKey: (
provider: string,
apiKey: string,
baseUrl?: string
) => Promise<{ success: boolean; error?: string }>;
getApiKeys: () => Promise<{
success: boolean;
hasAnthropicKey: boolean;
Expand Down Expand Up @@ -1295,8 +1300,9 @@ function createMockSetupAPI(): SetupAPI {
};
},

storeApiKey: async (provider: string, apiKey: string) => {
storeApiKey: async (provider: string, apiKey: string, baseUrl?: string) => {
console.log('[Mock] Storing API key for:', provider);
console.log('[Mock] Base URL:', baseUrl);
// In mock mode, we just pretend to store it (it's already in the app store)
return { success: true };
},
Expand Down
5 changes: 3 additions & 2 deletions apps/ui/src/lib/http-api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1011,11 +1011,12 @@ export class HttpApiClient implements ElectronAPI {

storeApiKey: (
provider: string,
apiKey: string
apiKey: string,
baseUrl?: string
): Promise<{
success: boolean;
error?: string;
}> => this.post('/api/setup/store-api-key', { provider, apiKey }),
}> => this.post('/api/setup/store-api-key', { provider, apiKey, baseUrl }),

deleteApiKey: (
provider: string
Expand Down
8 changes: 8 additions & 0 deletions libs/types/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,11 @@ export interface Credentials {
/** OpenAI API key (for compatibility or alternative providers) */
openai: string;
};
/** Custom base URLs for API providers (optional) */
baseUrls?: {
/** Custom Anthropic API base URL (sets ANTHROPIC_BASE_URL) */
anthropic?: string;
};
}

/**
Expand Down Expand Up @@ -685,6 +690,9 @@ export const DEFAULT_CREDENTIALS: Credentials = {
google: '',
openai: '',
},
baseUrls: {
anthropic: undefined,
},
};

/** Default project settings (empty - all settings are optional and fall back to global) */
Expand Down