@@ -840,71 +840,179 @@ async fn setup_claude(existing_config: Option<Config>) -> Result<()> {
840840 }
841841 }
842842
843- // Get authentication
843+ // Choose provider: Anthropic vs Google Vertex AI
844844 println ! ( ) ;
845- println ! ( "Cica uses Claude Code, which can be billed through your Claude" ) ;
846- println ! ( "subscription or based on API usage through your Console account." ) ;
845+ println ! ( "Claude Code can use Anthropic directly or Google Vertex AI (GCP)." ) ;
847846 println ! ( ) ;
848847
849- let auth_choices = vec ! [
850- "Claude subscription Pro, Max, Team, or Enterprise " ,
851- "Anthropic Console API usage billing" ,
848+ let provider_choices = vec ! [
849+ "Anthropic Subscription ( Pro/ Max/ Team) or API key " ,
850+ "Google Vertex AI GCP project ( billing via Google Cloud) " ,
852851 ] ;
853852
854- let auth_selection = Select :: with_theme ( & ColorfulTheme :: default ( ) )
855- . with_prompt ( "Select login method " )
856- . items ( & auth_choices )
853+ let provider_selection = Select :: with_theme ( & ColorfulTheme :: default ( ) )
854+ . with_prompt ( "Select Claude Code provider " )
855+ . items ( & provider_choices )
857856 . default ( 0 )
858857 . interact ( ) ?;
859858
860- let credential = match auth_selection {
861- 0 => {
862- // OAuth / setup-token flow
863- println ! ( ) ;
864- println ! ( "Run this command in any terminal:" ) ;
865- println ! ( ) ;
866- println ! ( " claude setup-token" ) ;
867- println ! ( ) ;
868- println ! ( "Note: The token may display across two lines, but it's one" ) ;
869- println ! ( "continuous string. Copy and paste it as a single line." ) ;
870- println ! ( ) ;
859+ let mut config = existing_config. unwrap_or_default ( ) ;
860+ let was_using_cursor = config. backend == AiBackend :: Cursor && config. is_cursor_configured ( ) ;
871861
872- Password :: with_theme ( & ColorfulTheme :: default ( ) )
873- . with_prompt ( "Paste the setup token" )
874- . interact ( ) ?
875- }
876- 1 => {
877- // API Key flow
862+ if provider_selection == 1 {
863+ // Vertex AI setup
864+ let paths = config:: paths ( ) ?;
865+ println ! ( ) ;
866+ println ! ( "Vertex AI Setup" ) ;
867+ println ! ( "───────────────" ) ;
868+ println ! ( ) ;
869+ println ! ( "You need a GCP project with Vertex AI enabled and Claude models" ) ;
870+ println ! ( "enabled in Model Garden." ) ;
871+ println ! ( ) ;
872+
873+ let project_id: String = Input :: with_theme ( & ColorfulTheme :: default ( ) )
874+ . with_prompt ( "GCP project ID" )
875+ . interact_text ( ) ?;
876+
877+ let region: String = Input :: with_theme ( & ColorfulTheme :: default ( ) )
878+ . with_prompt (
879+ "Region (e.g. europe-west1 or us-east5; see code.claude.com/docs/google-vertex-ai)" ,
880+ )
881+ . default ( "europe-west1" . to_string ( ) )
882+ . interact_text ( ) ?;
883+
884+ let auth_choices = vec ! [
885+ "Service account key file (JSON) Long-lived; recommended for servers" ,
886+ "gcloud application-default login Uses your user credentials (may expire)" ,
887+ ] ;
888+ let auth_selection = Select :: with_theme ( & ColorfulTheme :: default ( ) )
889+ . with_prompt ( "GCP auth (for servers use a service account key so auth does not expire)" )
890+ . items ( & auth_choices)
891+ . default ( 0 )
892+ . interact ( ) ?;
893+
894+ let vertex_credentials_path: Option < String > = if auth_selection == 0 {
878895 println ! ( ) ;
879- println ! ( "1. Go to https://console.anthropic.com/settings/keys" ) ;
880- println ! ( "2. Create a new API key" ) ;
896+ println ! ( "Create a service account in GCP with Vertex AI User (or similar)," ) ;
897+ println ! ( "download its JSON key, and enter the path below." ) ;
898+ println ! ( "Path can be absolute or relative to your Cica config directory." ) ;
881899 println ! ( ) ;
900+ let path: String = Input :: with_theme ( & ColorfulTheme :: default ( ) )
901+ . with_prompt ( "Path to service account JSON key file" )
902+ . interact_text ( ) ?;
903+ let path = path. trim ( ) . to_string ( ) ;
904+ if path. is_empty ( ) {
905+ None
906+ } else {
907+ print ! ( "Validating key file... " ) ;
908+ std:: io:: Write :: flush ( & mut std:: io:: stdout ( ) ) ?;
909+ match setup:: validate_vertex_credentials_path ( & path, & paths. base ) {
910+ Ok ( ( ) ) => {
911+ println ! ( "OK" ) ;
912+ Some ( path)
913+ }
914+ Err ( e) => {
915+ println ! ( "FAILED" ) ;
916+ bail ! ( "Invalid credentials file: {}" , e) ;
917+ }
918+ }
919+ }
920+ } else {
921+ None
922+ } ;
923+
924+ print ! ( "Validating Vertex config... " ) ;
925+ std:: io:: Write :: flush ( & mut std:: io:: stdout ( ) ) ?;
882926
883- Password :: with_theme ( & ColorfulTheme :: default ( ) )
884- . with_prompt ( "Paste your API key" )
885- . interact ( ) ?
927+ match setup:: validate_vertex_config (
928+ project_id. trim ( ) ,
929+ Some ( region. trim ( ) ) ,
930+ vertex_credentials_path. as_deref ( ) ,
931+ & paths. base ,
932+ )
933+ . await
934+ {
935+ Ok ( ( ) ) => println ! ( "OK" ) ,
936+ Err ( e) => {
937+ println ! ( "FAILED" ) ;
938+ bail ! ( "Vertex AI setup failed: {}" , e) ;
939+ }
886940 }
887- _ => unreachable ! ( ) ,
888- } ;
889941
890- // Trim whitespace and normalize
891- let credential = credential. trim ( ) . to_string ( ) ;
942+ config. claude . api_key = None ;
943+ config. claude . use_vertex = true ;
944+ config. claude . vertex_project_id = Some ( project_id. trim ( ) . to_string ( ) ) ;
945+ config. claude . vertex_region = if region. trim ( ) . is_empty ( ) {
946+ None
947+ } else {
948+ Some ( region. trim ( ) . to_string ( ) )
949+ } ;
950+ config. claude . vertex_credentials_path = vertex_credentials_path;
951+ } else {
952+ // Anthropic setup
953+ println ! ( ) ;
954+ println ! ( "Cica uses Claude Code, which can be billed through your Claude" ) ;
955+ println ! ( "subscription or based on API usage through your Console account." ) ;
956+ println ! ( ) ;
957+
958+ let auth_choices = vec ! [
959+ "Claude subscription Pro, Max, Team, or Enterprise" ,
960+ "Anthropic Console API usage billing" ,
961+ ] ;
892962
893- print ! ( "Validating... " ) ;
894- std:: io:: Write :: flush ( & mut std:: io:: stdout ( ) ) ?;
963+ let auth_selection = Select :: with_theme ( & ColorfulTheme :: default ( ) )
964+ . with_prompt ( "Select login method" )
965+ . items ( & auth_choices)
966+ . default ( 0 )
967+ . interact ( ) ?;
895968
896- match setup:: validate_credential ( & credential) . await {
897- Ok ( ( ) ) => println ! ( "OK" ) ,
898- Err ( e) => {
899- println ! ( "FAILED" ) ;
900- bail ! ( "Authentication failed: {}" , e) ;
969+ let credential = match auth_selection {
970+ 0 => {
971+ println ! ( ) ;
972+ println ! ( "Run this command in any terminal:" ) ;
973+ println ! ( ) ;
974+ println ! ( " claude setup-token" ) ;
975+ println ! ( ) ;
976+ println ! ( "Note: The token may display across two lines, but it's one" ) ;
977+ println ! ( "continuous string. Copy and paste it as a single line." ) ;
978+ println ! ( ) ;
979+
980+ Password :: with_theme ( & ColorfulTheme :: default ( ) )
981+ . with_prompt ( "Paste the setup token" )
982+ . interact ( ) ?
983+ }
984+ 1 => {
985+ println ! ( ) ;
986+ println ! ( "1. Go to https://console.anthropic.com/settings/keys" ) ;
987+ println ! ( "2. Create a new API key" ) ;
988+ println ! ( ) ;
989+
990+ Password :: with_theme ( & ColorfulTheme :: default ( ) )
991+ . with_prompt ( "Paste your API key" )
992+ . interact ( ) ?
993+ }
994+ _ => unreachable ! ( ) ,
995+ } ;
996+
997+ let credential = credential. trim ( ) . to_string ( ) ;
998+
999+ print ! ( "Validating... " ) ;
1000+ std:: io:: Write :: flush ( & mut std:: io:: stdout ( ) ) ?;
1001+
1002+ match setup:: validate_credential ( & credential) . await {
1003+ Ok ( ( ) ) => println ! ( "OK" ) ,
1004+ Err ( e) => {
1005+ println ! ( "FAILED" ) ;
1006+ bail ! ( "Authentication failed: {}" , e) ;
1007+ }
9011008 }
902- }
9031009
904- // Save config
905- let mut config = existing_config. unwrap_or_default ( ) ;
906- let was_using_cursor = config. backend == AiBackend :: Cursor && config. is_cursor_configured ( ) ;
907- config. claude . api_key = Some ( credential) ;
1010+ config. claude . api_key = Some ( credential) ;
1011+ config. claude . use_vertex = false ;
1012+ config. claude . vertex_project_id = None ;
1013+ config. claude . vertex_region = None ;
1014+ config. claude . vertex_credentials_path = None ;
1015+ }
9081016
9091017 // Ask whether to switch if another backend was active
9101018 if was_using_cursor {
0 commit comments