2026-01-04 21:58:59 -05:00
"use client" ;
2026-01-20 23:45:19 -05:00
import { useState , useEffect , useRef } from "react" ;
2026-03-12 22:41:40 -04:00
import { Card , Button , ModelSelectModal , ManualConfigModal , Tooltip } from "@/shared/components" ;
2026-01-04 21:58:59 -05:00
import Image from "next/image" ;
2026-05-05 09:22:21 -04:00
import BaseUrlSelect from "./BaseUrlSelect" ;
2026-05-10 10:54:54 -04:00
import ApiKeySelect from "./ApiKeySelect" ;
2026-05-07 04:45:09 -04:00
import { matchKnownEndpoint } from "./cliEndpointMatch" ;
2026-01-04 21:58:59 -05:00
const CLOUD _URL = process . env . NEXT _PUBLIC _CLOUD _URL ;
export default function ClaudeToolCard ( {
tool ,
isExpanded ,
onToggle ,
activeProviders ,
modelMappings ,
onModelMappingChange ,
baseUrl ,
hasActiveProviders ,
apiKeys ,
cloudEnabled ,
2026-02-25 04:32:05 -05:00
initialStatus ,
2026-05-05 09:22:21 -04:00
tunnelEnabled ,
tunnelPublicUrl ,
tailscaleEnabled ,
tailscaleUrl ,
2026-01-04 21:58:59 -05:00
} ) {
2026-02-25 04:32:05 -05:00
const [ claudeStatus , setClaudeStatus ] = useState ( initialStatus || null ) ;
2026-01-04 21:58:59 -05:00
const [ checkingClaude , setCheckingClaude ] = useState ( false ) ;
const [ applying , setApplying ] = useState ( false ) ;
const [ restoring , setRestoring ] = useState ( false ) ;
const [ message , setMessage ] = useState ( null ) ;
const [ showInstallGuide , setShowInstallGuide ] = useState ( false ) ;
const [ modalOpen , setModalOpen ] = useState ( false ) ;
const [ currentEditingAlias , setCurrentEditingAlias ] = useState ( null ) ;
const [ selectedApiKey , setSelectedApiKey ] = useState ( "" ) ;
const [ modelAliases , setModelAliases ] = useState ( { } ) ;
2026-01-14 03:42:38 -05:00
const [ showManualConfigModal , setShowManualConfigModal ] = useState ( false ) ;
2026-01-20 23:45:19 -05:00
const [ customBaseUrl , setCustomBaseUrl ] = useState ( "" ) ;
2026-03-12 22:41:40 -04:00
const [ ccFilterNaming , setCcFilterNaming ] = useState ( false ) ;
2026-01-20 23:45:19 -05:00
const hasInitializedModels = useRef ( false ) ;
2026-01-04 21:58:59 -05:00
const getConfigStatus = ( ) => {
if ( ! claudeStatus ? . installed ) return null ;
const currentUrl = claudeStatus . settings ? . env ? . ANTHROPIC _BASE _URL ;
if ( ! currentUrl ) return "not_configured" ;
2026-05-07 04:45:09 -04:00
if ( matchKnownEndpoint ( currentUrl , { tunnelPublicUrl , tailscaleUrl , cloudUrl : cloudEnabled ? CLOUD _URL : null } ) ) return "configured" ;
2026-01-04 21:58:59 -05:00
return "other" ;
} ;
const configStatus = getConfigStatus ( ) ;
useEffect ( ( ) => {
if ( apiKeys ? . length > 0 && ! selectedApiKey ) {
setSelectedApiKey ( apiKeys [ 0 ] . key ) ;
}
2026-01-19 05:15:11 -05:00
} , [ apiKeys , selectedApiKey ] ) ;
2026-01-04 21:58:59 -05:00
2026-02-25 04:32:05 -05:00
useEffect ( ( ) => {
if ( initialStatus ) setClaudeStatus ( initialStatus ) ;
} , [ initialStatus ] ) ;
2026-01-04 21:58:59 -05:00
useEffect ( ( ) => {
if ( isExpanded && ! claudeStatus ) {
checkClaudeStatus ( ) ;
fetchModelAliases ( ) ;
}
2026-02-25 04:32:05 -05:00
if ( isExpanded ) fetchModelAliases ( ) ;
} , [ isExpanded ] ) ;
2026-01-04 21:58:59 -05:00
2026-03-12 22:41:40 -04:00
useEffect ( ( ) => {
fetch ( "/api/settings" ) . then ( r => r . json ( ) ) . then ( data => {
setCcFilterNaming ( ! ! data . ccFilterNaming ) ;
} ) . catch ( ( ) => { } ) ;
} , [ ] ) ;
const handleCcFilterNamingToggle = async ( e ) => {
const value = e . target . checked ;
setCcFilterNaming ( value ) ;
await fetch ( "/api/settings" , {
method : "PATCH" ,
headers : { "Content-Type" : "application/json" } ,
body : JSON . stringify ( { ccFilterNaming : value } ) ,
} ) . catch ( ( ) => { } ) ;
} ;
2026-01-04 21:58:59 -05:00
const fetchModelAliases = async ( ) => {
try {
const res = await fetch ( "/api/models/alias" ) ;
const data = await res . json ( ) ;
if ( res . ok ) setModelAliases ( data . aliases || { } ) ;
} catch ( error ) {
console . log ( "Error fetching model aliases:" , error ) ;
}
} ;
useEffect ( ( ) => {
2026-01-20 23:45:19 -05:00
if ( claudeStatus ? . installed && ! hasInitializedModels . current ) {
hasInitializedModels . current = true ;
2026-01-04 21:58:59 -05:00
const env = claudeStatus . settings ? . env || { } ;
2026-05-03 03:55:43 -04:00
2026-01-04 21:58:59 -05:00
tool . defaultModels . forEach ( ( model ) => {
if ( model . envKey ) {
const value = env [ model . envKey ] || model . defaultValue || "" ;
2026-01-20 23:45:19 -05:00
// Only sync initial values from file once
if ( value ) {
onModelMappingChange ( model . alias , value ) ;
}
2026-01-04 21:58:59 -05:00
}
} ) ;
// Only set selectedApiKey if it exists in apiKeys list
const tokenFromFile = env . ANTHROPIC _AUTH _TOKEN ;
if ( tokenFromFile && apiKeys ? . some ( k => k . key === tokenFromFile ) ) {
setSelectedApiKey ( tokenFromFile ) ;
}
}
2026-01-19 05:15:11 -05:00
} , [ claudeStatus , apiKeys , tool . defaultModels , onModelMappingChange ] ) ;
2026-01-04 21:58:59 -05:00
const checkClaudeStatus = async ( ) => {
setCheckingClaude ( true ) ;
try {
const res = await fetch ( "/api/cli-tools/claude-settings" ) ;
const data = await res . json ( ) ;
setClaudeStatus ( data ) ;
} catch ( error ) {
setClaudeStatus ( { installed : false , error : error . message } ) ;
} finally {
setCheckingClaude ( false ) ;
}
} ;
2026-02-06 08:53:20 -05:00
const getEffectiveBaseUrl = ( ) => {
const url = customBaseUrl || baseUrl ;
return url . endsWith ( "/v1" ) ? url : ` ${ url } /v1 ` ;
} ;
const getDisplayUrl = ( ) => {
const url = customBaseUrl || baseUrl ;
return url . endsWith ( "/v1" ) ? url : ` ${ url } /v1 ` ;
} ;
2026-01-20 23:45:19 -05:00
2026-01-04 21:58:59 -05:00
const handleApplySettings = async ( ) => {
setApplying ( true ) ;
setMessage ( null ) ;
try {
2026-01-20 23:45:19 -05:00
const env = { ANTHROPIC _BASE _URL : getEffectiveBaseUrl ( ) } ;
2026-05-03 03:55:43 -04:00
2026-01-04 21:58:59 -05:00
// Get key from dropdown, fallback to first key or sk_9router for localhost
2026-05-03 03:55:43 -04:00
const keyToUse = selectedApiKey ? . trim ( )
2026-01-04 21:58:59 -05:00
|| ( apiKeys ? . length > 0 ? apiKeys [ 0 ] . key : null )
|| ( ! cloudEnabled ? "sk_9router" : null ) ;
2026-05-03 03:55:43 -04:00
2026-01-04 21:58:59 -05:00
if ( keyToUse ) {
env . ANTHROPIC _AUTH _TOKEN = keyToUse ;
}
2026-05-03 03:55:43 -04:00
2026-01-04 21:58:59 -05:00
tool . defaultModels . forEach ( ( model ) => {
const targetModel = modelMappings [ model . alias ] ;
if ( targetModel && model . envKey ) env [ model . envKey ] = targetModel ;
} ) ;
const res = await fetch ( "/api/cli-tools/claude-settings" , {
method : "POST" ,
headers : { "Content-Type" : "application/json" } ,
body : JSON . stringify ( { env } ) ,
} ) ;
const data = await res . json ( ) ;
if ( res . ok ) {
setMessage ( { type : "success" , text : "Settings applied successfully!" } ) ;
setClaudeStatus ( prev => ( { ... prev , hasBackup : true , settings : { ... prev ? . settings , env } } ) ) ;
} else {
setMessage ( { type : "error" , text : data . error || "Failed to apply settings" } ) ;
}
} catch ( error ) {
setMessage ( { type : "error" , text : error . message } ) ;
} finally {
setApplying ( false ) ;
}
} ;
const handleResetSettings = async ( ) => {
setRestoring ( true ) ;
setMessage ( null ) ;
try {
const res = await fetch ( "/api/cli-tools/claude-settings" , { method : "DELETE" } ) ;
const data = await res . json ( ) ;
if ( res . ok ) {
setMessage ( { type : "success" , text : "Settings reset successfully!" } ) ;
tool . defaultModels . forEach ( ( model ) => onModelMappingChange ( model . alias , model . defaultValue || "" ) ) ;
setSelectedApiKey ( "" ) ;
} else {
setMessage ( { type : "error" , text : data . error || "Failed to reset settings" } ) ;
}
} catch ( error ) {
setMessage ( { type : "error" , text : error . message } ) ;
} finally {
setRestoring ( false ) ;
}
} ;
const openModelSelector = ( alias ) => {
setCurrentEditingAlias ( alias ) ;
setModalOpen ( true ) ;
} ;
const handleModelSelect = ( model ) => {
if ( currentEditingAlias ) onModelMappingChange ( currentEditingAlias , model . value ) ;
} ;
// Generate settings.json content for manual copy
2026-01-14 03:42:38 -05:00
const getManualConfigs = ( ) => {
2026-05-03 03:55:43 -04:00
const keyToUse = ( selectedApiKey && selectedApiKey . trim ( ) )
? selectedApiKey
2026-01-04 21:58:59 -05:00
: ( ! cloudEnabled ? "sk_9router" : "<API_KEY_FROM_DASHBOARD>" ) ;
2026-01-20 23:45:19 -05:00
const env = { ANTHROPIC _BASE _URL : getEffectiveBaseUrl ( ) , ANTHROPIC _AUTH _TOKEN : keyToUse } ;
2026-01-04 21:58:59 -05:00
tool . defaultModels . forEach ( ( model ) => {
const targetModel = modelMappings [ model . alias ] ;
if ( targetModel && model . envKey ) env [ model . envKey ] = targetModel ;
} ) ;
2026-05-03 03:55:43 -04:00
2026-01-14 03:42:38 -05:00
return [
{
filename : "~/.claude/settings.json" ,
2026-04-15 23:58:35 -04:00
content : JSON . stringify ( { hasCompletedOnboarding : true , env } , null , 2 ) ,
2026-01-14 03:42:38 -05:00
} ,
] ;
2026-01-04 21:58:59 -05:00
} ;
return (
2026-03-02 22:10:03 -05:00
< Card padding = "xs" className = "overflow-hidden" >
2026-05-01 05:34:07 -04:00
< div className = "flex items-start justify-between gap-3 hover:cursor-pointer sm:items-center" onClick = { onToggle } >
< div className = "flex min-w-0 items-center gap-3" >
2026-01-31 00:58:04 -05:00
< div className = "size-8 flex items-center justify-center shrink-0" >
< Image src = "/providers/claude.png" alt = { tool . name } width = { 32 } height = { 32 } className = "size-8 object-contain rounded-lg" sizes = "32px" onError = { ( e ) => { e . target . style . display = "none" ; } } / >
2026-01-04 21:58:59 -05:00
< / d i v >
2026-01-31 00:58:04 -05:00
< div className = "min-w-0" >
2026-05-01 05:34:07 -04:00
< div className = "flex min-w-0 flex-wrap items-center gap-2" >
2026-01-31 00:58:04 -05:00
< h3 className = "font-medium text-sm" > { tool . name } < / h 3 >
{ configStatus === "configured" && < span className = "px-1.5 py-0.5 text-[10px] font-medium bg-green-500/10 text-green-600 dark:text-green-400 rounded-full" > Connected < / s p a n > }
{ configStatus === "not_configured" && < span className = "px-1.5 py-0.5 text-[10px] font-medium bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 rounded-full" > Not configured < / s p a n > }
{ configStatus === "other" && < span className = "px-1.5 py-0.5 text-[10px] font-medium bg-blue-500/10 text-blue-600 dark:text-blue-400 rounded-full" > Other < / s p a n > }
2026-01-04 21:58:59 -05:00
< / d i v >
2026-01-31 00:58:04 -05:00
< p className = "text-xs text-text-muted truncate" > { tool . description } < / p >
2026-01-04 21:58:59 -05:00
< / d i v >
< / d i v >
2026-01-31 00:58:04 -05:00
< span className = { ` material-symbols-outlined text-text-muted text-[20px] transition-transform ${ isExpanded ? "rotate-180" : "" } ` } > expand _more < / s p a n >
2026-01-04 21:58:59 -05:00
< / d i v >
{ isExpanded && (
< div className = "mt-4 pt-4 border-t border-border flex flex-col gap-4" >
{ checkingClaude && (
< div className = "flex items-center gap-2 text-text-muted" >
< span className = "material-symbols-outlined animate-spin" > progress _activity < / s p a n >
< span > Checking Claude CLI ... < / s p a n >
< / d i v >
) }
{ ! checkingClaude && claudeStatus && ! claudeStatus . installed && (
< div className = "flex flex-col gap-4" >
2026-04-17 01:51:08 -04:00
< div className = "flex flex-col gap-3 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg" >
< div className = "flex items-start gap-3" >
< span className = "material-symbols-outlined text-yellow-500" > warning < / s p a n >
< div className = "flex-1" >
< p className = "font-medium text-yellow-600 dark:text-yellow-400" > Claude CLI not detected locally < / p >
< p className = "text-sm text-text-muted" > Manual configuration is still available if 9 router is deployed on a remote server . < / p >
< / d i v >
2026-04-17 01:21:21 -04:00
< / d i v >
2026-04-17 01:51:08 -04:00
< div className = "flex items-center gap-2 pl-9" >
< Button variant = "secondary" size = "sm" onClick = { ( ) => setShowManualConfigModal ( true ) } className = "!bg-yellow-500/20 !border-yellow-500/40 !text-yellow-700 dark:!text-yellow-300 hover:!bg-yellow-500/30" >
2026-04-17 01:21:21 -04:00
< span className = "material-symbols-outlined text-[18px] mr-1" > content _copy < / s p a n >
Manual Config
< / B u t t o n >
< Button variant = "outline" size = "sm" onClick = { ( ) => setShowInstallGuide ( ! showInstallGuide ) } >
< span className = "material-symbols-outlined text-[18px] mr-1" > { showInstallGuide ? "expand_less" : "help" } < / s p a n >
{ showInstallGuide ? "Hide" : "How to Install" }
< / B u t t o n >
2026-01-04 21:58:59 -05:00
< / d i v >
< / d i v >
{ showInstallGuide && (
< div className = "p-4 bg-surface border border-border rounded-lg" >
< h4 className = "font-medium mb-3" > Installation Guide < / h 4 >
< div className = "space-y-3 text-sm" >
< div >
< p className = "text-text-muted mb-1" > macOS / Linux / Windows : < / p >
< code className = "block px-3 py-2 bg-black/5 dark:bg-white/5 rounded font-mono text-xs" > npm install - g @ anthropic - ai / claude - code < / c o d e >
< / d i v >
< p className = "text-text-muted" > After installation , run < code className = "px-1 bg-black/5 dark:bg-white/5 rounded" > claude < / c o d e > t o v e r i f y . < / p >
< / d i v >
< / d i v >
) }
< / d i v >
) }
{ ! checkingClaude && claudeStatus ? . installed && (
< >
2026-01-20 23:45:19 -05:00
< div className = "flex flex-col gap-2" >
2026-05-07 04:45:09 -04:00
{ /* Endpoint (selector) */ }
2026-05-05 09:22:21 -04:00
< div className = "grid grid-cols-1 gap-1.5 sm:grid-cols-[8rem_auto_1fr] sm:items-center sm:gap-2" >
2026-05-07 04:45:09 -04:00
< span className = "text-xs font-semibold text-text-main sm:text-right sm:text-sm" > Select Endpoint < / s p a n >
2026-05-01 05:34:07 -04:00
< span className = "material-symbols-outlined hidden text-text-muted text-[14px] sm:inline" > arrow _forward < / s p a n >
2026-05-05 09:22:21 -04:00
< BaseUrlSelect
value = { customBaseUrl || getDisplayUrl ( ) }
onChange = { setCustomBaseUrl }
requiresExternalUrl = { tool . requiresExternalUrl }
tunnelEnabled = { tunnelEnabled }
tunnelPublicUrl = { tunnelPublicUrl }
tailscaleEnabled = { tailscaleEnabled }
tailscaleUrl = { tailscaleUrl }
2026-01-20 23:45:19 -05:00
/ >
< / d i v >
2026-01-04 21:58:59 -05:00
2026-05-07 04:45:09 -04:00
{ /* Current configured */ }
{ claudeStatus ? . settings ? . env ? . ANTHROPIC _BASE _URL && (
< div className = "grid grid-cols-1 gap-1.5 sm:grid-cols-[8rem_auto_1fr_auto] sm:items-center sm:gap-2" >
< span className = "text-xs font-semibold text-text-main sm:text-right sm:text-sm" > Current < / s p a n >
< span className = "material-symbols-outlined hidden text-text-muted text-[14px] sm:inline" > arrow _forward < / s p a n >
< span className = "min-w-0 truncate rounded bg-surface/40 px-2 py-2 text-xs text-text-muted sm:py-1.5" >
{ claudeStatus . settings . env . ANTHROPIC _BASE _URL }
< / s p a n >
< / d i v >
) }
2026-01-20 23:45:19 -05:00
{ /* API Key */ }
2026-05-03 03:55:43 -04:00
< div className = "grid grid-cols-1 gap-1.5 sm:grid-cols-[8rem_auto_1fr_auto] sm:items-center sm:gap-2" >
2026-05-01 05:34:07 -04:00
< span className = "text-xs font-semibold text-text-main sm:text-right sm:text-sm" > API Key < / s p a n >
< span className = "material-symbols-outlined hidden text-text-muted text-[14px] sm:inline" > arrow _forward < / s p a n >
2026-05-10 10:54:54 -04:00
< ApiKeySelect value = { selectedApiKey } onChange = { setSelectedApiKey } apiKeys = { apiKeys } cloudEnabled = { cloudEnabled } / >
2026-01-20 23:45:19 -05:00
< / d i v >
2026-01-04 21:58:59 -05:00
2026-01-20 23:45:19 -05:00
{ /* Model Mappings */ }
2026-01-04 21:58:59 -05:00
{ tool . defaultModels . map ( ( model ) => (
2026-05-03 03:55:43 -04:00
< div key = { model . alias } className = "grid grid-cols-1 gap-1.5 sm:grid-cols-[8rem_auto_1fr_auto] sm:items-center sm:gap-2" >
2026-05-01 05:34:07 -04:00
< span className = "text-xs font-semibold text-text-main sm:text-right sm:text-sm" > { model . name } < / s p a n >
< span className = "material-symbols-outlined hidden text-text-muted text-[14px] sm:inline" > arrow _forward < / s p a n >
2026-05-03 11:24:57 -04:00
< div className = "relative w-full min-w-0" >
< input type = "text" value = { modelMappings [ model . alias ] || "" } onChange = { ( e ) => onModelMappingChange ( model . alias , e . target . value ) } placeholder = "provider/model-id" className = "w-full min-w-0 pl-2 pr-7 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" / >
{ modelMappings [ model . alias ] && < button onClick = { ( ) => onModelMappingChange ( model . alias , "" ) } className = "absolute right-1 top-1/2 -translate-y-1/2 p-0.5 text-text-muted hover:text-red-500 rounded transition-colors" title = "Clear" > < span className = "material-symbols-outlined text-[14px]" > close < / s p a n > < / b u t t o n > }
< / d i v >
2026-05-03 03:55:43 -04:00
< button onClick = { ( ) => openModelSelector ( model . alias ) } disabled = { ! hasActiveProviders } className = { ` w-full sm:w-auto rounded border px-2 py-2 text-xs transition-colors sm:py-1.5 whitespace-nowrap sm:shrink-0 ${ hasActiveProviders ? "bg-surface border-border text-text-main hover:border-primary cursor-pointer" : "opacity-50 cursor-not-allowed border-border" } ` } > Select Model < / b u t t o n >
2026-01-04 21:58:59 -05:00
< / d i v >
) ) }
2026-03-12 22:41:40 -04:00
{ /* CC Filter Naming */ }
2026-05-03 03:55:43 -04:00
< div className = "grid grid-cols-1 gap-1.5 sm:grid-cols-[8rem_auto_1fr_auto] sm:items-center sm:gap-2" >
2026-05-01 05:34:07 -04:00
< span className = "text-xs font-semibold text-text-main sm:text-right sm:text-sm" > Filter naming < / s p a n >
< span className = "material-symbols-outlined hidden text-text-muted text-[14px] sm:inline" > arrow _forward < / s p a n >
2026-03-12 22:41:40 -04:00
< label className = "flex items-center gap-1.5 cursor-pointer select-none" >
< input type = "checkbox" checked = { ccFilterNaming } onChange = { handleCcFilterNamingToggle } className = "w-3.5 h-3.5 accent-primary cursor-pointer" / >
< span className = "text-xs text-text-muted" > Filter naming requests < / s p a n >
2026-06-15 07:18:04 -04:00
< Tooltip text = "Intercepts Claude Code's topic-naming requests and returns a fake response locally, saving API tokens." >
< span className = "material-symbols-outlined text-text-muted text-[14px] cursor-help" > info < / s p a n >
< / T o o l t i p >
2026-03-12 22:41:40 -04:00
< / l a b e l >
< / d i v >
2026-01-04 21:58:59 -05:00
< / d i v >
{ message && (
< div className = { ` flex items-center gap-2 px-2 py-1.5 rounded text-xs ${ message . type === "success" ? "bg-green-500/10 text-green-600" : "bg-red-500/10 text-red-600" } ` } >
< span className = "material-symbols-outlined text-[14px]" > { message . type === "success" ? "check_circle" : "error" } < / s p a n >
< span > { message . text } < / s p a n >
< / d i v >
) }
2026-05-01 05:34:07 -04:00
< div className = "grid grid-cols-1 gap-2 sm:flex sm:items-center" >
2026-01-04 21:58:59 -05:00
< Button variant = "primary" size = "sm" onClick = { handleApplySettings } disabled = { ! hasActiveProviders } loading = { applying } >
< span className = "material-symbols-outlined text-[14px] mr-1" > save < / s p a n > A p p l y
< / B u t t o n >
< Button variant = "outline" size = "sm" onClick = { handleResetSettings } disabled = { ! claudeStatus ? . has9Router } loading = { restoring } >
< span className = "material-symbols-outlined text-[14px] mr-1" > restore < / s p a n > R e s e t
< / B u t t o n >
2026-01-14 03:42:38 -05:00
< Button variant = "ghost" size = "sm" onClick = { ( ) => setShowManualConfigModal ( true ) } >
< span className = "material-symbols-outlined text-[14px] mr-1" > content _copy < / s p a n > M a n u a l C o n f i g
2026-01-04 21:58:59 -05:00
< / B u t t o n >
< / d i v >
< / >
) }
< / d i v >
) }
< ModelSelectModal isOpen = { modalOpen } onClose = { ( ) => setModalOpen ( false ) } onSelect = { handleModelSelect } selectedModel = { currentEditingAlias ? modelMappings [ currentEditingAlias ] : null } activeProviders = { activeProviders } modelAliases = { modelAliases } title = { ` Select model for ${ currentEditingAlias } ` } / >
2026-05-03 03:55:43 -04:00
2026-01-14 03:42:38 -05:00
< ManualConfigModal
isOpen = { showManualConfigModal }
onClose = { ( ) => setShowManualConfigModal ( false ) }
title = "Claude CLI - Manual Configuration"
configs = { getManualConfigs ( ) }
/ >
2026-01-04 21:58:59 -05:00
< / C a r d >
) ;
}