@@ -280,16 +280,9 @@ def _setup_credentials(api_url: str | None = None, api_key: str | None = None) -
280280 cems_dir = Path .home () / ".cems"
281281 creds_file = cems_dir / "credentials"
282282
283- # Already configured — but offer project setup if interactive
283+ # Already configured
284284 if creds_file .exists () and not api_key :
285285 console .print (f"[green]Credentials:[/green] { creds_file } " )
286- if _is_interactive ():
287- also_project = click .confirm (
288- "Also set up project-specific credentials in this directory?" ,
289- default = False ,
290- )
291- if also_project :
292- _setup_project_credentials (None , None )
293286 return True
294287
295288 # Non-interactive with explicit values — preserve existing custom keys
@@ -339,17 +332,6 @@ def _setup_credentials(api_url: str | None = None, api_key: str | None = None) -
339332 console .print ("[red]API key is required.[/red]" )
340333 return False
341334
342- # Ask where to store credentials
343- location = click .prompt (
344- "Store credentials" ,
345- type = click .Choice (["global" , "project" ], case_sensitive = False ),
346- default = "global" ,
347- show_default = True ,
348- )
349- if location == "project" :
350- _setup_project_credentials (api_url , api_key )
351- return True
352-
353335 cems_dir .mkdir (parents = True , exist_ok = True )
354336 creds_file .write_text (
355337 f"# CEMS credentials — generated by cems setup\n "
@@ -967,19 +949,37 @@ def _setup_project_credentials(api_url: str | None, api_key: str | None) -> None
967949 console .print ("Usage: cems setup --project --api-url URL --api-key KEY" )
968950 raise click .Abort ()
969951
952+ # Use existing global credentials as defaults for project setup
953+ existing_creds = _read_credentials () if (Path .home () / ".cems" / "credentials" ).exists () else {}
954+ default_url = existing_creds .get ("CEMS_API_URL" , "http://localhost:8765" )
955+ default_key = existing_creds .get ("CEMS_API_KEY" , "" )
956+
970957 console .print ()
971958 console .print ("[bold]Project Credentials Setup[/bold]" )
972- console .print ("Get these from your CEMS admin." )
959+ if default_key :
960+ console .print ("Using global credentials as defaults. Press enter to keep, or type new values." )
961+ else :
962+ console .print ("Get these from your CEMS admin." )
973963 console .print ()
974964
975965 if not api_url :
976966 api_url = click .prompt (
977967 "CEMS API URL" ,
978- default = "http://localhost:8765" ,
968+ default = default_url ,
979969 show_default = True ,
980970 )
981971 if not api_key :
982- api_key = click .prompt ("CEMS API Key" , hide_input = True )
972+ if default_key :
973+ use_same = click .confirm (
974+ f"Use same API key as global ({ default_key [:8 ]} ...)?" ,
975+ default = True ,
976+ )
977+ if use_same :
978+ api_key = default_key
979+ else :
980+ api_key = click .prompt ("CEMS API Key" , hide_input = True )
981+ else :
982+ api_key = click .prompt ("CEMS API Key" , hide_input = True )
983983
984984 if not api_key :
985985 console .print ("[red]API key is required.[/red]" )
@@ -1100,17 +1100,18 @@ def setup(install_claude: bool, install_cursor: bool, install_codex: bool, insta
11001100 cems setup --claude --api-url URL --api-key KEY # Non-interactive
11011101 cems setup --project --api-url URL --api-key KEY # Per-project config
11021102 """
1103- # Per- project setup — completely separate flow
1104- if install_project :
1103+ # Non-interactive -- project flag — separate flow
1104+ if install_project and not _is_interactive () :
11051105 _setup_project_credentials (api_url , api_key )
11061106 return
1107+
11071108 console .print ()
11081109 console .print ("[bold]CEMS Setup[/bold]" )
11091110 console .print ()
11101111
11111112 data_path = _get_data_path ()
11121113
1113- # Determine what to install
1114+ # Step 1: Select IDEs
11141115 if not install_claude and not install_cursor and not install_codex and not install_goose :
11151116 if _is_interactive ():
11161117 selected = _multiselect (
@@ -1131,14 +1132,84 @@ def setup(install_claude: bool, install_cursor: bool, install_codex: bool, insta
11311132 console .print ("[yellow]Non-interactive mode: installing Claude Code hooks (use --cursor/--codex/--goose for others)[/yellow]" )
11321133 install_claude = True
11331134
1134- # Credentials
1135- if not _setup_credentials (api_url = api_url , api_key = api_key ):
1136- raise click .Abort ()
1135+ # Step 2: Credential scope
1136+ credential_scope = "global"
1137+ if _is_interactive () and not install_project :
1138+ console .print ()
1139+ credential_scope = _single_select (
1140+ "Credential scope" ,
1141+ [
1142+ ("global" , "Global — CEMS accesses all your projects" ),
1143+ ("project" , "Project — CEMS accesses only this project's data" ),
1144+ ],
1145+ default = 0 ,
1146+ )
1147+ install_project = credential_scope == "project"
1148+ elif install_project :
1149+ credential_scope = "project"
1150+
1151+ # Step 3: Credentials (adapts based on scope + existing state)
1152+ import os as _os
1153+ global_creds_file = Path .home () / ".cems" / "credentials"
1154+ project_creds_file = Path (_os .getcwd ()) / ".cems" / "credentials"
1155+
1156+ if credential_scope == "project" :
1157+ # Project scope — write to CWD/.cems/credentials
1158+ if project_creds_file .exists () and _is_interactive () and not api_key :
1159+ console .print ()
1160+ action = _single_select (
1161+ f"Project credentials found ({ project_creds_file } )" ,
1162+ [
1163+ ("keep" , "Keep existing" ),
1164+ ("overwrite" , "Overwrite with new values" ),
1165+ ],
1166+ default = 0 ,
1167+ )
1168+ if action == "overwrite" :
1169+ _setup_project_credentials (api_url , api_key )
1170+ elif not project_creds_file .exists ():
1171+ _setup_project_credentials (api_url , api_key )
1172+ else :
1173+ console .print (f"[green]Project credentials:[/green] { project_creds_file } " )
1174+
1175+ # Also ensure global creds exist for hooks/MCP that need them
1176+ if not global_creds_file .exists () and not api_key :
1177+ # Read the project creds we just wrote to populate global as well
1178+ pass # Global is optional in project-only mode
1179+ else :
1180+ # Global scope — write to ~/.cems/credentials
1181+ if global_creds_file .exists () and _is_interactive () and not api_key :
1182+ console .print ()
1183+ action = _single_select (
1184+ f"Global credentials found ({ global_creds_file } )" ,
1185+ [
1186+ ("keep" , "Keep existing" ),
1187+ ("overwrite" , "Overwrite with new values" ),
1188+ ],
1189+ default = 0 ,
1190+ )
1191+ if action == "overwrite" :
1192+ if not _setup_credentials (api_url = None , api_key = None ):
1193+ raise click .Abort ()
1194+ else :
1195+ console .print (f"[green]Credentials:[/green] { global_creds_file } " )
1196+ elif not _setup_credentials (api_url = api_url , api_key = api_key ):
1197+ raise click .Abort ()
11371198
11381199 console .print ()
11391200
11401201 # Resolve API URL and key for team discovery
1141- creds = _read_credentials ()
1202+ # Read from whichever credential file we just wrote/kept
1203+ if credential_scope == "project" and project_creds_file .exists ():
1204+ from cems .shared .credentials import parse_credentials_file
1205+ creds = parse_credentials_file (str (project_creds_file ))
1206+ # Merge with global if project creds are sparse
1207+ if global_creds_file .exists ():
1208+ global_creds = _read_credentials ()
1209+ for k , v in global_creds .items ():
1210+ creds .setdefault (k , v )
1211+ else :
1212+ creds = _read_credentials ()
11421213 resolved_url = api_url or creds .get ("CEMS_API_URL" , "http://localhost:8765" )
11431214 resolved_key = api_key or creds .get ("CEMS_API_KEY" , "" )
11441215
@@ -1212,7 +1283,10 @@ def setup(install_claude: bool, install_cursor: bool, install_codex: bool, insta
12121283 table = Table (show_header = False , box = None , padding = (0 , 2 ))
12131284 table .add_column (style = "cyan" )
12141285 table .add_column (style = "white" )
1215- table .add_row ("Credentials" , str (Path .home () / ".cems" / "credentials" ))
1286+ if credential_scope == "project" and project_creds_file .exists ():
1287+ table .add_row ("Project creds" , str (project_creds_file ))
1288+ if global_creds_file .exists ():
1289+ table .add_row ("Global creds" , str (global_creds_file ))
12161290 if install_claude :
12171291 table .add_row ("Claude hooks" , str (Path .home () / ".claude" / "hooks" ))
12181292 table .add_row ("Claude skills" , str (Path .home () / ".claude" / "skills" / "cems" ))
0 commit comments