feat: Implement API and UI for toggling Claude CLI between proxy and paid modes

This commit is contained in:
JEEL TILVA
2026-01-30 13:45:16 +05:30
parent b9b816e2bf
commit 7985524d49
7 changed files with 477 additions and 237 deletions

View File

@@ -17,7 +17,7 @@ import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import express from 'express';
import { getPublicConfig, saveConfig, config } from '../config.js';
import { DEFAULT_PORT, ACCOUNT_CONFIG_PATH, MAX_ACCOUNTS } from '../constants.js';
import { DEFAULT_PORT, ACCOUNT_CONFIG_PATH, MAX_ACCOUNTS, DEFAULT_PRESETS } from '../constants.js';
import { readClaudeConfig, updateClaudeConfig, replaceClaudeConfig, getClaudeConfigPath, readPresets, savePreset, deletePreset } from '../utils/claude-config.js';
import { logger } from '../utils/logger.js';
import { getAuthorizationUrl, completeOAuthFlow, startCallbackServer } from '../auth/oauth.js';
@@ -624,10 +624,85 @@ export function mountWebUI(app, dirname, accountManager) {
}
});
// ==========================================
// Claude CLI Mode Toggle API (Proxy/Paid)
// ==========================================
/**
* GET /api/claude/mode - Get current mode (proxy or paid)
* Returns 'proxy' if ANTHROPIC_BASE_URL is set to localhost, 'paid' otherwise
*/
app.get('/api/claude/mode', async (req, res) => {
try {
const claudeConfig = await readClaudeConfig();
const baseUrl = claudeConfig.env?.ANTHROPIC_BASE_URL || '';
// Determine mode based on ANTHROPIC_BASE_URL
const isProxy = baseUrl.includes('localhost') || baseUrl.includes('127.0.0.1');
res.json({
status: 'ok',
mode: isProxy ? 'proxy' : 'paid'
});
} catch (error) {
res.status(500).json({ status: 'error', error: error.message });
}
});
/**
* POST /api/claude/mode - Switch between proxy and paid mode
* Body: { mode: 'proxy' | 'paid' }
*
* When switching to 'paid' mode:
* - Removes the entire 'env' object from settings.json
* - Claude CLI uses its built-in defaults (official Anthropic API)
*
* When switching to 'proxy' mode:
* - Sets 'env' to the first default preset config (from constants.js)
*/
app.post('/api/claude/mode', async (req, res) => {
try {
const { mode } = req.body;
if (!mode || !['proxy', 'paid'].includes(mode)) {
return res.status(400).json({
status: 'error',
error: 'mode must be "proxy" or "paid"'
});
}
const claudeConfig = await readClaudeConfig();
if (mode === 'proxy') {
// Switch to proxy mode - use first default preset config (e.g., "Claude Thinking")
claudeConfig.env = { ...DEFAULT_PRESETS[0].config };
} else {
// Switch to paid mode - remove env entirely
delete claudeConfig.env;
}
// Save the updated config
const newConfig = await replaceClaudeConfig(claudeConfig);
logger.info(`[WebUI] Switched Claude CLI to ${mode} mode`);
res.json({
status: 'ok',
mode,
config: newConfig,
message: `Switched to ${mode === 'proxy' ? 'Proxy' : 'Paid (Anthropic API)'} mode. Restart Claude CLI to apply.`
});
} catch (error) {
logger.error('[WebUI] Error switching mode:', error);
res.status(500).json({ status: 'error', error: error.message });
}
});
// ==========================================
// Claude CLI Presets API
// ==========================================
/**
* GET /api/claude/presets - Get all saved presets
*/
@@ -841,18 +916,18 @@ export function mountWebUI(app, dirname, accountManager) {
const { callbackInput, state } = req.body;
if (!callbackInput || !state) {
return res.status(400).json({
status: 'error',
error: 'Missing callbackInput or state'
return res.status(400).json({
status: 'error',
error: 'Missing callbackInput or state'
});
}
// Find the pending flow
const flowData = pendingOAuthFlows.get(state);
if (!flowData) {
return res.status(400).json({
status: 'error',
error: 'OAuth flow not found. The account may have been already added via auto-callback. Please refresh the account list.'
return res.status(400).json({
status: 'error',
error: 'OAuth flow not found. The account may have been already added via auto-callback. Please refresh the account list.'
});
}
@@ -886,10 +961,10 @@ export function mountWebUI(app, dirname, accountManager) {
logger.success(`[WebUI] Account ${accountData.email} added via manual callback`);
res.json({
status: 'ok',
res.json({
status: 'ok',
email: accountData.email,
message: `Account ${accountData.email} added successfully`
message: `Account ${accountData.email} added successfully`
});
} catch (error) {
logger.error('[WebUI] Manual OAuth completion error:', error);