@@ -69,12 +69,16 @@ def config_group():
6969 "azure-openai" ,
7070 "claude-code" ,
7171 "codex" ,
72+ "google" ,
73+ "google-vertex" ,
7274 ],
7375 case_sensitive = False ,
7476 ),
7577 help = (
7678 "LLM provider type (default: openai-compatible). "
7779 "Use 'atlas-cloud' for Atlas Cloud (base URL auto-set; reads ATLASCLOUD_API_KEY). "
80+ "Use 'google' for the Gemini API (reads GEMINI_API_KEY / GOOGLE_API_KEY) or "
81+ "'google-vertex' for Vertex AI with Google credentials (no API key). "
7882 "Use 'claude-code' or 'codex' to run on a CLI subscription instead of an API key."
7983 ),
8084)
@@ -83,6 +87,17 @@ def config_group():
8387 "--api-version" , type = str , help = "Azure OpenAI API version (default: 2024-12-01-preview)"
8488)
8589@click .option ("--azure-deployment" , type = str , help = "Azure OpenAI deployment name" )
90+ @click .option (
91+ "--google-project" ,
92+ type = str ,
93+ help = "Google Cloud project for google-vertex (default: GOOGLE_CLOUD_PROJECT)" ,
94+ )
95+ @click .option (
96+ "--google-location" ,
97+ type = str ,
98+ help = "Vertex AI location for google-vertex, e.g. global, eu, us "
99+ "(default: GOOGLE_CLOUD_LOCATION, else global)" ,
100+ )
86101@click .option (
87102 "--use-gitignore/--no-gitignore" ,
88103 default = None ,
@@ -110,6 +125,8 @@ def config_set(
110125 aws_region : Optional [str ] = None ,
111126 api_version : Optional [str ] = None ,
112127 azure_deployment : Optional [str ] = None ,
128+ google_project : Optional [str ] = None ,
129+ google_location : Optional [str ] = None ,
113130 use_gitignore : Optional [bool ] = None ,
114131 prompt_caching : Optional [bool ] = None ,
115132):
@@ -142,6 +159,16 @@ def config_set(
142159 # Subscription mode (Codex)
143160 $ codewiki config set --provider codex --main-model gpt-5.2-codex
144161
162+ \b
163+ # Vertex AI (Google credentials, no API key or base URL)
164+ $ codewiki config set --provider google-vertex --google-project my-proj \\
165+ --google-location eu --main-model gemini-3.8-flash --cluster-model gemini-3.8-flash
166+
167+ \b
168+ # Gemini API (key read from GEMINI_API_KEY if --api-key is omitted)
169+ $ codewiki config set --provider google --main-model gemini-3.8-flash \\
170+ --cluster-model gemini-3.8-flash
171+
145172 \b
146173 # Update only API key
147174 $ codewiki config set --api-key sk-new-key
@@ -181,6 +208,8 @@ def config_set(
181208 aws_region ,
182209 api_version ,
183210 azure_deployment ,
211+ google_project ,
212+ google_location ,
184213 use_gitignore is not None ,
185214 prompt_caching is not None ,
186215 ]
@@ -199,6 +228,20 @@ def config_set(
199228 if not api_key :
200229 api_key = os .getenv ("ATLASCLOUD_API_KEY" )
201230
231+ manager = ConfigManager ()
232+ manager .load () # Load existing config if present
233+
234+ if provider and provider .lower () == "google" and not api_key :
235+ api_key = os .getenv ("GEMINI_API_KEY" ) or os .getenv ("GOOGLE_API_KEY" )
236+ previous = manager .get_config ()
237+ if not api_key and not (previous and previous .provider == "google" ):
238+ # The keyring has one key slot: reusing it would send another
239+ # provider's key to Google.
240+ raise ConfigurationError (
241+ "--provider google needs a Gemini API key: pass --api-key or set "
242+ "GEMINI_API_KEY (or GOOGLE_API_KEY)."
243+ )
244+
202245 # Validate inputs before saving
203246 validated_data = {}
204247
@@ -265,9 +308,11 @@ def config_set(
265308 if prompt_caching is not None :
266309 validated_data ["prompt_caching" ] = prompt_caching
267310
268- # Create config manager and save
269- manager = ConfigManager ()
270- manager .load () # Load existing config if present
311+ if google_project is not None :
312+ validated_data ["google_project" ] = google_project
313+
314+ if google_location is not None :
315+ validated_data ["google_location" ] = google_location
271316
272317 manager .save (
273318 api_key = validated_data .get ("api_key" ),
@@ -285,6 +330,8 @@ def config_set(
285330 aws_region = validated_data .get ("aws_region" ),
286331 api_version = validated_data .get ("api_version" ),
287332 azure_deployment = validated_data .get ("azure_deployment" ),
333+ google_project = validated_data .get ("google_project" ),
334+ google_location = validated_data .get ("google_location" ),
288335 use_gitignore = validated_data .get ("use_gitignore" ),
289336 prompt_caching = validated_data .get ("prompt_caching" ),
290337 )
@@ -352,6 +399,12 @@ def config_set(
352399 if azure_deployment :
353400 click .secho (f"✓ Azure Deployment: { azure_deployment } " , fg = "green" )
354401
402+ if google_project :
403+ click .secho (f"✓ Google project: { google_project } " , fg = "green" )
404+
405+ if google_location :
406+ click .secho (f"✓ Google location: { google_location } " , fg = "green" )
407+
355408 if use_gitignore is not None :
356409 click .secho (f"✓ Use gitignore: { use_gitignore } " , fg = "green" )
357410
@@ -405,6 +458,9 @@ def config_show(output_json: bool):
405458 "api_key" : mask_api_key (api_key ) if api_key else "Not set" ,
406459 "api_key_storage" : "keychain" if manager .keyring_available else "encrypted_file" ,
407460 "base_url" : config .base_url if config else "" ,
461+ "provider" : config .provider if config else "" ,
462+ "google_project" : config .google_project if config else "" ,
463+ "google_location" : config .google_location if config else "" ,
408464 "main_model" : config .main_model if config else "" ,
409465 "cluster_model" : config .cluster_model if config else "" ,
410466 "fallback_model" : config .fallback_model if config else "glm-4p5" ,
@@ -430,12 +486,14 @@ def config_show(output_json: bool):
430486 click .echo ("━" * 40 )
431487 click .echo ()
432488
433- from codewiki .src .be .backend import is_caw_provider
489+ from codewiki .src .be .backend import is_caw_provider , needs_base_url
434490
435491 caw_mode = bool (config ) and is_caw_provider (config .provider )
436492
437493 click .secho ("Credentials" , fg = "cyan" , bold = True )
438- if caw_mode :
494+ if config and config .provider == "google-vertex" :
495+ click .secho (" Google ADC / Workload Identity (no API key needed)" , fg = "cyan" )
496+ elif caw_mode :
439497 cli_name = "claude" if config .provider == "claude-code" else "codex"
440498 click .secho (
441499 f" Subscription mode: authenticate via '{ cli_name } login' (no API key needed)" ,
@@ -453,10 +511,18 @@ def config_show(output_json: bool):
453511 click .echo (f" Provider: { config .provider } " )
454512 click .echo (f" Main Model: { config .main_model or 'Not set' } " )
455513 if not caw_mode :
456- click .echo (f" Base URL: { config .base_url or 'Not set' } " )
514+ if needs_base_url (config .provider ):
515+ click .echo (f" Base URL: { config .base_url or 'Not set' } " )
457516 click .echo (f" Cluster Model: { config .cluster_model or 'Not set' } " )
458517 click .echo (f" Fallback Model: { config .fallback_model or 'Not set' } " )
459- if config .provider == "bedrock" :
518+ if config .provider == "google-vertex" :
519+ project = config .google_project or "from GOOGLE_CLOUD_PROJECT"
520+ location = config .google_location or (
521+ "from GOOGLE_CLOUD_LOCATION, else global"
522+ )
523+ click .echo (f" Google Project: { project } " )
524+ click .echo (f" Google Location: { location } " )
525+ elif config .provider == "bedrock" :
460526 click .echo (f" AWS Region: { config .aws_region } " )
461527 elif config .provider == "azure-openai" :
462528 click .echo (f" API Version: { config .api_version } " )
@@ -565,20 +631,29 @@ def config_validate(quick: bool, verbose: bool):
565631
566632 # Load config early so we know the provider for the rest of the checks.
567633 config = manager .get_config ()
568- from codewiki .src .be .backend import is_caw_provider
634+ from codewiki .src .be .backend import (
635+ is_caw_provider ,
636+ is_google_provider ,
637+ needs_api_key ,
638+ needs_base_url ,
639+ )
569640
570641 caw_mode = bool (config ) and is_caw_provider (config .provider )
642+ key_needed = bool (config ) and needs_api_key (config .provider )
643+ url_needed = bool (config ) and needs_base_url (config .provider )
644+ api_key = None
571645
572- # Step 2: Check API key (skipped for subscription providers)
646+ # Step 2: Check API key (skipped for subscription providers and Vertex AI )
573647 if verbose :
574648 click .echo ()
575649 click .echo ("[2/5] Checking API key..." )
576650
577- if caw_mode :
651+ if not key_needed :
652+ reason = "subscription mode" if caw_mode else "Google credentials"
578653 if verbose :
579- click .secho (" ✓ API key not required (subscription mode )" , fg = "green" )
654+ click .secho (f " ✓ API key not required ({ reason } )" , fg = "green" )
580655 else :
581- click .secho ("✓ API key not required (subscription mode )" , fg = "green" )
656+ click .secho (f "✓ API key not required ({ reason } )" , fg = "green" )
582657 else :
583658 if verbose :
584659 storage = "system keychain" if manager .keyring_available else "encrypted file"
@@ -597,16 +672,17 @@ def config_validate(quick: bool, verbose: bool):
597672 else :
598673 click .secho ("✓ API key present (stored in keychain)" , fg = "green" )
599674
600- # Step 3: Check base URL (skipped for subscription providers)
675+ # Step 3: Check base URL (skipped for subscription and Google providers)
601676 if verbose :
602677 click .echo ()
603678 click .echo ("[3/5] Checking base URL..." )
604679
605- if caw_mode :
680+ if not url_needed :
681+ reason = "subscription mode" if caw_mode else "Google endpoint"
606682 if verbose :
607- click .secho (" ✓ Base URL not required (subscription mode )" , fg = "green" )
683+ click .secho (f " ✓ Base URL not required ({ reason } )" , fg = "green" )
608684 else :
609- click .secho ("✓ Base URL not required (subscription mode )" , fg = "green" )
685+ click .secho (f "✓ Base URL not required ({ reason } )" , fg = "green" )
610686 else :
611687 if verbose :
612688 click .echo (f" URL: { config .base_url } " )
@@ -657,7 +733,7 @@ def config_validate(quick: bool, verbose: bool):
657733 # Warn about non-top-tier cluster model
658734 if not is_top_tier_model (config .cluster_model ):
659735 click .secho (
660- "⚠️ Cluster model is not top-tier. Consider using claude-sonnet-4 or gpt-4 ." ,
736+ "⚠️ Cluster model is not top-tier. Consider a Claude Sonnet/Opus, GPT-5 or Gemini 3 model ." ,
661737 fg = "yellow" ,
662738 )
663739
@@ -690,6 +766,26 @@ def config_validate(quick: bool, verbose: bool):
690766 f"✓ { cli_name } CLI available (run '{ cli_name } login' if not yet authenticated)" ,
691767 fg = "green" ,
692768 )
769+ elif is_google_provider (config .provider ) and not quick :
770+ if verbose :
771+ click .echo ()
772+ click .echo ("[5/5] Testing Google API access..." )
773+ try :
774+ from codewiki .src .be import google_provider
775+
776+ google_provider .check_access (
777+ config .provider ,
778+ api_key if key_needed else None ,
779+ config .main_model ,
780+ config .google_project ,
781+ config .google_location ,
782+ )
783+ click .secho ("✓ API connectivity test successful" , fg = "green" )
784+ except Exception as e :
785+ click .secho ("✗ API connectivity test failed" , fg = "red" )
786+ if verbose :
787+ click .echo (f" Error: { e } " )
788+ sys .exit (EXIT_CONFIG_ERROR )
693789 elif not quick :
694790 if verbose :
695791 click .echo ()
0 commit comments