diff --git a/src/components/CodexLogo.jsx b/src/components/CodexLogo.jsx
new file mode 100644
index 0000000..91759dd
--- /dev/null
+++ b/src/components/CodexLogo.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import { useTheme } from '../contexts/ThemeContext';
+
+const CodexLogo = ({ className = 'w-5 h-5' }) => {
+ const { isDarkMode } = useTheme();
+
+ return (
+
+ );
+};
+
+export default CodexLogo;
diff --git a/src/components/CursorLogo.jsx b/src/components/CursorLogo.jsx
index 18bda9d..1cac4d2 100644
--- a/src/components/CursorLogo.jsx
+++ b/src/components/CursorLogo.jsx
@@ -1,8 +1,15 @@
import React from 'react';
+import { useTheme } from '../contexts/ThemeContext';
const CursorLogo = ({ className = 'w-5 h-5' }) => {
+ const { isDarkMode } = useTheme();
+
return (
-
+
);
};
diff --git a/src/components/LoginModal.jsx b/src/components/LoginModal.jsx
index 42b7fc2..633aaf9 100644
--- a/src/components/LoginModal.jsx
+++ b/src/components/LoginModal.jsx
@@ -2,12 +2,12 @@ import { X } from 'lucide-react';
import StandaloneShell from './StandaloneShell';
/**
- * Reusable login modal component for Claude and Cursor CLI authentication
+ * Reusable login modal component for Claude, Cursor, and Codex CLI authentication
*
* @param {Object} props
* @param {boolean} props.isOpen - Whether the modal is visible
* @param {Function} props.onClose - Callback when modal is closed
- * @param {'claude'|'cursor'} props.provider - Which CLI provider to authenticate with
+ * @param {'claude'|'cursor'|'codex'} props.provider - Which CLI provider to authenticate with
* @param {Object} props.project - Project object containing name and path information
* @param {Function} props.onComplete - Callback when login process completes (receives exitCode)
* @param {string} props.customCommand - Optional custom command to override defaults
@@ -30,6 +30,8 @@ function LoginModal({
return 'claude setup-token --dangerously-skip-permissions';
case 'cursor':
return 'cursor-agent login';
+ case 'codex':
+ return 'codex login';
default:
return 'claude setup-token --dangerously-skip-permissions';
}
@@ -41,6 +43,8 @@ function LoginModal({
return 'Claude CLI Login';
case 'cursor':
return 'Cursor CLI Login';
+ case 'codex':
+ return 'Codex CLI Login';
default:
return 'CLI Login';
}
diff --git a/src/components/Onboarding.jsx b/src/components/Onboarding.jsx
index 33d61e5..2247c73 100644
--- a/src/components/Onboarding.jsx
+++ b/src/components/Onboarding.jsx
@@ -1,7 +1,8 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
import { ChevronRight, ChevronLeft, Check, GitBranch, User, Mail, LogIn, ExternalLink, Loader2 } from 'lucide-react';
import ClaudeLogo from './ClaudeLogo';
import CursorLogo from './CursorLogo';
+import CodexLogo from './CodexLogo';
import LoginModal from './LoginModal';
import { authenticatedFetch } from '../utils/api';
import { useAuth } from '../contexts/AuthContext';
@@ -13,10 +14,8 @@ const Onboarding = ({ onComplete }) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState('');
- // CLI authentication states
- const [showLoginModal, setShowLoginModal] = useState(false);
- const [loginProvider, setLoginProvider] = useState('');
- const [selectedProject, setSelectedProject] = useState({ name: 'default', fullPath: '' });
+ const [activeLoginProvider, setActiveLoginProvider] = useState(null);
+ const [selectedProject] = useState({ name: 'default', fullPath: '' });
const [claudeAuthStatus, setClaudeAuthStatus] = useState({
authenticated: false,
@@ -32,19 +31,21 @@ const Onboarding = ({ onComplete }) => {
error: null
});
+ const [codexAuthStatus, setCodexAuthStatus] = useState({
+ authenticated: false,
+ email: null,
+ loading: true,
+ error: null
+ });
+
const { user } = useAuth();
- // Load existing git config on mount
+ const prevActiveLoginProviderRef = useRef(undefined);
+
useEffect(() => {
loadGitConfig();
}, []);
- // Check authentication status on mount and when modal closes
- useEffect(() => {
- checkClaudeAuthStatus();
- checkCursorAuthStatus();
- }, []);
-
const loadGitConfig = async () => {
try {
const response = await authenticatedFetch('/api/user/git-config');
@@ -55,24 +56,22 @@ const Onboarding = ({ onComplete }) => {
}
} catch (error) {
console.error('Error loading git config:', error);
- // Silently fail - user can still enter config manually
}
};
- // Auto-check authentication status periodically when on CLI steps
useEffect(() => {
- if (currentStep === 1 || currentStep === 2) {
- const interval = setInterval(() => {
- if (currentStep === 1) {
- checkClaudeAuthStatus();
- } else if (currentStep === 2) {
- checkCursorAuthStatus();
- }
- }, 3000); // Check every 3 seconds
+ const prevProvider = prevActiveLoginProviderRef.current;
+ prevActiveLoginProviderRef.current = activeLoginProvider;
- return () => clearInterval(interval);
+ const isInitialMount = prevProvider === undefined;
+ const isModalClosing = prevProvider !== null && activeLoginProvider === null;
+
+ if (isInitialMount || isModalClosing) {
+ checkClaudeAuthStatus();
+ checkCursorAuthStatus();
+ checkCodexAuthStatus();
}
- }, [currentStep]);
+ }, [activeLoginProvider]);
const checkClaudeAuthStatus = async () => {
try {
@@ -134,22 +133,48 @@ const Onboarding = ({ onComplete }) => {
}
};
- const handleClaudeLogin = () => {
- setLoginProvider('claude');
- setShowLoginModal(true);
+ const checkCodexAuthStatus = async () => {
+ try {
+ const response = await authenticatedFetch('/api/cli/codex/status');
+ if (response.ok) {
+ const data = await response.json();
+ setCodexAuthStatus({
+ authenticated: data.authenticated,
+ email: data.email,
+ loading: false,
+ error: data.error || null
+ });
+ } else {
+ setCodexAuthStatus({
+ authenticated: false,
+ email: null,
+ loading: false,
+ error: 'Failed to check authentication status'
+ });
+ }
+ } catch (error) {
+ console.error('Error checking Codex auth status:', error);
+ setCodexAuthStatus({
+ authenticated: false,
+ email: null,
+ loading: false,
+ error: error.message
+ });
+ }
};
- const handleCursorLogin = () => {
- setLoginProvider('cursor');
- setShowLoginModal(true);
- };
+ const handleClaudeLogin = () => setActiveLoginProvider('claude');
+ const handleCursorLogin = () => setActiveLoginProvider('cursor');
+ const handleCodexLogin = () => setActiveLoginProvider('codex');
const handleLoginComplete = (exitCode) => {
if (exitCode === 0) {
- if (loginProvider === 'claude') {
+ if (activeLoginProvider === 'claude') {
checkClaudeAuthStatus();
- } else if (loginProvider === 'cursor') {
+ } else if (activeLoginProvider === 'cursor') {
checkCursorAuthStatus();
+ } else if (activeLoginProvider === 'codex') {
+ checkCodexAuthStatus();
}
}
};
@@ -194,7 +219,6 @@ const Onboarding = ({ onComplete }) => {
return;
}
- // Other steps: just move forward
setCurrentStep(currentStep + 1);
};
@@ -208,7 +232,6 @@ const Onboarding = ({ onComplete }) => {
setError('');
try {
- // Mark onboarding as complete
const response = await authenticatedFetch('/api/user/complete-onboarding', {
method: 'POST'
});
@@ -218,7 +241,6 @@ const Onboarding = ({ onComplete }) => {
throw new Error(data.error || 'Failed to complete onboarding');
}
- // Call the onComplete callback
if (onComplete) {
onComplete();
}
@@ -237,15 +259,9 @@ const Onboarding = ({ onComplete }) => {
required: true
},
{
- title: 'Claude Code CLI',
- description: 'Connect your Claude Code account',
- icon: () =>
,
- required: false
- },
- {
- title: 'Cursor CLI',
- description: 'Connect your Cursor account',
- icon: () =>
,
+ title: 'Connect Agents',
+ description: 'Connect your AI coding assistants',
+ icon: LogIn,
required: false
}
];
@@ -312,135 +328,117 @@ const Onboarding = ({ onComplete }) => {
case 1:
return (
-
-
-
-
-
Claude Code CLI
+
+
Connect Your AI Agents
- Connect your Claude account to enable AI-powered coding features
+ Login to one or more AI coding assistants. All are optional.
- {/* Auth Status Card */}
-
-
-
-
-
- {claudeAuthStatus.loading ? 'Checking...' :
- claudeAuthStatus.authenticated ? 'Connected' : 'Not Connected'}
-
+ {/* Agent Cards Grid */}
+
+ {/* Claude */}
+
+
+
+
+
+
+
+
+ Claude Code
+ {claudeAuthStatus.authenticated && }
+
+
+ {claudeAuthStatus.loading ? 'Checking...' :
+ claudeAuthStatus.authenticated ? claudeAuthStatus.email || 'Connected' : 'Not connected'}
+
+
+
+ {!claudeAuthStatus.authenticated && !claudeAuthStatus.loading && (
+
+ Login
+
+ )}
- {claudeAuthStatus.authenticated && (
-
- )}
- {claudeAuthStatus.authenticated && claudeAuthStatus.email && (
-
- Signed in as: {claudeAuthStatus.email}
-
- )}
-
- {!claudeAuthStatus.authenticated && (
- <>
-
- Click the button below to authenticate with Claude Code CLI. A terminal will open with authentication instructions.
-
-
-
- Login to Claude Code
-
-
- Or manually run: claude auth login
-
- >
- )}
-
- {claudeAuthStatus.error && !claudeAuthStatus.authenticated && (
-
-
{claudeAuthStatus.error}
+ {/* Cursor */}
+
+
+
+
+
+
+
+
+ Cursor
+ {cursorAuthStatus.authenticated && }
+
+
+ {cursorAuthStatus.loading ? 'Checking...' :
+ cursorAuthStatus.authenticated ? cursorAuthStatus.email || 'Connected' : 'Not connected'}
+
+
+
+ {!cursorAuthStatus.authenticated && !cursorAuthStatus.loading && (
+
+ Login
+
+ )}
- )}
-
-
-
-
This step is optional. You can skip and configure it later in Settings.
-
-
- );
-
- case 2:
- return (
-
-
-
-
-
-
Cursor CLI
-
- Connect your Cursor account to enable AI-powered features
-
-
-
- {/* Auth Status Card */}
-
-
-
-
-
- {cursorAuthStatus.loading ? 'Checking...' :
- cursorAuthStatus.authenticated ? 'Connected' : 'Not Connected'}
-
-
- {cursorAuthStatus.authenticated && (
-
- )}
- {cursorAuthStatus.authenticated && cursorAuthStatus.email && (
-
- Signed in as: {cursorAuthStatus.email}
-
- )}
-
- {!cursorAuthStatus.authenticated && (
- <>
-
- Click the button below to authenticate with Cursor CLI. A terminal will open with authentication instructions.
-
-
-
- Login to Cursor
-
-
- Or manually run: cursor auth login
-
- >
- )}
-
- {cursorAuthStatus.error && !cursorAuthStatus.authenticated && (
-
-
{cursorAuthStatus.error}
+ {/* Codex */}
+
+
+
+
+
+
+
+
+ OpenAI Codex
+ {codexAuthStatus.authenticated && }
+
+
+ {codexAuthStatus.loading ? 'Checking...' :
+ codexAuthStatus.authenticated ? codexAuthStatus.email || 'Connected' : 'Not connected'}
+
+
+
+ {!codexAuthStatus.authenticated && !codexAuthStatus.loading && (
+
+ Login
+
+ )}
- )}
+
-
-
This step is optional. You can skip and configure it later in Settings.
+
+
You can configure these later in Settings.
);
@@ -455,8 +453,7 @@ const Onboarding = ({ onComplete }) => {
case 0:
return gitName.trim() && gitEmail.trim() && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(gitEmail);
case 1:
- case 2:
- return true; // CLI steps are optional
+ return true;
default:
return false;
}
@@ -572,14 +569,13 @@ const Onboarding = ({ onComplete }) => {
- {/* Login Modal */}
- {showLoginModal && (
+ {activeLoginProvider && (
setShowLoginModal(false)}
- provider={loginProvider}
+ isOpen={!!activeLoginProvider}
+ onClose={() => setActiveLoginProvider(null)}
+ provider={activeLoginProvider}
project={selectedProject}
- onLoginComplete={handleLoginComplete}
+ onComplete={handleLoginComplete}
/>
)}
>
diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx
index b3760ac..a19abbe 100644
--- a/src/components/Settings.jsx
+++ b/src/components/Settings.jsx
@@ -6,13 +6,20 @@ import { X, Plus, Settings as SettingsIcon, Shield, AlertTriangle, Moon, Sun, Se
import { useTheme } from '../contexts/ThemeContext';
import ClaudeLogo from './ClaudeLogo';
import CursorLogo from './CursorLogo';
+import CodexLogo from './CodexLogo';
import CredentialsSettings from './CredentialsSettings';
import GitSettings from './GitSettings';
import TasksSettings from './TasksSettings';
import LoginModal from './LoginModal';
import { authenticatedFetch } from '../utils/api';
-function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
+// New settings components
+import AgentListItem from './settings/AgentListItem';
+import AccountContent from './settings/AccountContent';
+import PermissionsContent from './settings/PermissionsContent';
+import McpServersContent from './settings/McpServersContent';
+
+function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }) {
const { isDarkMode, toggleDarkMode } = useTheme();
const [allowedTools, setAllowedTools] = useState([]);
const [disallowedTools, setDisallowedTools] = useState([]);
@@ -48,7 +55,8 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
const [mcpToolsLoading, setMcpToolsLoading] = useState({});
const [activeTab, setActiveTab] = useState(initialTab);
const [jsonValidationError, setJsonValidationError] = useState('');
- const [toolsProvider, setToolsProvider] = useState('claude'); // 'claude' or 'cursor'
+ const [selectedAgent, setSelectedAgent] = useState('claude'); // 'claude', 'cursor', or 'codex'
+ const [selectedCategory, setSelectedCategory] = useState('account'); // 'account', 'permissions', or 'mcp'
// Code Editor settings
const [codeEditorTheme, setCodeEditorTheme] = useState(() =>
@@ -75,6 +83,22 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
const [newCursorDisallowedCommand, setNewCursorDisallowedCommand] = useState('');
const [cursorMcpServers, setCursorMcpServers] = useState([]);
+ // Codex-specific states
+ const [codexMcpServers, setCodexMcpServers] = useState([]);
+ const [codexPermissionMode, setCodexPermissionMode] = useState('default');
+ const [showCodexMcpForm, setShowCodexMcpForm] = useState(false);
+ const [codexMcpFormData, setCodexMcpFormData] = useState({
+ name: '',
+ type: 'stdio',
+ config: {
+ command: '',
+ args: [],
+ env: {}
+ }
+ });
+ const [editingCodexMcpServer, setEditingCodexMcpServer] = useState(null);
+ const [codexMcpLoading, setCodexMcpLoading] = useState(false);
+
const [showLoginModal, setShowLoginModal] = useState(false);
const [loginProvider, setLoginProvider] = useState('');
const [selectedProject, setSelectedProject] = useState(null);
@@ -91,6 +115,12 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
loading: true,
error: null
});
+ const [codexAuthStatus, setCodexAuthStatus] = useState({
+ authenticated: false,
+ email: null,
+ loading: true,
+ error: null
+ });
// Common tool patterns for Claude
const commonTools = [
@@ -141,7 +171,43 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
console.error('Error fetching Cursor MCP servers:', error);
}
};
-
+
+ const fetchCodexMcpServers = async () => {
+ try {
+ const configResponse = await authenticatedFetch('/api/codex/mcp/config/read');
+
+ if (configResponse.ok) {
+ const configData = await configResponse.json();
+ if (configData.success && configData.servers) {
+ setCodexMcpServers(configData.servers);
+ return;
+ }
+ }
+
+ const cliResponse = await authenticatedFetch('/api/codex/mcp/cli/list');
+
+ if (cliResponse.ok) {
+ const cliData = await cliResponse.json();
+ if (cliData.success && cliData.servers) {
+ const servers = cliData.servers.map(server => ({
+ id: server.name,
+ name: server.name,
+ type: server.type || 'stdio',
+ scope: 'user',
+ config: {
+ command: server.command || '',
+ args: server.args || [],
+ env: server.env || {}
+ }
+ }));
+ setCodexMcpServers(servers);
+ }
+ }
+ } catch (error) {
+ console.error('Error fetching Codex MCP servers:', error);
+ }
+ };
+
// MCP API functions
const fetchMcpServers = async () => {
try {
@@ -303,11 +369,134 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
}
};
+ const saveCodexMcpServer = async (serverData) => {
+ try {
+ if (editingCodexMcpServer) {
+ await deleteCodexMcpServer(editingCodexMcpServer.id);
+ }
+
+ const response = await authenticatedFetch('/api/codex/mcp/cli/add', {
+ method: 'POST',
+ body: JSON.stringify({
+ name: serverData.name,
+ command: serverData.config?.command,
+ args: serverData.config?.args || [],
+ env: serverData.config?.env || {}
+ })
+ });
+
+ if (response.ok) {
+ const result = await response.json();
+ if (result.success) {
+ await fetchCodexMcpServers();
+ return true;
+ } else {
+ throw new Error(result.error || 'Failed to save Codex MCP server');
+ }
+ } else {
+ const error = await response.json();
+ throw new Error(error.error || 'Failed to save server');
+ }
+ } catch (error) {
+ console.error('Error saving Codex MCP server:', error);
+ throw error;
+ }
+ };
+
+ const deleteCodexMcpServer = async (serverId) => {
+ try {
+ const response = await authenticatedFetch(`/api/codex/mcp/cli/remove/${serverId}`, {
+ method: 'DELETE'
+ });
+
+ if (response.ok) {
+ const result = await response.json();
+ if (result.success) {
+ await fetchCodexMcpServers();
+ return true;
+ } else {
+ throw new Error(result.error || 'Failed to delete Codex MCP server');
+ }
+ } else {
+ const error = await response.json();
+ throw new Error(error.error || 'Failed to delete server');
+ }
+ } catch (error) {
+ console.error('Error deleting Codex MCP server:', error);
+ throw error;
+ }
+ };
+
+ const resetCodexMcpForm = () => {
+ setCodexMcpFormData({
+ name: '',
+ type: 'stdio',
+ config: {
+ command: '',
+ args: [],
+ env: {}
+ }
+ });
+ setEditingCodexMcpServer(null);
+ setShowCodexMcpForm(false);
+ };
+
+ const openCodexMcpForm = (server = null) => {
+ if (server) {
+ setEditingCodexMcpServer(server);
+ setCodexMcpFormData({
+ name: server.name,
+ type: server.type || 'stdio',
+ config: {
+ command: server.config?.command || '',
+ args: server.config?.args || [],
+ env: server.config?.env || {}
+ }
+ });
+ } else {
+ resetCodexMcpForm();
+ }
+ setShowCodexMcpForm(true);
+ };
+
+ const handleCodexMcpSubmit = async (e) => {
+ e.preventDefault();
+ setCodexMcpLoading(true);
+
+ try {
+ if (editingCodexMcpServer) {
+ // Delete old server first, then add new one
+ await deleteCodexMcpServer(editingCodexMcpServer.name);
+ }
+ await saveCodexMcpServer(codexMcpFormData);
+ resetCodexMcpForm();
+ setSaveStatus('success');
+ } catch (error) {
+ alert(`Error: ${error.message}`);
+ setSaveStatus('error');
+ } finally {
+ setCodexMcpLoading(false);
+ }
+ };
+
+ const handleCodexMcpDelete = async (serverName) => {
+ if (confirm('Are you sure you want to delete this MCP server?')) {
+ try {
+ await deleteCodexMcpServer(serverName);
+ setSaveStatus('success');
+ } catch (error) {
+ alert(`Error: ${error.message}`);
+ setSaveStatus('error');
+ }
+ }
+ };
+
useEffect(() => {
if (isOpen) {
loadSettings();
checkClaudeAuthStatus();
checkCursorAuthStatus();
+ checkCodexAuthStatus();
setActiveTab(initialTab);
}
}, [isOpen, initialTab]);
@@ -360,7 +549,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
// Load Cursor settings from localStorage
const savedCursorSettings = localStorage.getItem('cursor-tools-settings');
-
+
if (savedCursorSettings) {
const cursorSettings = JSON.parse(savedCursorSettings);
setCursorAllowedCommands(cursorSettings.allowedCommands || []);
@@ -373,11 +562,24 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
setCursorSkipPermissions(false);
}
+ // Load Codex settings from localStorage
+ const savedCodexSettings = localStorage.getItem('codex-settings');
+
+ if (savedCodexSettings) {
+ const codexSettings = JSON.parse(savedCodexSettings);
+ setCodexPermissionMode(codexSettings.permissionMode || 'default');
+ } else {
+ setCodexPermissionMode('default');
+ }
+
// Load MCP servers from API
await fetchMcpServers();
-
+
// Load Cursor MCP servers
await fetchCursorMcpServers();
+
+ // Load Codex MCP servers
+ await fetchCodexMcpServers();
} catch (error) {
console.error('Error loading tool settings:', error);
setAllowedTools([]);
@@ -448,6 +650,38 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
});
}
};
+
+ const checkCodexAuthStatus = async () => {
+ try {
+ const response = await authenticatedFetch('/api/cli/codex/status');
+
+ if (response.ok) {
+ const data = await response.json();
+ setCodexAuthStatus({
+ authenticated: data.authenticated,
+ email: data.email,
+ loading: false,
+ error: data.error || null
+ });
+ } else {
+ setCodexAuthStatus({
+ authenticated: false,
+ email: null,
+ loading: false,
+ error: 'Failed to check authentication status'
+ });
+ }
+ } catch (error) {
+ console.error('Error checking Codex auth status:', error);
+ setCodexAuthStatus({
+ authenticated: false,
+ email: null,
+ loading: false,
+ error: error.message
+ });
+ }
+ };
+
const handleClaudeLogin = () => {
setLoginProvider('claude');
setSelectedProject(projects?.[0] || { name: 'default', fullPath: process.cwd() });
@@ -460,6 +694,12 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
setShowLoginModal(true);
};
+ const handleCodexLogin = () => {
+ setLoginProvider('codex');
+ setSelectedProject(projects?.[0] || { name: 'default', fullPath: process.cwd() });
+ setShowLoginModal(true);
+ };
+
const handleLoginComplete = (exitCode) => {
if (exitCode === 0) {
setSaveStatus('success');
@@ -468,6 +708,8 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
checkClaudeAuthStatus();
} else if (loginProvider === 'cursor') {
checkCursorAuthStatus();
+ } else if (loginProvider === 'codex') {
+ checkCodexAuthStatus();
}
}
};
@@ -493,11 +735,18 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
skipPermissions: cursorSkipPermissions,
lastUpdated: new Date().toISOString()
};
-
+
+ // Save Codex settings
+ const codexSettings = {
+ permissionMode: codexPermissionMode,
+ lastUpdated: new Date().toISOString()
+ };
+
// Save to localStorage
localStorage.setItem('claude-settings', JSON.stringify(claudeSettings));
localStorage.setItem('cursor-tools-settings', JSON.stringify(cursorSettings));
-
+ localStorage.setItem('codex-settings', JSON.stringify(codexSettings));
+
setSaveStatus('success');
setTimeout(() => {
@@ -716,14 +965,14 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
setActiveTab('tools')}
+ onClick={() => setActiveTab('agents')}
className={`px-4 py-3 text-sm font-medium border-b-2 transition-colors ${
- activeTab === 'tools'
+ activeTab === 'agents'
? 'border-blue-600 text-blue-600 dark:text-blue-400'
: 'border-transparent text-muted-foreground hover:text-foreground'
}`}
>
- Tools
+ Agents
setActiveTab('appearance')}
@@ -997,474 +1246,197 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
{/* Git Tab */}
{activeTab === 'git' && }
- {/* Tools Tab */}
- {activeTab === 'tools' && (
-
-
- {/* Provider Tabs */}
-
-
-
setToolsProvider('claude')}
- className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
- toolsProvider === 'claude'
- ? 'border-blue-600 text-blue-600 dark:text-blue-400'
- : 'border-transparent text-muted-foreground hover:text-foreground'
- }`}
- >
-
-
-
Claude
+ {/* Agents Tab */}
+ {activeTab === 'agents' && (
+
+ {/* Mobile: Horizontal Agent Tabs */}
+
+
+
setSelectedAgent('claude')}
+ isMobile={true}
+ />
+ setSelectedAgent('cursor')}
+ isMobile={true}
+ />
+ setSelectedAgent('codex')}
+ isMobile={true}
+ />
-
-
setToolsProvider('cursor')}
- className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
- toolsProvider === 'cursor'
- ? 'border-purple-600 text-purple-600 dark:text-purple-400'
- : 'border-transparent text-muted-foreground hover:text-foreground'
- }`}
- >
-
-
- Cursor
-
-
-
-
-
- {/* Claude Content */}
- {toolsProvider === 'claude' && (
-
-
- {/* Skip Permissions */}
-
-
-
-
- Permission Settings
-
-
-
-
- setSkipPermissions(e.target.checked)}
- className="w-4 h-4 text-blue-600 bg-gray-100 dark:bg-gray-700 border-gray-300 dark:border-gray-600 rounded focus:ring-blue-500 focus:ring-2 checked:bg-blue-600 dark:checked:bg-blue-600"
- />
-
-
- Skip permission prompts (use with caution)
-
-
- Equivalent to --dangerously-skip-permissions flag
-
-
-
-
-
+
-
-
-
-
- Authentication
-
-
-
-
-
- {claudeAuthStatus.loading ? (
-
- Checking authentication...
-
- ) : claudeAuthStatus.authenticated ? (
-
-
- ✓ Logged in
-
- {claudeAuthStatus.email && (
-
- as {claudeAuthStatus.email}
-
- )}
-
- ) : (
-
- Not authenticated
-
+ {/* Desktop: Sidebar - Agent List */}
+
+
+
setSelectedAgent('claude')}
+ />
+ setSelectedAgent('cursor')}
+ />
+ setSelectedAgent('codex')}
+ />
+
+
+
+ {/* Main Panel */}
+
+ {/* Category Tabs */}
+
+
+ setSelectedCategory('account')}
+ className={`px-3 md:px-4 py-2 md:py-3 text-xs md:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
+ selectedCategory === 'account'
+ ? 'border-blue-600 text-blue-600 dark:text-blue-400'
+ : 'border-transparent text-muted-foreground hover:text-foreground'
+ }`}
+ >
+ Account
+
+ setSelectedCategory('permissions')}
+ className={`px-3 md:px-4 py-2 md:py-3 text-xs md:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
+ selectedCategory === 'permissions'
+ ? 'border-blue-600 text-blue-600 dark:text-blue-400'
+ : 'border-transparent text-muted-foreground hover:text-foreground'
+ }`}
+ >
+ Permissions
+
+ setSelectedCategory('mcp')}
+ className={`px-3 md:px-4 py-2 md:py-3 text-xs md:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
+ selectedCategory === 'mcp'
+ ? 'border-blue-600 text-blue-600 dark:text-blue-400'
+ : 'border-transparent text-muted-foreground hover:text-foreground'
+ }`}
+ >
+ MCP Servers
+
+
+
+
+ {/* Category Content */}
+
+ {/* Account Category */}
+ {selectedCategory === 'account' && (
+
+ )}
+
+ {/* Permissions Category */}
+ {selectedCategory === 'permissions' && selectedAgent === 'claude' && (
+
+ )}
+
+ {selectedCategory === 'permissions' && selectedAgent === 'cursor' && (
+
+ )}
+
+ {selectedCategory === 'permissions' && selectedAgent === 'codex' && (
+
+ )}
+
+ {/* MCP Servers Category */}
+ {selectedCategory === 'mcp' && selectedAgent === 'claude' && (
+
openMcpForm()}
+ onEdit={(server) => openMcpForm(server)}
+ onDelete={(serverId, scope) => handleMcpDelete(serverId, scope)}
+ onTest={(serverId, scope) => handleMcpTest(serverId, scope)}
+ onDiscoverTools={(serverId, scope) => handleMcpToolsDiscovery(serverId, scope)}
+ testResults={mcpTestResults}
+ serverTools={mcpServerTools}
+ toolsLoading={mcpToolsLoading}
+ />
+ )}
+
+ {selectedCategory === 'mcp' && selectedAgent === 'cursor' && (
+ {/* TODO: Add cursor MCP form */}}
+ onEdit={(server) => {/* TODO: Edit cursor MCP form */}}
+ onDelete={(serverId) => {/* TODO: Delete cursor MCP */}}
+ />
+ )}
+
+ {selectedCategory === 'mcp' && selectedAgent === 'codex' && (
+ openCodexMcpForm()}
+ onEdit={(server) => openCodexMcpForm(server)}
+ onDelete={(serverId) => handleCodexMcpDelete(serverId)}
+ />
)}
-
-
-
-
- Claude CLI Login
-
-
- {claudeAuthStatus.authenticated
- ? 'Re-authenticate or switch accounts'
- : 'Sign in to your Claude account to enable AI features'}
-
-
-
-
- Login
-
-
-
-
- {/* Allowed Tools */}
-
-
-
-
- Allowed Tools
-
-
-
- Tools that are automatically allowed without prompting for permission
-
-
-
-
setNewAllowedTool(e.target.value)}
- placeholder='e.g., "Bash(git log:*)" or "Write"'
- onKeyPress={(e) => {
- if (e.key === 'Enter') {
- addAllowedTool(newAllowedTool);
- }
- }}
- className="flex-1 h-10 touch-manipulation"
- style={{ fontSize: '16px' }}
- />
-
addAllowedTool(newAllowedTool)}
- disabled={!newAllowedTool}
- size="sm"
- className="h-10 px-4 touch-manipulation"
- >
-
- Add Tool
-
-
-
- {/* Common tools quick add */}
-
-
- Quick add common tools:
-
-
- {commonTools.map(tool => (
- addAllowedTool(tool)}
- disabled={allowedTools.includes(tool)}
- className="text-xs h-8 touch-manipulation truncate"
- >
- {tool}
-
- ))}
-
-
-
-
- {allowedTools.map(tool => (
-
-
- {tool}
-
- removeAllowedTool(tool)}
- className="text-green-600 hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
- >
-
-
-
- ))}
- {allowedTools.length === 0 && (
-
- No allowed tools configured
-
- )}
-
-
-
- {/* Disallowed Tools */}
-
-
-
-
- Disallowed Tools
-
-
-
- Tools that are automatically blocked without prompting for permission
-
-
-
-
setNewDisallowedTool(e.target.value)}
- placeholder='e.g., "Bash(rm:*)" or "Write"'
- onKeyPress={(e) => {
- if (e.key === 'Enter') {
- addDisallowedTool(newDisallowedTool);
- }
- }}
- className="flex-1 h-10 touch-manipulation"
- style={{ fontSize: '16px' }}
- />
-
addDisallowedTool(newDisallowedTool)}
- disabled={!newDisallowedTool}
- size="sm"
- className="h-10 px-4 touch-manipulation"
- >
-
- Add Tool
-
-
-
-
- {disallowedTools.map(tool => (
-
-
- {tool}
-
- removeDisallowedTool(tool)}
- className="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
- >
-
-
-
- ))}
- {disallowedTools.length === 0 && (
-
- No disallowed tools configured
-
- )}
-
-
-
- {/* Help Section */}
-
-
- Tool Pattern Examples:
-
-
- "Bash(git log:*)" - Allow all git log commands
- "Bash(git diff:*)" - Allow all git diff commands
- "Write" - Allow all Write tool usage
- "Read" - Allow all Read tool usage
- "Bash(rm:*)" - Block all rm commands (dangerous)
-
-
-
- {/* MCP Server Management */}
-
-
-
-
- MCP Servers
-
-
-
-
- Model Context Protocol servers provide additional tools and data sources to Claude
-
-
-
-
-
openMcpForm()}
- className="bg-purple-600 hover:bg-purple-700 text-white"
- size="sm"
- >
-
- Add MCP Server
-
-
-
- {/* MCP Servers List */}
-
- {mcpServers.map(server => (
-
-
-
-
- {getTransportIcon(server.type)}
- {server.name}
-
- {server.type}
-
-
- {server.scope === 'local' ? '📁 local' : server.scope === 'user' ? '👤 user' : server.scope}
-
- {server.projectPath && (
-
- {server.projectPath.split('/').pop()}
-
- )}
-
-
-
- {server.type === 'stdio' && server.config.command && (
-
Command: {server.config.command}
- )}
- {(server.type === 'sse' || server.type === 'http') && server.config.url && (
-
URL: {server.config.url}
- )}
- {server.config.args && server.config.args.length > 0 && (
-
Args: {server.config.args.join(' ')}
- )}
- {server.config.env && Object.keys(server.config.env).length > 0 && (
-
Environment: {Object.entries(server.config.env).map(([k, v]) => `${k}=${v}`).join(', ')}
- )}
- {server.raw && (
-
- View full config
-
- {JSON.stringify(server.raw, null, 2)}
-
-
- )}
-
-
- {/* Test Results */}
- {mcpTestResults[server.id] && (
-
-
{mcpTestResults[server.id].message}
- {mcpTestResults[server.id].details && mcpTestResults[server.id].details.length > 0 && (
-
- {mcpTestResults[server.id].details.map((detail, i) => (
- • {detail}
- ))}
-
- )}
-
- )}
-
- {/* Tools Discovery Results */}
- {mcpServerTools[server.id] && (
-
-
Available Tools & Resources
-
- {mcpServerTools[server.id].tools && mcpServerTools[server.id].tools.length > 0 && (
-
-
Tools ({mcpServerTools[server.id].tools.length}):
-
-
- )}
-
- {mcpServerTools[server.id].resources && mcpServerTools[server.id].resources.length > 0 && (
-
-
Resources ({mcpServerTools[server.id].resources.length}):
-
-
- )}
-
- {mcpServerTools[server.id].prompts && mcpServerTools[server.id].prompts.length > 0 && (
-
-
Prompts ({mcpServerTools[server.id].prompts.length}):
-
-
- )}
-
- {(!mcpServerTools[server.id].tools || mcpServerTools[server.id].tools.length === 0) &&
- (!mcpServerTools[server.id].resources || mcpServerTools[server.id].resources.length === 0) &&
- (!mcpServerTools[server.id].prompts || mcpServerTools[server.id].prompts.length === 0) && (
-
No tools, resources, or prompts discovered
- )}
-
- )}
-
-
-
- openMcpForm(server)}
- variant="ghost"
- size="sm"
- className="text-gray-600 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
- title="Edit server"
- >
-
-
- handleMcpDelete(server.id, server.scope)}
- variant="ghost"
- size="sm"
- className="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
- title="Delete server"
- >
-
-
-
-
-
- ))}
- {mcpServers.length === 0 && (
-
- No MCP servers configured
-
- )}
-
-
+ )}
{/* MCP Server Form Modal */}
{showMcpForm && (
@@ -1816,272 +1788,103 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
)}
-
- )}
-
- {/* Cursor Content */}
- {toolsProvider === 'cursor' && (
-
-
- {/* Skip Permissions for Cursor */}
-
-
-
+
+ {/* Codex MCP Server Form Modal */}
+ {showCodexMcpForm && (
+
+
+
- Cursor Permission Settings
+ {editingCodexMcpServer ? 'Edit MCP Server' : 'Add MCP Server'}
+
+
+
-
-
-
-
-
-
- Authentication
-
-
-
-
-
- {cursorAuthStatus.loading ? (
-
- Checking authentication...
-
- ) : cursorAuthStatus.authenticated ? (
-
-
- ✓ Logged in
-
- {cursorAuthStatus.email && (
-
- as {cursorAuthStatus.email}
-
- )}
-
- ) : (
-
- Not authenticated
-
- )}
-
-
-
-
-
- Cursor CLI Login
-
-
- {cursorAuthStatus.authenticated
- ? 'Re-authenticate or switch accounts'
- : 'Sign in to your Cursor account to enable AI features'}
-
-
-
-
- Login
-
-
-
-
- {/* Allowed Shell Commands */}
-
-
-
-
- Allowed Shell Commands
-
-
-
- Shell commands that are automatically allowed without prompting for permission
-
-
-
-
setNewCursorCommand(e.target.value)}
- placeholder='e.g., "Shell(ls)" or "Shell(git status)"'
- onKeyPress={(e) => {
- if (e.key === 'Enter') {
- if (newCursorCommand && !cursorAllowedCommands.includes(newCursorCommand)) {
- setCursorAllowedCommands([...cursorAllowedCommands, newCursorCommand]);
- setNewCursorCommand('');
- }
- }
- }}
- className="flex-1 h-10 touch-manipulation"
- style={{ fontSize: '16px' }}
- />
-
{
- if (newCursorCommand && !cursorAllowedCommands.includes(newCursorCommand)) {
- setCursorAllowedCommands([...cursorAllowedCommands, newCursorCommand]);
- setNewCursorCommand('');
- }
- }}
- disabled={!newCursorCommand}
- size="sm"
- className="h-10 px-4 touch-manipulation"
- >
-
- Add Command
-
-
+
+
+ Command *
+
+ setCodexMcpFormData(prev => ({
+ ...prev,
+ config: { ...prev.config, command: e.target.value }
+ }))}
+ placeholder="npx @my-org/mcp-server"
+ required
+ />
+
- {/* Common commands quick add */}
-
-
- Quick add common commands:
-
-
- {commonCursorCommands.map(cmd => (
-
{
- if (!cursorAllowedCommands.includes(cmd)) {
- setCursorAllowedCommands([...cursorAllowedCommands, cmd]);
+
+
+ Arguments (one per line)
+
+
+
+
+
+ Environment Variables (KEY=VALUE, one per line)
+
+
-
-
- {cursorAllowedCommands.map(cmd => (
-
-
- {cmd}
-
- setCursorAllowedCommands(cursorAllowedCommands.filter(c => c !== cmd))}
- className="text-green-600 hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
- >
-
-
-
- ))}
- {cursorAllowedCommands.length === 0 && (
-
- No allowed shell commands configured
-
- )}
-
+
+
+ Cancel
+
+
+ {codexMcpLoading ? 'Saving...' : (editingCodexMcpServer ? 'Update Server' : 'Add Server')}
+
+
+
-
- {/* Disallowed Shell Commands */}
-
-
-
-
- Disallowed Shell Commands
-
-
-
- Shell commands that should always be denied
-
-
-
-
setNewCursorDisallowedCommand(e.target.value)}
- placeholder='e.g., "Shell(rm -rf)" or "Shell(sudo)"'
- onKeyPress={(e) => {
- if (e.key === 'Enter') {
- if (newCursorDisallowedCommand && !cursorDisallowedCommands.includes(newCursorDisallowedCommand)) {
- setCursorDisallowedCommands([...cursorDisallowedCommands, newCursorDisallowedCommand]);
- setNewCursorDisallowedCommand('');
- }
- }
- }}
- className="flex-1 h-10 touch-manipulation"
- style={{ fontSize: '16px' }}
- />
-
{
- if (newCursorDisallowedCommand && !cursorDisallowedCommands.includes(newCursorDisallowedCommand)) {
- setCursorDisallowedCommands([...cursorDisallowedCommands, newCursorDisallowedCommand]);
- setNewCursorDisallowedCommand('');
- }
- }}
- disabled={!newCursorDisallowedCommand}
- size="sm"
- className="h-10 px-4 touch-manipulation"
- >
-
- Add Command
-
-
-
-
- {cursorDisallowedCommands.map(cmd => (
-
-
- {cmd}
-
- setCursorDisallowedCommands(cursorDisallowedCommands.filter(c => c !== cmd))}
- className="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
- >
-
-
-
- ))}
- {cursorDisallowedCommands.length === 0 && (
-
- No disallowed shell commands configured
-
- )}
-
-
-
- {/* Help Section */}
-
-
- Cursor Shell Command Examples:
-
-
- "Shell(ls)" - Allow ls command
- "Shell(git status)" - Allow git status command
- "Shell(mkdir)" - Allow mkdir command
- "-f" flag - Skip all permission prompts (dangerous)
-
-
-
- )}
)}
@@ -2149,6 +1952,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
{/* Login Modal */}
setShowLoginModal(false)}
provider={loginProvider}
diff --git a/src/components/Shell.jsx b/src/components/Shell.jsx
index c095836..9a74436 100644
--- a/src/components/Shell.jsx
+++ b/src/components/Shell.jsx
@@ -220,7 +220,6 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
return;
}
- console.log('[Shell] Terminal initializing, mounting component');
terminal.current = new Terminal({
cursorBlink: true,
@@ -346,7 +345,6 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
}
return () => {
- console.log('[Shell] Terminal cleanup, unmounting component');
resizeObserver.disconnect();
if (ws.current && (ws.current.readyState === WebSocket.OPEN || ws.current.readyState === WebSocket.CONNECTING)) {
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx
index 926379f..328029f 100644
--- a/src/components/Sidebar.jsx
+++ b/src/components/Sidebar.jsx
@@ -9,6 +9,7 @@ import { FolderOpen, Folder, Plus, MessageSquare, Clock, ChevronDown, ChevronRig
import { cn } from '../lib/utils';
import ClaudeLogo from './ClaudeLogo';
import CursorLogo from './CursorLogo.jsx';
+import CodexLogo from './CodexLogo.jsx';
import TaskIndicator from './TaskIndicator';
import ProjectCreationWizard from './ProjectCreationWizard';
import { api } from '../utils/api';
@@ -220,12 +221,17 @@ function Sidebar({
// Helper function to get all sessions for a project (initial + additional)
const getAllSessions = (project) => {
- // Combine Claude and Cursor sessions; Sidebar will display icon per row
+ // Combine Claude, Cursor, and Codex sessions; Sidebar will display icon per row
const claudeSessions = [...(project.sessions || []), ...(additionalSessions[project.name] || [])].map(s => ({ ...s, __provider: 'claude' }));
const cursorSessions = (project.cursorSessions || []).map(s => ({ ...s, __provider: 'cursor' }));
+ const codexSessions = (project.codexSessions || []).map(s => ({ ...s, __provider: 'codex' }));
// Sort by most recent activity/date
- const normalizeDate = (s) => new Date(s.__provider === 'cursor' ? s.createdAt : s.lastActivity);
- return [...claudeSessions, ...cursorSessions].sort((a, b) => normalizeDate(b) - normalizeDate(a));
+ const normalizeDate = (s) => {
+ if (s.__provider === 'cursor') return new Date(s.createdAt);
+ if (s.__provider === 'codex') return new Date(s.createdAt || s.lastActivity);
+ return new Date(s.lastActivity);
+ };
+ return [...claudeSessions, ...cursorSessions, ...codexSessions].sort((a, b) => normalizeDate(b) - normalizeDate(a));
};
// Helper function to get the last activity date for a project
@@ -297,14 +303,22 @@ function Sidebar({
setEditingName('');
};
- const deleteSession = async (projectName, sessionId) => {
+ const deleteSession = async (projectName, sessionId, provider = 'claude') => {
if (!confirm('Are you sure you want to delete this session? This action cannot be undone.')) {
return;
}
try {
- console.log('[Sidebar] Deleting session:', { projectName, sessionId });
- const response = await api.deleteSession(projectName, sessionId);
+ console.log('[Sidebar] Deleting session:', { projectName, sessionId, provider });
+
+ // Call the appropriate API based on provider
+ let response;
+ if (provider === 'codex') {
+ response = await api.deleteCodexSession(sessionId);
+ } else {
+ response = await api.deleteSession(projectName, sessionId);
+ }
+
console.log('[Sidebar] Delete response:', { ok: response.ok, status: response.status });
if (response.ok) {
@@ -1014,17 +1028,33 @@ function Sidebar({
) : (
getAllSessions(project).map((session) => {
- // Handle both Claude and Cursor session formats
+ // Handle Claude, Cursor, and Codex session formats
const isCursorSession = session.__provider === 'cursor';
-
+ const isCodexSession = session.__provider === 'codex';
+
// Calculate if session is active (within last 10 minutes)
- const sessionDate = new Date(isCursorSession ? session.createdAt : session.lastActivity);
+ const getSessionDate = () => {
+ if (isCursorSession) return new Date(session.createdAt);
+ if (isCodexSession) return new Date(session.createdAt || session.lastActivity);
+ return new Date(session.lastActivity);
+ };
+ const sessionDate = getSessionDate();
const diffInMinutes = Math.floor((currentTime - sessionDate) / (1000 * 60));
const isActive = diffInMinutes < 10;
-
+
// Get session display values
- const sessionName = isCursorSession ? (session.name || 'Untitled Session') : (session.summary || 'New Session');
- const sessionTime = isCursorSession ? session.createdAt : session.lastActivity;
+ const getSessionName = () => {
+ if (isCursorSession) return session.name || 'Untitled Session';
+ if (isCodexSession) return session.summary || session.name || 'Codex Session';
+ return session.summary || 'New Session';
+ };
+ const sessionName = getSessionName();
+ const getSessionTime = () => {
+ if (isCursorSession) return session.createdAt;
+ if (isCodexSession) return session.createdAt || session.lastActivity;
+ return session.lastActivity;
+ };
+ const sessionTime = getSessionTime();
const messageCount = session.messageCount || 0;
return (
@@ -1059,6 +1089,8 @@ function Sidebar({
)}>
{isCursorSession ? (
+ ) : isCodexSession ? (
+
) : (
)}
@@ -1081,21 +1113,22 @@ function Sidebar({
{isCursorSession ? (
+ ) : isCodexSession ? (
+
) : (
)}
- {/* Mobile delete button - only for Claude sessions */}
{!isCursorSession && (
{
e.stopPropagation();
- deleteSession(project.name, session.id);
+ deleteSession(project.name, session.id, session.__provider);
}}
- onTouchEnd={handleTouchClick(() => deleteSession(project.name, session.id))}
+ onTouchEnd={handleTouchClick(() => deleteSession(project.name, session.id, session.__provider))}
>
@@ -1118,6 +1151,8 @@ function Sidebar({
{isCursorSession ? (
+ ) : isCodexSession ? (
+
) : (
)}
@@ -1135,10 +1170,11 @@ function Sidebar({
{messageCount}
)}
- {/* Provider tiny icon */}
{isCursorSession ? (
+ ) : isCodexSession ? (
+
) : (
)}
@@ -1147,10 +1183,9 @@ function Sidebar({
- {/* Desktop hover buttons - only for Claude sessions */}
{!isCursorSession && (
- {editingSession === session.id ? (
+ {editingSession === session.id && !isCodexSession ? (
<>
) : (
<>
- {/* Generate summary button */}
- {/*
{
- e.stopPropagation();
- generateSessionSummary(project.name, session.id);
- }}
- title="Generate AI summary for this session"
- disabled={generatingSummary[`${project.name}-${session.id}`]}
- >
- {generatingSummary[`${project.name}-${session.id}`] ? (
-
- ) : (
-
- )}
- */}
- {/* Edit button */}
-
{
- e.stopPropagation();
- setEditingSession(session.id);
- setEditingSessionName(session.summary || 'New Session');
- }}
- title="Manually edit session name"
- >
-
-
- {/* Delete button */}
+ {!isCodexSession && (
+
{
+ e.stopPropagation();
+ setEditingSession(session.id);
+ setEditingSessionName(session.summary || 'New Session');
+ }}
+ title="Manually edit session name"
+ >
+
+
+ )}
{
e.stopPropagation();
- deleteSession(project.name, session.id);
+ deleteSession(project.name, session.id, session.__provider);
}}
title="Delete this session permanently"
>
diff --git a/src/components/settings/AccountContent.jsx b/src/components/settings/AccountContent.jsx
new file mode 100644
index 0000000..93e0a9a
--- /dev/null
+++ b/src/components/settings/AccountContent.jsx
@@ -0,0 +1,124 @@
+import { Button } from '../ui/button';
+import { Badge } from '../ui/badge';
+import { LogIn } from 'lucide-react';
+import ClaudeLogo from '../ClaudeLogo';
+import CursorLogo from '../CursorLogo';
+import CodexLogo from '../CodexLogo';
+
+const agentConfig = {
+ claude: {
+ name: 'Claude',
+ description: 'Anthropic Claude AI assistant',
+ Logo: ClaudeLogo,
+ bgClass: 'bg-blue-50 dark:bg-blue-900/20',
+ borderClass: 'border-blue-200 dark:border-blue-800',
+ textClass: 'text-blue-900 dark:text-blue-100',
+ subtextClass: 'text-blue-700 dark:text-blue-300',
+ buttonClass: 'bg-blue-600 hover:bg-blue-700',
+ },
+ cursor: {
+ name: 'Cursor',
+ description: 'Cursor AI-powered code editor',
+ Logo: CursorLogo,
+ bgClass: 'bg-purple-50 dark:bg-purple-900/20',
+ borderClass: 'border-purple-200 dark:border-purple-800',
+ textClass: 'text-purple-900 dark:text-purple-100',
+ subtextClass: 'text-purple-700 dark:text-purple-300',
+ buttonClass: 'bg-purple-600 hover:bg-purple-700',
+ },
+ codex: {
+ name: 'Codex',
+ description: 'OpenAI Codex AI assistant',
+ Logo: CodexLogo,
+ bgClass: 'bg-gray-100 dark:bg-gray-800/50',
+ borderClass: 'border-gray-300 dark:border-gray-600',
+ textClass: 'text-gray-900 dark:text-gray-100',
+ subtextClass: 'text-gray-700 dark:text-gray-300',
+ buttonClass: 'bg-gray-800 hover:bg-gray-900 dark:bg-gray-700 dark:hover:bg-gray-600',
+ },
+};
+
+export default function AccountContent({ agent, authStatus, onLogin }) {
+ const config = agentConfig[agent];
+ const { Logo } = config;
+
+ return (
+
+
+
+
+
{config.name} Account
+
{config.description}
+
+
+
+
+
+ {/* Connection Status */}
+
+
+
+ Connection Status
+
+
+ {authStatus?.loading ? (
+ 'Checking authentication status...'
+ ) : authStatus?.authenticated ? (
+ `Logged in as ${authStatus.email || 'authenticated user'}`
+ ) : (
+ 'Not connected'
+ )}
+
+
+
+ {authStatus?.loading ? (
+
+ Checking...
+
+ ) : authStatus?.authenticated ? (
+
+ Connected
+
+ ) : (
+
+ Disconnected
+
+ )}
+
+
+
+
+
+
+
+ {authStatus?.authenticated ? 'Re-authenticate' : 'Login'}
+
+
+ {authStatus?.authenticated
+ ? 'Sign in with a different account or refresh credentials'
+ : `Sign in to your ${config.name} account to enable AI features`}
+
+
+
+
+ {authStatus?.authenticated ? 'Re-login' : 'Login'}
+
+
+
+
+ {authStatus?.error && (
+
+
+ Error: {authStatus.error}
+
+
+ )}
+
+
+
+ );
+}
diff --git a/src/components/settings/AgentListItem.jsx b/src/components/settings/AgentListItem.jsx
new file mode 100644
index 0000000..e550c23
--- /dev/null
+++ b/src/components/settings/AgentListItem.jsx
@@ -0,0 +1,104 @@
+import ClaudeLogo from '../ClaudeLogo';
+import CursorLogo from '../CursorLogo';
+import CodexLogo from '../CodexLogo';
+
+const agentConfig = {
+ claude: {
+ name: 'Claude',
+ color: 'blue',
+ Logo: ClaudeLogo,
+ },
+ cursor: {
+ name: 'Cursor',
+ color: 'purple',
+ Logo: CursorLogo,
+ },
+ codex: {
+ name: 'Codex',
+ color: 'gray',
+ Logo: CodexLogo,
+ },
+};
+
+const colorClasses = {
+ blue: {
+ border: 'border-l-blue-500 md:border-l-blue-500',
+ borderBottom: 'border-b-blue-500',
+ bg: 'bg-blue-50 dark:bg-blue-900/20',
+ dot: 'bg-blue-500',
+ },
+ purple: {
+ border: 'border-l-purple-500 md:border-l-purple-500',
+ borderBottom: 'border-b-purple-500',
+ bg: 'bg-purple-50 dark:bg-purple-900/20',
+ dot: 'bg-purple-500',
+ },
+ gray: {
+ border: 'border-l-gray-700 dark:border-l-gray-300',
+ borderBottom: 'border-b-gray-700 dark:border-b-gray-300',
+ bg: 'bg-gray-100 dark:bg-gray-800/50',
+ dot: 'bg-gray-700 dark:bg-gray-300',
+ },
+};
+
+export default function AgentListItem({ agentId, authStatus, isSelected, onClick, isMobile = false }) {
+ const config = agentConfig[agentId];
+ const colors = colorClasses[config.color];
+ const { Logo } = config;
+
+ // Mobile: horizontal layout with bottom border
+ if (isMobile) {
+ return (
+
+
+
+ {config.name}
+ {authStatus?.authenticated && (
+
+ )}
+
+
+ );
+ }
+
+ // Desktop: vertical layout with left border
+ return (
+
+
+
+ {config.name}
+
+
+ {authStatus?.loading ? (
+
Checking...
+ ) : authStatus?.authenticated ? (
+
+
+
+ {authStatus.email || 'Connected'}
+
+
+ ) : (
+
+
+ Not connected
+
+ )}
+
+
+ );
+}
diff --git a/src/components/settings/McpServersContent.jsx b/src/components/settings/McpServersContent.jsx
new file mode 100644
index 0000000..4495ef9
--- /dev/null
+++ b/src/components/settings/McpServersContent.jsx
@@ -0,0 +1,314 @@
+import { useState } from 'react';
+import { Button } from '../ui/button';
+import { Input } from '../ui/input';
+import { Badge } from '../ui/badge';
+import { Server, Plus, Edit3, Trash2, Terminal, Globe, Zap, X } from 'lucide-react';
+
+const getTransportIcon = (type) => {
+ switch (type) {
+ case 'stdio': return ;
+ case 'sse': return ;
+ case 'http': return ;
+ default: return ;
+ }
+};
+
+// Claude MCP Servers
+function ClaudeMcpServers({
+ servers,
+ onAdd,
+ onEdit,
+ onDelete,
+ onTest,
+ onDiscoverTools,
+ testResults,
+ serverTools,
+ toolsLoading,
+}) {
+ return (
+
+
+
+
+ MCP Servers
+
+
+
+ Model Context Protocol servers provide additional tools and data sources to Claude
+
+
+
+
+
+ {servers.map(server => (
+
+
+
+
+ {getTransportIcon(server.type)}
+ {server.name}
+
+ {server.type}
+
+
+ {server.scope === 'local' ? 'local' : server.scope === 'user' ? 'user' : server.scope}
+
+
+
+
+ {server.type === 'stdio' && server.config?.command && (
+
Command: {server.config.command}
+ )}
+ {(server.type === 'sse' || server.type === 'http') && server.config?.url && (
+
URL: {server.config.url}
+ )}
+ {server.config?.args && server.config.args.length > 0 && (
+
Args: {server.config.args.join(' ')}
+ )}
+
+
+ {/* Test Results */}
+ {testResults?.[server.id] && (
+
+
{testResults[server.id].message}
+
+ )}
+
+ {/* Tools Discovery Results */}
+ {serverTools?.[server.id] && serverTools[server.id].tools?.length > 0 && (
+
+
Tools ({serverTools[server.id].tools.length}):
+
+ {serverTools[server.id].tools.slice(0, 5).map((tool, i) => (
+ {tool.name}
+ ))}
+ {serverTools[server.id].tools.length > 5 && (
+ +{serverTools[server.id].tools.length - 5} more
+ )}
+
+
+ )}
+
+
+
+ onEdit(server)}
+ variant="ghost"
+ size="sm"
+ className="text-gray-600 hover:text-gray-700"
+ title="Edit server"
+ >
+
+
+ onDelete(server.id, server.scope)}
+ variant="ghost"
+ size="sm"
+ className="text-red-600 hover:text-red-700"
+ title="Delete server"
+ >
+
+
+
+
+
+ ))}
+ {servers.length === 0 && (
+
+ No MCP servers configured
+
+ )}
+
+
+ );
+}
+
+// Cursor MCP Servers
+function CursorMcpServers({ servers, onAdd, onEdit, onDelete }) {
+ return (
+
+
+
+
+ MCP Servers
+
+
+
+ Model Context Protocol servers provide additional tools and data sources to Cursor
+
+
+
+
+
+ {servers.map(server => (
+
+
+
+
+
+ {server.name}
+ stdio
+
+
+ {server.config?.command && (
+
Command: {server.config.command}
+ )}
+
+
+
+ onEdit(server)}
+ variant="ghost"
+ size="sm"
+ className="text-gray-600 hover:text-gray-700"
+ >
+
+
+ onDelete(server.name)}
+ variant="ghost"
+ size="sm"
+ className="text-red-600 hover:text-red-700"
+ >
+
+
+
+
+
+ ))}
+ {servers.length === 0 && (
+
+ No MCP servers configured
+
+ )}
+
+
+ );
+}
+
+// Codex MCP Servers
+function CodexMcpServers({ servers, onAdd, onEdit, onDelete }) {
+ return (
+
+
+
+
+ MCP Servers
+
+
+
+ Model Context Protocol servers provide additional tools and data sources to Codex
+
+
+
+
+
+ {servers.map(server => (
+
+
+
+
+
+ {server.name}
+ stdio
+
+
+
+ {server.config?.command && (
+
Command: {server.config.command}
+ )}
+ {server.config?.args && server.config.args.length > 0 && (
+
Args: {server.config.args.join(' ')}
+ )}
+ {server.config?.env && Object.keys(server.config.env).length > 0 && (
+
Environment: {Object.entries(server.config.env).map(([k, v]) => `${k}=${v}`).join(', ')}
+ )}
+
+
+
+
+ onEdit(server)}
+ variant="ghost"
+ size="sm"
+ className="text-gray-600 hover:text-gray-700"
+ title="Edit server"
+ >
+
+
+ onDelete(server.name)}
+ variant="ghost"
+ size="sm"
+ className="text-red-600 hover:text-red-700"
+ title="Delete server"
+ >
+
+
+
+
+
+ ))}
+ {servers.length === 0 && (
+
+ No MCP servers configured
+
+ )}
+
+
+ {/* Help Section */}
+
+
About Codex MCP
+
+ Codex supports stdio-based MCP servers. You can add servers that extend Codex's capabilities
+ with additional tools and resources.
+
+
+
+ );
+}
+
+// Main component
+export default function McpServersContent({ agent, ...props }) {
+ if (agent === 'claude') {
+ return ;
+ }
+ if (agent === 'cursor') {
+ return ;
+ }
+ if (agent === 'codex') {
+ return ;
+ }
+ return null;
+}
diff --git a/src/components/settings/PermissionsContent.jsx b/src/components/settings/PermissionsContent.jsx
new file mode 100644
index 0000000..608d43e
--- /dev/null
+++ b/src/components/settings/PermissionsContent.jsx
@@ -0,0 +1,611 @@
+import { Button } from '../ui/button';
+import { Input } from '../ui/input';
+import { Shield, AlertTriangle, Plus, X } from 'lucide-react';
+
+// Common tool patterns for Claude
+const commonClaudeTools = [
+ 'Bash(git log:*)',
+ 'Bash(git diff:*)',
+ 'Bash(git status:*)',
+ 'Write',
+ 'Read',
+ 'Edit',
+ 'Glob',
+ 'Grep',
+ 'MultiEdit',
+ 'Task',
+ 'TodoWrite',
+ 'TodoRead',
+ 'WebFetch',
+ 'WebSearch'
+];
+
+// Common shell commands for Cursor
+const commonCursorCommands = [
+ 'Shell(ls)',
+ 'Shell(mkdir)',
+ 'Shell(cd)',
+ 'Shell(cat)',
+ 'Shell(echo)',
+ 'Shell(git status)',
+ 'Shell(git diff)',
+ 'Shell(git log)',
+ 'Shell(npm install)',
+ 'Shell(npm run)',
+ 'Shell(python)',
+ 'Shell(node)'
+];
+
+// Claude Permissions
+function ClaudePermissions({
+ skipPermissions,
+ setSkipPermissions,
+ allowedTools,
+ setAllowedTools,
+ disallowedTools,
+ setDisallowedTools,
+ newAllowedTool,
+ setNewAllowedTool,
+ newDisallowedTool,
+ setNewDisallowedTool,
+}) {
+ const addAllowedTool = (tool) => {
+ if (tool && !allowedTools.includes(tool)) {
+ setAllowedTools([...allowedTools, tool]);
+ setNewAllowedTool('');
+ }
+ };
+
+ const removeAllowedTool = (tool) => {
+ setAllowedTools(allowedTools.filter(t => t !== tool));
+ };
+
+ const addDisallowedTool = (tool) => {
+ if (tool && !disallowedTools.includes(tool)) {
+ setDisallowedTools([...disallowedTools, tool]);
+ setNewDisallowedTool('');
+ }
+ };
+
+ const removeDisallowedTool = (tool) => {
+ setDisallowedTools(disallowedTools.filter(t => t !== tool));
+ };
+
+ return (
+
+ {/* Skip Permissions */}
+
+
+
+
+ Permission Settings
+
+
+
+
+ setSkipPermissions(e.target.checked)}
+ className="w-4 h-4 text-blue-600 bg-gray-100 dark:bg-gray-700 border-gray-300 dark:border-gray-600 rounded focus:ring-blue-500 focus:ring-2"
+ />
+
+
+ Skip permission prompts (use with caution)
+
+
+ Equivalent to --dangerously-skip-permissions flag
+
+
+
+
+
+
+ {/* Allowed Tools */}
+
+
+
+
+ Allowed Tools
+
+
+
+ Tools that are automatically allowed without prompting for permission
+
+
+
+
setNewAllowedTool(e.target.value)}
+ placeholder='e.g., "Bash(git log:*)" or "Write"'
+ onKeyPress={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ addAllowedTool(newAllowedTool);
+ }
+ }}
+ className="flex-1 h-10"
+ />
+
addAllowedTool(newAllowedTool)}
+ disabled={!newAllowedTool}
+ size="sm"
+ className="h-10 px-4"
+ >
+
+ Add
+
+
+
+ {/* Quick add buttons */}
+
+
+ Quick add common tools:
+
+
+ {commonClaudeTools.map(tool => (
+ addAllowedTool(tool)}
+ disabled={allowedTools.includes(tool)}
+ className="text-xs h-8"
+ >
+ {tool}
+
+ ))}
+
+
+
+
+ {allowedTools.map(tool => (
+
+
+ {tool}
+
+ removeAllowedTool(tool)}
+ className="text-green-600 hover:text-green-700"
+ >
+
+
+
+ ))}
+ {allowedTools.length === 0 && (
+
+ No allowed tools configured
+
+ )}
+
+
+
+ {/* Disallowed Tools */}
+
+
+
+ Tools that are automatically blocked without prompting for permission
+
+
+
+
setNewDisallowedTool(e.target.value)}
+ placeholder='e.g., "Bash(rm:*)"'
+ onKeyPress={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ addDisallowedTool(newDisallowedTool);
+ }
+ }}
+ className="flex-1 h-10"
+ />
+
addDisallowedTool(newDisallowedTool)}
+ disabled={!newDisallowedTool}
+ size="sm"
+ className="h-10 px-4"
+ >
+
+ Add
+
+
+
+
+ {disallowedTools.map(tool => (
+
+
+ {tool}
+
+ removeDisallowedTool(tool)}
+ className="text-red-600 hover:text-red-700"
+ >
+
+
+
+ ))}
+ {disallowedTools.length === 0 && (
+
+ No blocked tools configured
+
+ )}
+
+
+
+ {/* Help Section */}
+
+
+ Tool Pattern Examples:
+
+
+ "Bash(git log:*)" - Allow all git log commands
+ "Bash(git diff:*)" - Allow all git diff commands
+ "Write" - Allow all Write tool usage
+ "Bash(rm:*)" - Block all rm commands (dangerous)
+
+
+
+ );
+}
+
+// Cursor Permissions
+function CursorPermissions({
+ skipPermissions,
+ setSkipPermissions,
+ allowedCommands,
+ setAllowedCommands,
+ disallowedCommands,
+ setDisallowedCommands,
+ newAllowedCommand,
+ setNewAllowedCommand,
+ newDisallowedCommand,
+ setNewDisallowedCommand,
+}) {
+ const addAllowedCommand = (cmd) => {
+ if (cmd && !allowedCommands.includes(cmd)) {
+ setAllowedCommands([...allowedCommands, cmd]);
+ setNewAllowedCommand('');
+ }
+ };
+
+ const removeAllowedCommand = (cmd) => {
+ setAllowedCommands(allowedCommands.filter(c => c !== cmd));
+ };
+
+ const addDisallowedCommand = (cmd) => {
+ if (cmd && !disallowedCommands.includes(cmd)) {
+ setDisallowedCommands([...disallowedCommands, cmd]);
+ setNewDisallowedCommand('');
+ }
+ };
+
+ const removeDisallowedCommand = (cmd) => {
+ setDisallowedCommands(disallowedCommands.filter(c => c !== cmd));
+ };
+
+ return (
+
+ {/* Skip Permissions */}
+
+
+
+
+ Permission Settings
+
+
+
+
+ setSkipPermissions(e.target.checked)}
+ className="w-4 h-4 text-purple-600 bg-gray-100 dark:bg-gray-700 border-gray-300 dark:border-gray-600 rounded focus:ring-purple-500 focus:ring-2"
+ />
+
+
+ Skip permission prompts (use with caution)
+
+
+ Equivalent to -f flag in Cursor CLI
+
+
+
+
+
+
+ {/* Allowed Commands */}
+
+
+
+
+ Allowed Shell Commands
+
+
+
+ Shell commands that are automatically allowed without prompting
+
+
+
+
setNewAllowedCommand(e.target.value)}
+ placeholder='e.g., "Shell(ls)" or "Shell(git status)"'
+ onKeyPress={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ addAllowedCommand(newAllowedCommand);
+ }
+ }}
+ className="flex-1 h-10"
+ />
+
addAllowedCommand(newAllowedCommand)}
+ disabled={!newAllowedCommand}
+ size="sm"
+ className="h-10 px-4"
+ >
+
+ Add
+
+
+
+ {/* Quick add buttons */}
+
+
+ Quick add common commands:
+
+
+ {commonCursorCommands.map(cmd => (
+ addAllowedCommand(cmd)}
+ disabled={allowedCommands.includes(cmd)}
+ className="text-xs h-8"
+ >
+ {cmd}
+
+ ))}
+
+
+
+
+ {allowedCommands.map(cmd => (
+
+
+ {cmd}
+
+ removeAllowedCommand(cmd)}
+ className="text-green-600 hover:text-green-700"
+ >
+
+
+
+ ))}
+ {allowedCommands.length === 0 && (
+
+ No allowed commands configured
+
+ )}
+
+
+
+ {/* Disallowed Commands */}
+
+
+
+
+ Blocked Shell Commands
+
+
+
+ Shell commands that are automatically blocked
+
+
+
+
setNewDisallowedCommand(e.target.value)}
+ placeholder='e.g., "Shell(rm -rf)" or "Shell(sudo)"'
+ onKeyPress={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ addDisallowedCommand(newDisallowedCommand);
+ }
+ }}
+ className="flex-1 h-10"
+ />
+
addDisallowedCommand(newDisallowedCommand)}
+ disabled={!newDisallowedCommand}
+ size="sm"
+ className="h-10 px-4"
+ >
+
+ Add
+
+
+
+
+ {disallowedCommands.map(cmd => (
+
+
+ {cmd}
+
+ removeDisallowedCommand(cmd)}
+ className="text-red-600 hover:text-red-700"
+ >
+
+
+
+ ))}
+ {disallowedCommands.length === 0 && (
+
+ No blocked commands configured
+
+ )}
+
+
+
+ {/* Help Section */}
+
+
+ Shell Command Examples:
+
+
+ "Shell(ls)" - Allow ls command
+ "Shell(git status)" - Allow git status
+ "Shell(npm install)" - Allow npm install
+ "Shell(rm -rf)" - Block recursive delete
+
+
+
+ );
+}
+
+// Codex Permissions
+function CodexPermissions({ permissionMode, setPermissionMode }) {
+ return (
+
+
+
+
+
+ Permission Mode
+
+
+
+ Controls how Codex handles file modifications and command execution
+
+
+ {/* Default Mode */}
+
setPermissionMode('default')}
+ >
+
+ setPermissionMode('default')}
+ className="mt-1 w-4 h-4 text-green-600"
+ />
+
+
Default
+
+ Only trusted commands (ls, cat, grep, git status, etc.) run automatically.
+ Other commands are skipped. Can write to workspace.
+
+
+
+
+
+ {/* Accept Edits Mode */}
+
setPermissionMode('acceptEdits')}
+ >
+
+ setPermissionMode('acceptEdits')}
+ className="mt-1 w-4 h-4 text-green-600"
+ />
+
+
Accept Edits
+
+ All commands run automatically within the workspace.
+ Full auto mode with sandboxed execution.
+
+
+
+
+
+ {/* Bypass Permissions Mode */}
+
setPermissionMode('bypassPermissions')}
+ >
+
+ setPermissionMode('bypassPermissions')}
+ className="mt-1 w-4 h-4 text-orange-600"
+ />
+
+
+
+ Full system access with no restrictions. All commands run automatically
+ with full disk and network access. Use with caution.
+
+
+
+
+
+ {/* Technical Details */}
+
+
+ Technical details
+
+
+
Default: sandboxMode=workspace-write, approvalPolicy=untrusted. Trusted commands: cat, cd, grep, head, ls, pwd, tail, git status/log/diff/show, find (without -exec), etc.
+
Accept Edits: sandboxMode=workspace-write, approvalPolicy=never. All commands auto-execute within project directory.
+
Bypass Permissions: sandboxMode=danger-full-access, approvalPolicy=never. Full system access, use only in trusted environments.
+
You can override this per-session using the mode button in the chat interface.
+
+
+
+
+ );
+}
+
+// Main component
+export default function PermissionsContent({ agent, ...props }) {
+ if (agent === 'claude') {
+ return ;
+ }
+ if (agent === 'cursor') {
+ return ;
+ }
+ if (agent === 'codex') {
+ return ;
+ }
+ return null;
+}
diff --git a/src/utils/api.js b/src/utils/api.js
index f0b8e6f..894bce5 100644
--- a/src/utils/api.js
+++ b/src/utils/api.js
@@ -3,9 +3,12 @@ export const authenticatedFetch = (url, options = {}) => {
const isPlatform = import.meta.env.VITE_IS_PLATFORM === 'true';
const token = localStorage.getItem('auth-token');
- const defaultHeaders = {
- 'Content-Type': 'application/json',
- };
+ const defaultHeaders = {};
+
+ // Only set Content-Type for non-FormData requests
+ if (!(options.body instanceof FormData)) {
+ defaultHeaders['Content-Type'] = 'application/json';
+ }
if (!isPlatform && token) {
defaultHeaders['Authorization'] = `Bearer ${token}`;
@@ -44,14 +47,23 @@ export const api = {
projects: () => authenticatedFetch('/api/projects'),
sessions: (projectName, limit = 5, offset = 0) =>
authenticatedFetch(`/api/projects/${projectName}/sessions?limit=${limit}&offset=${offset}`),
- sessionMessages: (projectName, sessionId, limit = null, offset = 0) => {
+ sessionMessages: (projectName, sessionId, limit = null, offset = 0, provider = 'claude') => {
const params = new URLSearchParams();
if (limit !== null) {
params.append('limit', limit);
params.append('offset', offset);
}
const queryString = params.toString();
- const url = `/api/projects/${projectName}/sessions/${sessionId}/messages${queryString ? `?${queryString}` : ''}`;
+
+ // Route to the correct endpoint based on provider
+ let url;
+ if (provider === 'codex') {
+ url = `/api/codex/sessions/${sessionId}/messages${queryString ? `?${queryString}` : ''}`;
+ } else if (provider === 'cursor') {
+ url = `/api/cursor/sessions/${sessionId}/messages${queryString ? `?${queryString}` : ''}`;
+ } else {
+ url = `/api/projects/${projectName}/sessions/${sessionId}/messages${queryString ? `?${queryString}` : ''}`;
+ }
return authenticatedFetch(url);
},
renameProject: (projectName, displayName) =>
@@ -63,6 +75,10 @@ export const api = {
authenticatedFetch(`/api/projects/${projectName}/sessions/${sessionId}`, {
method: 'DELETE',
}),
+ deleteCodexSession: (sessionId) =>
+ authenticatedFetch(`/api/codex/sessions/${sessionId}`, {
+ method: 'DELETE',
+ }),
deleteProject: (projectName) =>
authenticatedFetch(`/api/projects/${projectName}`, {
method: 'DELETE',
diff --git a/vite.config.js b/vite.config.js
index f93c7d5..5fad3ad 100755
--- a/vite.config.js
+++ b/vite.config.js
@@ -17,7 +17,7 @@ export default defineConfig(({ command, mode }) => {
ws: true
},
'/shell': {
- target: `ws://localhost:${env.PORT || 3002}`,
+ target: `ws://localhost:${env.PORT || 3001}`,
ws: true
}
}