@@ -8,88 +8,165 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
88import { Button } from "@/components/ui/button" ;
99import { Label } from "@/components/ui/label" ;
1010import { Alert , AlertDescription } from "@/components/ui/alert" ;
11- import { AlertCircle , Save } from "lucide-react" ;
11+ import { AlertCircle , Save , Key , Settings2 } from "lucide-react" ;
1212import { toast } from "sonner" ;
1313import { SupabaseService } from "@/lib/supabase-service" ;
1414import { useUserProfile } from "@/hooks/useUserProfile" ;
1515
1616interface CodeAgentConfig {
17- claudeCode ?: Record < string , string > ;
18- codexCLI ?: Record < string , string > ;
17+ claudeCode ?: {
18+ env ?: Record < string , string > ;
19+ credentials ?: Record < string , any > | null ;
20+ } ;
21+ codex ?: {
22+ env ?: Record < string , string > ;
23+ } ;
1924}
2025
2126const DEFAULT_CLAUDE_ENV = {
2227 ANTHROPIC_API_KEY : "" ,
2328 // Add other Claude-specific env vars here if needed
2429} ;
2530
31+ const DEFAULT_CLAUDE_CREDENTIALS = {
32+ // Example structure - user can customize
33+ } ;
34+
2635const DEFAULT_CODEX_ENV = {
2736 OPENAI_API_KEY : "" ,
2837 DISABLE_SANDBOX : "yes" ,
2938 CONTINUE_ON_BROWSER : "no" ,
3039 // Add other Codex-specific env vars here if needed
3140} ;
3241
42+ // Helper function to check if credentials is meaningful (not empty/null/undefined)
43+ const hasMeaningfulCredentials = ( creds : any ) : boolean => {
44+ if ( ! creds || creds === null || creds === undefined || creds === '' ) {
45+ return false ;
46+ }
47+ if ( typeof creds === 'object' && Object . keys ( creds ) . length === 0 ) {
48+ return false ;
49+ }
50+ return true ;
51+ } ;
52+
3353export function CodeAgentSettings ( ) {
3454 const { profile, refreshProfile } = useUserProfile ( ) ;
3555 const [ claudeEnv , setClaudeEnv ] = useState ( "" ) ;
56+ const [ claudeCredentials , setClaudeCredentials ] = useState ( "" ) ;
3657 const [ codexEnv , setCodexEnv ] = useState ( "" ) ;
3758 const [ isLoading , setIsLoading ] = useState ( false ) ;
38- const [ errors , setErrors ] = useState < { claude ?: string ; codex ?: string } > ( { } ) ;
59+ const [ errors , setErrors ] = useState < {
60+ claudeEnv ?: string ;
61+ claudeCredentials ?: string ;
62+ codexEnv ?: string ;
63+ } > ( { } ) ;
3964
4065 // Load settings from profile on mount
4166 useEffect ( ( ) => {
4267 if ( profile ?. preferences ) {
43- const prefs = profile . preferences as CodeAgentConfig ;
44- setClaudeEnv ( JSON . stringify ( prefs . claudeCode || DEFAULT_CLAUDE_ENV , null , 2 ) ) ;
45- setCodexEnv ( JSON . stringify ( prefs . codexCLI || DEFAULT_CODEX_ENV , null , 2 ) ) ;
68+ const prefs = profile . preferences as any ; // Use any for backward compatibility
69+
70+ // Handle backward compatibility for Claude config
71+ let claudeConfig : any = { } ;
72+ if ( prefs . claudeCode ) {
73+ // Check if it's the new structure (has env/credentials properties)
74+ if ( prefs . claudeCode . env || prefs . claudeCode . credentials ) {
75+ claudeConfig = prefs . claudeCode ;
76+ } else {
77+ // Old structure - migrate to new format
78+ const { credentials, ...envVars } = prefs . claudeCode ;
79+
80+ claudeConfig = {
81+ env : envVars ,
82+ credentials : hasMeaningfulCredentials ( credentials ) ? credentials : null
83+ } ;
84+ }
85+ }
86+
87+ // Handle backward compatibility for Codex config
88+ let codexConfig : any = { } ;
89+ if ( prefs . codex ) {
90+ // Check if it's the new structure
91+ if ( prefs . codex . env ) {
92+ codexConfig = prefs . codex ;
93+ } else {
94+ // New structure for codex
95+ codexConfig = { env : prefs . codex } ;
96+ }
97+ } else if ( prefs . codexCLI ) {
98+ // Old codexCLI key - migrate to new codex key
99+ codexConfig = { env : prefs . codexCLI } ;
100+ }
101+
102+ setClaudeEnv ( JSON . stringify ( claudeConfig . env || DEFAULT_CLAUDE_ENV , null , 2 ) ) ;
103+ setClaudeCredentials ( JSON . stringify ( claudeConfig . credentials || DEFAULT_CLAUDE_CREDENTIALS , null , 2 ) ) ;
104+ setCodexEnv ( JSON . stringify ( codexConfig . env || DEFAULT_CODEX_ENV , null , 2 ) ) ;
46105 } else {
47106 setClaudeEnv ( JSON . stringify ( DEFAULT_CLAUDE_ENV , null , 2 ) ) ;
107+ setClaudeCredentials ( JSON . stringify ( DEFAULT_CLAUDE_CREDENTIALS , null , 2 ) ) ;
48108 setCodexEnv ( JSON . stringify ( DEFAULT_CODEX_ENV , null , 2 ) ) ;
49109 }
50110 } , [ profile ] ) ;
51111
52- const validateJSON = ( value : string , agent : "claude" | "codex" ) => {
112+ const validateJSON = ( value : string , key : string ) => {
53113 try {
54114 JSON . parse ( value ) ;
55- setErrors ( prev => ( { ...prev , [ agent ] : undefined } ) ) ;
115+ setErrors ( prev => ( { ...prev , [ key ] : undefined } ) ) ;
56116 return true ;
57117 } catch ( e ) {
58- setErrors ( prev => ( { ...prev , [ agent ] : "Invalid JSON format" } ) ) ;
118+ setErrors ( prev => ( { ...prev , [ key ] : "Invalid JSON format" } ) ) ;
59119 return false ;
60120 }
61121 } ;
62122
63123 const handleSave = async ( ) => {
64- // Validate both JSONs
65- const isClaudeValid = validateJSON ( claudeEnv , "claude" ) ;
66- const isCodexValid = validateJSON ( codexEnv , "codex" ) ;
124+ // Validate all JSONs
125+ const isClaudeEnvValid = validateJSON ( claudeEnv , "claudeEnv" ) ;
126+ const isClaudeCredentialsValid = validateJSON ( claudeCredentials , "claudeCredentials" ) ;
127+ const isCodexEnvValid = validateJSON ( codexEnv , "codexEnv" ) ;
67128
68- if ( ! isClaudeValid || ! isCodexValid ) {
129+ if ( ! isClaudeEnvValid || ! isClaudeCredentialsValid || ! isCodexEnvValid ) {
69130 toast . error ( "Please fix JSON errors before saving" ) ;
70131 return ;
71132 }
72133
73134 setIsLoading ( true ) ;
74135 try {
75- const claudeConfig = JSON . parse ( claudeEnv ) ;
76- const codexConfig = JSON . parse ( codexEnv ) ;
136+ const claudeEnvConfig = JSON . parse ( claudeEnv ) ;
137+ const claudeCredentialsConfig = JSON . parse ( claudeCredentials ) ;
138+ const codexEnvConfig = JSON . parse ( codexEnv ) ;
77139
78140 const preferences : CodeAgentConfig = {
79- claudeCode : claudeConfig ,
80- codexCLI : codexConfig ,
141+ claudeCode : {
142+ env : claudeEnvConfig ,
143+ credentials : hasMeaningfulCredentials ( claudeCredentialsConfig ) ? claudeCredentialsConfig : null ,
144+ } ,
145+ codex : {
146+ env : codexEnvConfig ,
147+ } ,
81148 } ;
82149
83150 // Merge with existing preferences if any
84151 const existingPrefs = ( profile ?. preferences || { } ) as Record < string , any > ;
152+
153+ // Clean up old keys during migration
154+ const { codexCLI, ...cleanedPrefs } = existingPrefs ;
155+
85156 const mergedPrefs = {
86- ...existingPrefs ,
157+ ...cleanedPrefs ,
87158 ...preferences ,
88159 } ;
89160
90161 await SupabaseService . updateUserProfile ( { preferences : mergedPrefs } ) ;
91162 await refreshProfile ( ) ;
92- toast . success ( "Code agent settings saved successfully" ) ;
163+
164+ // Provide feedback about credentials handling
165+ const credentialsMessage = hasMeaningfulCredentials ( claudeCredentialsConfig )
166+ ? "Claude credentials will be configured"
167+ : "Claude credentials are empty and will be skipped" ;
168+
169+ toast . success ( `Code agent settings saved successfully. ${ credentialsMessage } ` ) ;
93170 } catch ( error ) {
94171 console . error ( "Failed to save settings:" , error ) ;
95172 toast . error ( "Failed to save settings" ) ;
@@ -104,70 +181,126 @@ export function CodeAgentSettings() {
104181 < CardHeader >
105182 < CardTitle > Code Agent Settings</ CardTitle >
106183 < CardDescription >
107- Configure environment variables for each code agent. These settings will be used when creating containers.
184+ Configure environment variables and credentials for each code agent. These settings will be used when creating containers.
108185 </ CardDescription >
109186 </ CardHeader >
110- < CardContent className = "space-y-6 " >
187+ < CardContent className = "space-y-8 " >
111188 < Alert >
112189 < AlertCircle className = "h-4 w-4" />
113190 < AlertDescription >
114- < strong > Important:</ strong > Store sensitive API keys here instead of hardcoding them. Personal settings will override default environment variables.
191+ < strong > Important:</ strong > Environment variables and credentials are stored separately. Store sensitive API keys in environment variables, and authentication configs in credentials .
115192 </ AlertDescription >
116193 </ Alert >
117194
118195 { /* Claude Code Settings */ }
119- < div className = "space-y-2" >
120- < Label htmlFor = "claude-env" > Claude Code Environment Variables</ Label >
121- < div className = "border rounded-lg overflow-hidden" >
122- < CodeMirror
123- id = "claude-env"
124- value = { claudeEnv }
125- height = "200px"
126- extensions = { [ javascript ( { jsx : false } ) ] }
127- theme = { githubLight }
128- onChange = { ( value ) => {
129- setClaudeEnv ( value ) ;
130- validateJSON ( value , "claude" ) ;
131- } }
132- placeholder = { JSON . stringify ( DEFAULT_CLAUDE_ENV , null , 2 ) }
133- />
196+ < div className = "space-y-6" >
197+ < div className = "flex items-center gap-2 pb-2 border-b" >
198+ < Settings2 className = "w-5 h-5 text-blue-600" />
199+ < h3 className = "text-lg font-semibold" > Claude Code Configuration</ h3 >
200+ </ div >
201+
202+ { /* Claude Environment Variables */ }
203+ < div className = "space-y-2" >
204+ < Label htmlFor = "claude-env" className = "flex items-center gap-2" >
205+ < Settings2 className = "w-4 h-4" />
206+ Environment Variables
207+ </ Label >
208+ < div className = "border rounded-lg overflow-hidden" >
209+ < CodeMirror
210+ id = "claude-env"
211+ value = { claudeEnv }
212+ height = "200px"
213+ extensions = { [ javascript ( { jsx : false } ) ] }
214+ theme = { githubLight }
215+ onChange = { ( value ) => {
216+ setClaudeEnv ( value ) ;
217+ validateJSON ( value , "claudeEnv" ) ;
218+ } }
219+ placeholder = { JSON . stringify ( DEFAULT_CLAUDE_ENV , null , 2 ) }
220+ />
221+ </ div >
222+ { errors . claudeEnv && (
223+ < p className = "text-sm text-red-500 mt-1" > { errors . claudeEnv } </ p >
224+ ) }
225+ < p className = "text-sm text-muted-foreground" >
226+ Configure environment variables for Claude Code CLI (@anthropic-ai/claude-code)
227+ </ p >
228+ </ div >
229+
230+ { /* Claude Credentials */ }
231+ < div className = "space-y-2" >
232+ < Label htmlFor = "claude-credentials" className = "flex items-center gap-2" >
233+ < Key className = "w-4 h-4" />
234+ Credentials (Optional)
235+ </ Label >
236+ < div className = "border rounded-lg overflow-hidden" >
237+ < CodeMirror
238+ id = "claude-credentials"
239+ value = { claudeCredentials }
240+ height = "150px"
241+ extensions = { [ javascript ( { jsx : false } ) ] }
242+ theme = { githubLight }
243+ onChange = { ( value ) => {
244+ setClaudeCredentials ( value ) ;
245+ validateJSON ( value , "claudeCredentials" ) ;
246+ } }
247+ placeholder = { JSON . stringify ( DEFAULT_CLAUDE_CREDENTIALS , null , 2 ) }
248+ />
249+ </ div >
250+ { errors . claudeCredentials && (
251+ < p className = "text-sm text-red-500 mt-1" > { errors . claudeCredentials } </ p >
252+ ) }
253+ < p className = "text-sm text-muted-foreground" >
254+ Configure authentication credentials for Claude Code CLI (will be saved to ~/.claude/.credentials.json)
255+ </ p >
134256 </ div >
135- { errors . claude && (
136- < p className = "text-sm text-red-500 mt-1" > { errors . claude } </ p >
137- ) }
138- < p className = "text-sm text-muted-foreground" >
139- Configure environment variables for Claude Code CLI (@anthropic-ai/claude-code)
140- </ p >
141257 </ div >
142258
143259 { /* Codex CLI Settings */ }
144- < div className = "space-y-2" >
145- < Label htmlFor = "codex-env" > Codex CLI Environment Variables</ Label >
146- < div className = "border rounded-lg overflow-hidden" >
147- < CodeMirror
148- id = "codex-env"
149- value = { codexEnv }
150- height = "200px"
151- extensions = { [ javascript ( { jsx : false } ) ] }
152- theme = { githubLight }
153- onChange = { ( value ) => {
154- setCodexEnv ( value ) ;
155- validateJSON ( value , "codex" ) ;
156- } }
157- placeholder = { JSON . stringify ( DEFAULT_CODEX_ENV , null , 2 ) }
158- />
260+ < div className = "space-y-6" >
261+ < div className = "flex items-center gap-2 pb-2 border-b" >
262+ < Settings2 className = "w-5 h-5 text-green-600" />
263+ < h3 className = "text-lg font-semibold" > Codex CLI Configuration</ h3 >
264+ </ div >
265+
266+ { /* Codex Environment Variables */ }
267+ < div className = "space-y-2" >
268+ < Label htmlFor = "codex-env" className = "flex items-center gap-2" >
269+ < Settings2 className = "w-4 h-4" />
270+ Environment Variables
271+ </ Label >
272+ < div className = "border rounded-lg overflow-hidden" >
273+ < CodeMirror
274+ id = "codex-env"
275+ value = { codexEnv }
276+ height = "200px"
277+ extensions = { [ javascript ( { jsx : false } ) ] }
278+ theme = { githubLight }
279+ onChange = { ( value ) => {
280+ setCodexEnv ( value ) ;
281+ validateJSON ( value , "codexEnv" ) ;
282+ } }
283+ placeholder = { JSON . stringify ( DEFAULT_CODEX_ENV , null , 2 ) }
284+ />
285+ </ div >
286+ { errors . codexEnv && (
287+ < p className = "text-sm text-red-500 mt-1" > { errors . codexEnv } </ p >
288+ ) }
289+ < p className = "text-sm text-muted-foreground" >
290+ Configure environment variables for Codex CLI (@openai/codex)
291+ </ p >
292+ </ div >
293+
294+ < div className = "p-3 bg-yellow-50 border border-yellow-200 rounded-md" >
295+ < p className = "text-sm text-yellow-800" >
296+ < strong > Note:</ strong > Codex CLI does not require separate credentials configuration. All settings are handled via environment variables.
297+ </ p >
159298 </ div >
160- { errors . codex && (
161- < p className = "text-sm text-red-500 mt-1" > { errors . codex } </ p >
162- ) }
163- < p className = "text-sm text-muted-foreground" >
164- Configure environment variables for Codex CLI (@openai/codex)
165- </ p >
166299 </ div >
167300
168301 < Button
169302 onClick = { handleSave }
170- disabled = { isLoading || ! ! errors . claude || ! ! errors . codex }
303+ disabled = { isLoading || ! ! errors . claudeEnv || ! ! errors . claudeCredentials || ! ! errors . codexEnv }
171304 className = "w-full"
172305 >
173306 < Save className = "w-4 h-4 mr-2" />
0 commit comments