-
-
+ {isProviderEnabled('codex') && (
+
+ {provider === 'codex' && (
+
+ )}
+
+ )}
{/* Model Selection - Always reserve space to prevent jumping */}
diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx
index 0d828a8e8..bcdbc6d05 100644
--- a/src/components/Settings.jsx
+++ b/src/components/Settings.jsx
@@ -13,6 +13,7 @@ import GitSettings from './GitSettings';
import TasksSettings from './TasksSettings';
import LoginModal from './LoginModal';
import { authenticatedFetch } from '../utils/api';
+import { isProviderEnabled, getDefaultProvider } from '../utils/providers';
// New settings components
import AgentListItem from './settings/AgentListItem';
@@ -58,7 +59,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
const [mcpToolsLoading, setMcpToolsLoading] = useState({});
const [activeTab, setActiveTab] = useState(initialTab);
const [jsonValidationError, setJsonValidationError] = useState('');
- const [selectedAgent, setSelectedAgent] = useState('claude'); // 'claude', 'cursor', or 'codex'
+ const [selectedAgent, setSelectedAgent] = useState(() => getDefaultProvider()); // Only enabled providers
const [selectedCategory, setSelectedCategory] = useState('account'); // 'account', 'permissions', or 'mcp'
// Code Editor settings
@@ -1260,51 +1261,63 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
{/* Mobile: Horizontal Agent Tabs */}
-
setSelectedAgent('claude')}
- isMobile={true}
- />
- setSelectedAgent('cursor')}
- isMobile={true}
- />
- setSelectedAgent('codex')}
- isMobile={true}
- />
+ {isProviderEnabled('claude') && (
+ setSelectedAgent('claude')}
+ isMobile={true}
+ />
+ )}
+ {isProviderEnabled('cursor') && (
+ setSelectedAgent('cursor')}
+ isMobile={true}
+ />
+ )}
+ {isProviderEnabled('codex') && (
+ setSelectedAgent('codex')}
+ isMobile={true}
+ />
+ )}
{/* Desktop: Sidebar - Agent List */}
-
setSelectedAgent('claude')}
- />
- setSelectedAgent('cursor')}
- />
- setSelectedAgent('codex')}
- />
+ {isProviderEnabled('claude') && (
+ setSelectedAgent('claude')}
+ />
+ )}
+ {isProviderEnabled('cursor') && (
+ setSelectedAgent('cursor')}
+ />
+ )}
+ {isProviderEnabled('codex') && (
+ setSelectedAgent('codex')}
+ />
+ )}
diff --git a/src/utils/providers.js b/src/utils/providers.js
new file mode 100644
index 000000000..4d9271856
--- /dev/null
+++ b/src/utils/providers.js
@@ -0,0 +1,92 @@
+/**
+ * Provider configuration utility
+ *
+ * Controls which AI providers are available in the UI.
+ * Set VITE_ENABLED_PROVIDERS in .env to customize.
+ *
+ * @module utils/providers
+ * @example
+ * // Environment variable examples:
+ * // VITE_ENABLED_PROVIDERS=claude - Only Claude Code
+ * // VITE_ENABLED_PROVIDERS=claude,cursor - Claude and Cursor
+ * // VITE_ENABLED_PROVIDERS=claude,cursor,codex - All providers (default)
+ */
+
+/**
+ * List of all supported provider identifiers.
+ * @constant {string[]}
+ */
+const ALL_PROVIDERS = ['claude', 'cursor', 'codex'];
+
+/**
+ * Retrieves the list of enabled providers from environment variable.
+ *
+ * Parses the VITE_ENABLED_PROVIDERS environment variable and returns
+ * an array of valid provider names. Invalid provider names are filtered out.
+ *
+ * @function getEnabledProviders
+ * @returns {string[]} Array of enabled provider names (lowercase)
+ * @example
+ * // With VITE_ENABLED_PROVIDERS=claude,cursor
+ * getEnabledProviders(); // ['claude', 'cursor']
+ *
+ * @example
+ * // Without VITE_ENABLED_PROVIDERS set
+ * getEnabledProviders(); // ['claude', 'cursor', 'codex']
+ */
+export function getEnabledProviders() {
+ const envProviders = import.meta.env.VITE_ENABLED_PROVIDERS;
+
+ if (!envProviders) {
+ // Default: all providers enabled
+ return ALL_PROVIDERS;
+ }
+
+ // Parse comma-separated list and filter valid providers
+ const providers = envProviders
+ .split(',')
+ .map(p => p.trim().toLowerCase())
+ .filter(p => ALL_PROVIDERS.includes(p));
+
+ // Fallback to claude if no valid providers
+ return providers.length > 0 ? providers : ['claude'];
+}
+
+/**
+ * Checks if a specific provider is enabled.
+ *
+ * Safely handles null, undefined, and non-string inputs by returning false.
+ *
+ * @function isProviderEnabled
+ * @param {*} provider - Provider name to check (expected: 'claude', 'cursor', 'codex')
+ * @returns {boolean} True if provider is enabled, false otherwise
+ * @example
+ * // With VITE_ENABLED_PROVIDERS=claude
+ * isProviderEnabled('claude'); // true
+ * isProviderEnabled('cursor'); // false
+ * isProviderEnabled(null); // false
+ * isProviderEnabled(undefined); // false
+ */
+export function isProviderEnabled(provider) {
+ // Safely handle null, undefined, or non-string inputs
+ if (typeof provider !== 'string' || !provider.trim()) {
+ return false;
+ }
+ return getEnabledProviders().includes(provider.toLowerCase());
+}
+
+/**
+ * Gets the default provider (first enabled provider).
+ *
+ * Returns the first provider from the enabled list, falling back to 'claude'
+ * if somehow the list is empty (which shouldn't happen with current logic).
+ *
+ * @function getDefaultProvider
+ * @returns {string} The default provider name
+ * @example
+ * // With VITE_ENABLED_PROVIDERS=cursor,claude
+ * getDefaultProvider(); // 'cursor'
+ */
+export function getDefaultProvider() {
+ return getEnabledProviders()[0] || 'claude';
+}