@@ -1296,8 +1296,21 @@ def push(project_root, sftp_host, sftp_username, sftp_key, project_id, project_n
12961296 if not project_root :
12971297 console .print ("[bold red]No project root specified and no .cf/project.json found in current directory. Please provide --project-root.[/bold red]" )
12981298 raise click .Abort ()
1299+
1300+ # Require platform link and login before pushing
1301+ platform_id = _load_project_platform_id (project_root )
1302+ if not platform_id :
1303+ console .print ("[bold red]Project is not linked to the platform.[/bold red]" )
1304+ console .print ("Run [bold]cf link[/bold] to connect this project, or [bold]cf init[/bold] to create a new one." )
1305+ raise click .Abort ()
1306+
12991307 # Load user config for defaults
13001308 config = load_user_config ()
1309+ api_key = config .get ("api_key" )
1310+ if not api_key :
1311+ console .print ("[bold red]Not logged in.[/bold red]" )
1312+ console .print ("Run [bold]cf login[/bold] to authenticate before pushing." )
1313+ raise click .Abort ()
13011314 if not sftp_username :
13021315 sftp_username = config .get ("sftp_username" )
13031316 if not sftp_username :
@@ -1430,32 +1443,25 @@ def push(project_root, sftp_host, sftp_username, sftp_key, project_id, project_n
14301443 sftp .close ()
14311444 transport .close ()
14321445
1433- # --- Platform sync ---
1434- platform_id = _load_project_platform_id (project_root or "." )
1435- config = load_user_config ()
1436- api_key = config .get ("api_key" )
1437-
1438- if platform_id and api_key :
1446+ # --- Platform sync (platform_id and api_key verified at top of push) ---
1447+ try :
1448+ import json as _json
1449+ with open (project_json_path , "r" ) as f :
1450+ pj = _json .load (f )
14391451 try :
1440- import json as _json
1441- with open (project_json_path , "r" ) as f :
1442- pj = _json .load (f )
1443- try :
1444- _api_put (f"/projects/{ platform_id } " , {"cli_project_json" : pj , "cli_sync_source" : "push" })
1445- console .print ("[green]✓ Platform project synced[/green]" )
1446- except SystemExit :
1447- console .print ("[yellow]⚠ SFTP upload succeeded but platform sync failed[/yellow]" )
1448- except Exception :
1449- console .print ("[yellow]⚠ Could not read project.json for platform sync[/yellow]" )
1452+ _api_put (f"/projects/{ platform_id } " , {"cli_project_json" : pj , "cli_sync_source" : "push" })
1453+ console .print ("[green]✓ Platform project synced[/green]" )
1454+ except SystemExit :
1455+ console .print ("[yellow]⚠ SFTP upload succeeded but platform sync failed[/yellow]" )
1456+ except Exception :
1457+ console .print ("[yellow]⚠ Could not read project.json for platform sync[/yellow]" )
14501458
1451- if submit :
1452- try :
1453- _api_post (f"/projects/{ platform_id } /submit" , {})
1454- console .print ("[green]✓ Project submitted for review[/green]" )
1455- except SystemExit :
1456- console .print ("[yellow]⚠ Submit failed — ensure the project has a name and description[/yellow]" )
1457- elif submit :
1458- console .print ("[yellow]⚠ --submit requires a linked platform project and active login[/yellow]" )
1459+ if submit :
1460+ try :
1461+ _api_post (f"/projects/{ platform_id } /submit" , {})
1462+ console .print ("[green]✓ Project submitted for review[/green]" )
1463+ except SystemExit :
1464+ console .print ("[yellow]⚠ Submit failed — ensure the project has a name[/yellow]" )
14591465
14601466@main .command ('pull' )
14611467@click .option ('--project-name' , required = False , help = 'Project name to pull results for (defaults to value in .cf/project.json if present).' )
@@ -1472,9 +1478,21 @@ def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
14721478 if not project_name :
14731479 console .print ("[bold red]No project name specified and no .cf/project.json found in current directory. Please provide --project-name.[/bold red]" )
14741480 raise click .Abort ()
1475-
1481+
1482+ # Require platform link and login before pulling
1483+ platform_id = _load_project_platform_id ("." )
1484+ if not platform_id :
1485+ console .print ("[bold red]Project is not linked to the platform.[/bold red]" )
1486+ console .print ("Run [bold]cf link[/bold] to connect this project, or [bold]cf init[/bold] to create a new one." )
1487+ raise click .Abort ()
1488+
14761489 # Load user config for defaults
14771490 config = load_user_config ()
1491+ api_key = config .get ("api_key" )
1492+ if not api_key :
1493+ console .print ("[bold red]Not logged in.[/bold red]" )
1494+ console .print ("Run [bold]cf login[/bold] to authenticate before pulling." )
1495+ raise click .Abort ()
14781496 if not sftp_username :
14791497 sftp_username = config .get ("sftp_username" )
14801498 if not sftp_username :
@@ -1530,15 +1548,29 @@ def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
15301548 sftp_download_recursive (sftp , remote_dir , output_dir , console = console )
15311549 console .print (f"[green]✓ All files downloaded to { output_dir } [/green]" )
15321550
1533- # Automatically update local project config if available
1551+ # Merge pulled project config into local .cf/project.json, preserving platform_project_id
15341552 pulled_config_path = os .path .join (output_dir , "config" , "project.json" )
15351553 if os .path .exists (pulled_config_path ):
15361554 local_config_path = os .path .join (".cf" , "project.json" )
15371555 os .makedirs (".cf" , exist_ok = True )
1538-
1556+
15391557 try :
1540- import shutil
1541- shutil .copy2 (pulled_config_path , local_config_path )
1558+ import json as _json
1559+ pulled_data = _json .loads (open (pulled_config_path ).read ())
1560+
1561+ existing_data = {}
1562+ if os .path .exists (local_config_path ):
1563+ existing_data = _json .loads (open (local_config_path ).read ())
1564+
1565+ saved_platform_id = existing_data .get ("project" , {}).get ("platform_project_id" )
1566+
1567+ merged = pulled_data
1568+ if saved_platform_id :
1569+ merged .setdefault ("project" , {})["platform_project_id" ] = saved_platform_id
1570+
1571+ with open (local_config_path , "w" ) as f :
1572+ _json .dump (merged , f , indent = 2 )
1573+
15421574 console .print (f"[green]✓ Project config automatically updated[/green]" )
15431575 except Exception as e :
15441576 console .print (f"[yellow]Warning: Failed to update project config: { e } [/yellow]" )
@@ -1555,39 +1587,35 @@ def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
15551587 transport .close ()
15561588 console .print (f"[dim]Disconnected from { sftp_host } [/dim]" )
15571589
1558- # --- Platform sync and review notes ---
1559- platform_id = _load_project_platform_id ("." )
1560- pull_config = load_user_config ()
1561- api_key = pull_config .get ("api_key" )
1562-
1563- if platform_id and api_key :
1564- # Sync local project.json to platform
1565- local_pj = os .path .join ("." , ".cf" , "project.json" )
1566- if os .path .exists (local_pj ):
1567- try :
1568- import json as _json
1569- with open (local_pj , "r" ) as f :
1570- pj = _json .load (f )
1571- _api_put (f"/projects/{ platform_id } " , {"cli_project_json" : pj , "cli_sync_source" : "pull" })
1572- console .print ("[green]✓ Platform project synced[/green]" )
1573- except (SystemExit , Exception ):
1574- pass
1575-
1590+ # --- Platform sync and review notes (platform_id and api_key verified at top of pull) ---
1591+ local_pj = os .path .join ("." , ".cf" , "project.json" )
1592+ if os .path .exists (local_pj ):
15761593 try :
1577- project = _api_get (f"/projects/{ platform_id } " )
1578- status = project .get ("status" , "" )
1579- notes = project .get ("admin_review_notes" )
1580- if notes :
1581- from rich .panel import Panel
1582- style = "bold red" if status == "CHANGES_REQUESTED" else "yellow"
1583- console .print ()
1584- console .print (Panel (
1585- notes ,
1586- title = "Review Notes" if status != "CHANGES_REQUESTED" else "Changes Requested" ,
1587- border_style = style ,
1588- ))
1594+ import json as _json
1595+ with open (local_pj , "r" ) as f :
1596+ pj = _json .load (f )
1597+ _api_put (f"/projects/{ platform_id } " , {"cli_project_json" : pj , "cli_sync_source" : "pull" })
1598+ console .print ("[green]✓ Platform project synced[/green]" )
15891599 except SystemExit :
1590- pass
1600+ console .print ("[yellow]⚠ SFTP download succeeded but platform sync failed[/yellow]" )
1601+ except Exception :
1602+ console .print ("[yellow]⚠ Could not read project.json for platform sync[/yellow]" )
1603+
1604+ try :
1605+ project = _api_get (f"/projects/{ platform_id } " )
1606+ status = project .get ("status" , "" )
1607+ notes = project .get ("admin_review_notes" )
1608+ if notes :
1609+ from rich .panel import Panel
1610+ style = "bold red" if status == "CHANGES_REQUESTED" else "yellow"
1611+ console .print ()
1612+ console .print (Panel (
1613+ notes ,
1614+ title = "Review Notes" if status != "CHANGES_REQUESTED" else "Changes Requested" ,
1615+ border_style = style ,
1616+ ))
1617+ except SystemExit :
1618+ pass
15911619
15921620STATUS_COLORS = {
15931621 "DRAFT" : "dim" ,
0 commit comments