@@ -389,6 +389,59 @@ def _prompt_with_default(label: str, current: Optional[str], detected: Optional[
389389 return raw
390390
391391
392+ def _shuttle_sort_key (shuttle : dict ) -> str :
393+ """Sort shuttles by date while handling null/missing dates safely."""
394+ tapeout_date = shuttle .get ("tapeout_date" )
395+ if isinstance (tapeout_date , str ) and tapeout_date .strip ():
396+ return tapeout_date
397+ return "9999-12-31"
398+
399+
400+ def _confirm_new_project_creation () -> bool :
401+ """Ask for explicit confirmation before creating a new platform project."""
402+ return click .confirm (
403+ "Create a NEW platform project now? "
404+ "(Select 'No' if you intended to link an existing project with `cf link`.)" ,
405+ default = False ,
406+ )
407+
408+
409+ def _prompt_init_platform_action () -> str :
410+ """Ask whether init should link to an existing project or create a new one."""
411+ console .print ("\n [bold]Platform action[/bold]" )
412+ console .print (" [cyan]1[/cyan]. Link to an existing platform project" )
413+ console .print (" [cyan]2[/cyan]. Create a new platform project" )
414+ choice = console .input ("Select option [1/2, default 1]: " ).strip ()
415+ if choice in ("" , "1" ):
416+ return "link"
417+ if choice == "2" :
418+ return "create"
419+ console .print ("[yellow]Invalid selection — defaulting to linking an existing project.[/yellow]" )
420+ return "link"
421+
422+
423+ def _choose_platform_project (projects : List [dict ]) -> Optional [dict ]:
424+ """Show a numbered project list and return the selected project, if any."""
425+ console .print ("\n [bold]Your platform projects:[/bold]" )
426+ for i , p in enumerate (projects , 1 ):
427+ status_str = p .get ('status' , 'unknown' )
428+ shuttle_str = f" — { p .get ('shuttle_name' , '' )} " if p .get ('shuttle_name' ) else ""
429+ console .print (f" [cyan]{ i } [/cyan]. { p ['name' ]} { shuttle_str } [{ status_str } ]" )
430+ console .print (f" [cyan]{ len (projects ) + 1 } [/cyan]. Create a new platform project" )
431+
432+ choice = console .input ("\n Select project number: " ).strip ()
433+ try :
434+ idx = int (choice ) - 1
435+ if 0 <= idx < len (projects ):
436+ return projects [idx ]
437+ if idx == len (projects ):
438+ return None
439+ except ValueError :
440+ pass
441+ console .print ("[red]Invalid selection.[/red]" )
442+ return None
443+
444+
392445@main .command ('init' )
393446@click .option ('--project-root' , required = False , type = click .Path (file_okay = False ), help = 'Project directory (defaults to current directory).' )
394447@click .option ('--shuttle' , default = None , help = 'Shuttle name or ID to associate with the project.' )
@@ -539,12 +592,43 @@ def _merged(key_local: str, key_platform: Optional[str] = None) -> Optional[str]
539592 console .print (f" Portal: { portal_url } /projects/{ platform_id } " )
540593 return
541594
595+ if api_key :
596+ try :
597+ projects = _api_get ("/projects/me" )
598+ except SystemExit :
599+ projects = []
600+ if projects :
601+ action = _prompt_init_platform_action ()
602+ if action == "link" :
603+ selected = _choose_platform_project (projects )
604+ if selected :
605+ proj ['platform_project_id' ] = selected ['id' ]
606+ if selected .get ('name' ):
607+ old_name = proj .get ('name' )
608+ proj ['name' ] = selected ['name' ]
609+ if old_name and old_name != selected ['name' ]:
610+ console .print (
611+ f"[yellow]Updated project name: '{ old_name } ' → '{ selected ['name' ]} ' "
612+ "(synced from platform)[/yellow]"
613+ )
614+ with open (project_json_path , 'w' ) as f :
615+ json .dump (data , f , indent = 2 )
616+ portal_url = _get_portal_url ()
617+ console .print (f"\n [green]✓ Linked to existing platform project[/green]" )
618+ console .print (f" Name: { selected ['name' ]} " )
619+ console .print (f" ID: { selected ['id' ]} " )
620+ if github_repo_url :
621+ console .print (f" GitHub: { github_repo_url } " )
622+ console .print (f" Portal: { portal_url } /projects/{ selected ['id' ]} " )
623+ return
624+ console .print ("[dim]Continuing with new project creation.[/dim]" )
625+
542626 shuttle_id = shuttle
543627 if not shuttle_id :
544628 try :
545629 shuttles = _api_get ("/shuttles/available" )
546630 if shuttles :
547- shuttles .sort (key = lambda s : s . get ( 'tapeout_date' , '9999-12-31' ) )
631+ shuttles .sort (key = _shuttle_sort_key )
548632 console .print ("\n [bold]Available shuttles:[/bold]" )
549633 for i , s in enumerate (shuttles , 1 ):
550634 deadline = s .get ('tapeout_date' , '' )
@@ -571,6 +655,13 @@ def _merged(key_local: str, key_platform: Optional[str] = None) -> Optional[str]
571655 if github_repo_url :
572656 create_data ["github_repo_url" ] = github_repo_url
573657
658+ if not _confirm_new_project_creation ():
659+ with open (project_json_path , 'w' ) as f :
660+ json .dump (data , f , indent = 2 )
661+ console .print ("[yellow]Skipped platform project creation.[/yellow]" )
662+ console .print ("[dim]Tip: Run [bold]cf link[/bold] to select an existing platform project.[/dim]" )
663+ return
664+
574665 try :
575666 project_resp = _api_post ("/projects" , create_data )
576667 new_id = project_resp .get ('id' )
0 commit comments