@@ -402,8 +402,13 @@ def init(project_root, shuttle, description):
402402 local_proj = local_data .get ('project' , {}) if isinstance (local_data , dict ) else {}
403403
404404 config = load_user_config ()
405+ api_key = config .get ('api_key' )
405406 username = config .get ("sftp_username" )
406- if not username :
407+ # Try to refresh sftp_username from the platform, but don't block init on it.
408+ # SFTP accounts are only auto-provisioned once a project deposit is paid/waived/
409+ # sponsored and the user has an SSH key on their profile; init must work before
410+ # that so users can configure locally and use `cf precheck` / `cf push --remote`.
411+ if not username and api_key :
407412 try :
408413 me = _api_get ("/auth/cli/whoami" )
409414 username = me .get ("sftp_username" )
@@ -412,11 +417,10 @@ def init(project_root, shuttle, description):
412417 save_user_config (config )
413418 except SystemExit :
414419 pass
415- if not username :
416- console .print ("[bold red]No SFTP account linked to your platform account. Please run 'cf login' first.[/bold red]" )
417- raise click .Abort ()
418-
419- api_key = config .get ('api_key' )
420+ # Fall back to email (or 'unknown') purely as a label in .cf/project.json.
421+ # This field is metadata only: SFTP routing uses the live session identity,
422+ # and the backend stores cli_project_json as an opaque blob.
423+ user_label = username or config .get ("user_email" ) or "unknown"
420424 platform_id = local_proj .get ('platform_project_id' )
421425 platform_proj : Optional [dict ] = None
422426 if platform_id and api_key :
@@ -469,7 +473,7 @@ def _merged(key_local: str, key_platform: Optional[str] = None) -> Optional[str]
469473 proj = data .setdefault ('project' , {})
470474 proj ['name' ] = name
471475 proj ['type' ] = project_type
472- proj ['user' ] = username
476+ proj ['user' ] = user_label
473477 proj .setdefault ('version' , local_proj .get ('version' ) or "1" )
474478 proj .setdefault ('user_project_wrapper_hash' , local_proj .get ('user_project_wrapper_hash' , "" ))
475479 proj .setdefault ('submission_state' , local_proj .get ('submission_state' , "Draft" ))
@@ -1619,7 +1623,11 @@ def push(project_root, sftp_host, sftp_username, sftp_key, project_id, project_n
16191623 sftp_username = me .get ("sftp_username" )
16201624 if not sftp_username :
16211625 console .print ("[bold red]No SFTP account linked to your platform account.[/bold red]" )
1622- console .print ("Contact support or provide --sftp-username." )
1626+ console .print (
1627+ "An SFTP account is provisioned once a project deposit is paid/waived/sponsored "
1628+ "and an SSH public key is on your profile."
1629+ )
1630+ console .print ("Override with --sftp-username if you already know yours, or contact support." )
16231631 raise click .Abort ()
16241632 config ["sftp_username" ] = sftp_username
16251633 save_user_config (config )
@@ -1805,7 +1813,11 @@ def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
18051813 sftp_username = me .get ("sftp_username" )
18061814 if not sftp_username :
18071815 console .print ("[bold red]No SFTP account linked to your platform account.[/bold red]" )
1808- console .print ("Contact support or provide --sftp-username." )
1816+ console .print (
1817+ "An SFTP account is provisioned once a project deposit is paid/waived/sponsored "
1818+ "and an SSH public key is on your profile."
1819+ )
1820+ console .print ("Override with --sftp-username if you already know yours, or contact support." )
18091821 raise click .Abort ()
18101822 config ["sftp_username" ] = sftp_username
18111823 save_user_config (config )
@@ -2089,24 +2101,34 @@ def status(sftp_host, sftp_username, sftp_key, json_output, show_all):
20892101 platform_id = _load_project_platform_id (os .getcwd ())
20902102 if not platform_id :
20912103 console .print ("[dim]Tip: Run [bold]cf link[/bold] to connect this project to the platform.[/dim]\n " )
2104+ # SFTP listing is a best-effort extra on top of the platform status above.
2105+ # Skip it quietly when the user has no SFTP account yet (auto-provisioned
2106+ # after a project deposit is paid/waived/sponsored + an SSH key is on file).
20922107 if not sftp_username :
2093- me = _api_get ("/auth/cli/whoami" )
2094- sftp_username = me .get ("sftp_username" )
2108+ if config .get ("api_key" ):
2109+ try :
2110+ me = _api_get ("/auth/cli/whoami" )
2111+ sftp_username = me .get ("sftp_username" )
2112+ except SystemExit :
2113+ sftp_username = None
20952114 if not sftp_username :
2096- console .print ("[red]No SFTP account linked to your platform account.[/red]" )
2097- console .print ("Contact support or provide --sftp-username." )
2098- raise click .Abort ()
2115+ console .print (
2116+ "[dim]SFTP listing skipped — no SFTP account linked yet. "
2117+ "An account is provisioned once a project deposit is paid/waived/sponsored "
2118+ "and an SSH public key is on your profile.[/dim]"
2119+ )
2120+ return
20992121 config ["sftp_username" ] = sftp_username
21002122 save_user_config (config )
21012123 if not sftp_key :
21022124 sftp_key = config .get ("sftp_key" )
2103-
2125+
21042126 # Always resolve key_path to absolute path if set
21052127 if sftp_key :
21062128 key_path = os .path .abspath (os .path .expanduser (sftp_key ))
21072129 else :
21082130 key_path = DEFAULT_SSH_KEY
2109-
2131+
21102132 if not os .path .exists (key_path ):
21112133 console .print (f"[red]SFTP key file not found: { key_path } [/red]" )
21122134 console .print ("[yellow]Please run 'cf keygen' to generate a key or 'cf config' to set a custom key path.[/yellow]" )
@@ -2232,7 +2254,11 @@ def tapeouts(sftp_host, sftp_username, sftp_key, limit, days):
22322254 sftp_username = me .get ("sftp_username" )
22332255 if not sftp_username :
22342256 console .print ("[red]No SFTP account linked to your platform account.[/red]" )
2235- console .print ("Contact support or provide --sftp-username." )
2257+ console .print (
2258+ "An SFTP account is provisioned once a project deposit is paid/waived/sponsored "
2259+ "and an SSH public key is on your profile."
2260+ )
2261+ console .print ("Override with --sftp-username if you already know yours, or contact support." )
22362262 raise click .Abort ()
22372263 config ["sftp_username" ] = sftp_username
22382264 save_user_config (config )
@@ -2422,7 +2448,11 @@ def confirm(project_root, sftp_host, sftp_username, sftp_key, project_name):
24222448 sftp_username = me .get ("sftp_username" )
24232449 if not sftp_username :
24242450 console .print ("[bold red]No SFTP account linked to your platform account.[/bold red]" )
2425- console .print ("Contact support or provide --sftp-username." )
2451+ console .print (
2452+ "An SFTP account is provisioned once a project deposit is paid/waived/sponsored "
2453+ "and an SSH public key is on your profile."
2454+ )
2455+ console .print ("Override with --sftp-username if you already know yours, or contact support." )
24262456 raise click .Abort ()
24272457 config ["sftp_username" ] = sftp_username
24282458 save_user_config (config )
0 commit comments