chipfoundry-cli 2.2.2__py3-none-any.whl → 2.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
chipfoundry_cli/main.py CHANGED
@@ -323,7 +323,16 @@ def init(project_root, shuttle, description):
323
323
  config = load_user_config()
324
324
  username = config.get("sftp_username")
325
325
  if not username:
326
- console.print("[bold red]No SFTP username found in user config. Please run 'chipfoundry config' first.[/bold red]")
326
+ try:
327
+ me = _api_get("/auth/cli/whoami")
328
+ username = me.get("sftp_username")
329
+ if username:
330
+ config["sftp_username"] = username
331
+ save_user_config(config)
332
+ except SystemExit:
333
+ pass
334
+ if not username:
335
+ console.print("[bold red]No SFTP account linked to your platform account. Please run 'cf login' first.[/bold red]")
327
336
  raise click.Abort()
328
337
 
329
338
  gds_dir = Path(project_root) / 'gds'
@@ -1313,10 +1322,14 @@ def push(project_root, sftp_host, sftp_username, sftp_key, project_id, project_n
1313
1322
  console.print("Run [bold]cf login[/bold] to authenticate before pushing.")
1314
1323
  raise click.Abort()
1315
1324
  if not sftp_username:
1316
- sftp_username = config.get("sftp_username")
1325
+ me = _api_get("/auth/cli/whoami")
1326
+ sftp_username = me.get("sftp_username")
1317
1327
  if not sftp_username:
1318
- console.print("[bold red]No SFTP username provided and not found in config. Please run 'chipfoundry init' or provide --sftp-username.[/bold red]")
1328
+ console.print("[bold red]No SFTP account linked to your platform account.[/bold red]")
1329
+ console.print("Contact support or provide --sftp-username.")
1319
1330
  raise click.Abort()
1331
+ config["sftp_username"] = sftp_username
1332
+ save_user_config(config)
1320
1333
  if not sftp_key:
1321
1334
  sftp_key = config.get("sftp_key")
1322
1335
 
@@ -1495,10 +1508,14 @@ def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
1495
1508
  console.print("Run [bold]cf login[/bold] to authenticate before pulling.")
1496
1509
  raise click.Abort()
1497
1510
  if not sftp_username:
1498
- sftp_username = config.get("sftp_username")
1511
+ me = _api_get("/auth/cli/whoami")
1512
+ sftp_username = me.get("sftp_username")
1499
1513
  if not sftp_username:
1500
- console.print("[bold red]No SFTP username provided and not found in config. Please run 'cf config' or provide --sftp-username.[/bold red]")
1514
+ console.print("[bold red]No SFTP account linked to your platform account.[/bold red]")
1515
+ console.print("Contact support or provide --sftp-username.")
1501
1516
  raise click.Abort()
1517
+ config["sftp_username"] = sftp_username
1518
+ save_user_config(config)
1502
1519
  if not sftp_key:
1503
1520
  sftp_key = config.get("sftp_key")
1504
1521
 
@@ -1751,10 +1768,14 @@ def status(sftp_host, sftp_username, sftp_key, json_output, show_all):
1751
1768
  if not platform_id:
1752
1769
  console.print("[dim]Tip: Run [bold]cf link[/bold] to connect this project to the platform.[/dim]\n")
1753
1770
  if not sftp_username:
1754
- sftp_username = config.get("sftp_username")
1771
+ me = _api_get("/auth/cli/whoami")
1772
+ sftp_username = me.get("sftp_username")
1755
1773
  if not sftp_username:
1756
- console.print("[red]No SFTP username provided and not found in config. Please run 'cf config' or provide --sftp-username.[/red]")
1774
+ console.print("[red]No SFTP account linked to your platform account.[/red]")
1775
+ console.print("Contact support or provide --sftp-username.")
1757
1776
  raise click.Abort()
1777
+ config["sftp_username"] = sftp_username
1778
+ save_user_config(config)
1758
1779
  if not sftp_key:
1759
1780
  sftp_key = config.get("sftp_key")
1760
1781
 
@@ -1885,10 +1906,14 @@ def tapeouts(sftp_host, sftp_username, sftp_key, limit, days):
1885
1906
  """Show all tapeout runs (archived projects) with their timestamps."""
1886
1907
  config = load_user_config()
1887
1908
  if not sftp_username:
1888
- sftp_username = config.get("sftp_username")
1909
+ me = _api_get("/auth/cli/whoami")
1910
+ sftp_username = me.get("sftp_username")
1889
1911
  if not sftp_username:
1890
- console.print("[red]No SFTP username provided and not found in config. Please run 'cf config' or provide --sftp-username.[/red]")
1912
+ console.print("[red]No SFTP account linked to your platform account.[/red]")
1913
+ console.print("Contact support or provide --sftp-username.")
1891
1914
  raise click.Abort()
1915
+ config["sftp_username"] = sftp_username
1916
+ save_user_config(config)
1892
1917
  if not sftp_key:
1893
1918
  sftp_key = config.get("sftp_key")
1894
1919
 
@@ -2071,10 +2096,14 @@ def confirm(project_root, sftp_host, sftp_username, sftp_key, project_name):
2071
2096
  # Load user config for defaults
2072
2097
  config = load_user_config()
2073
2098
  if not sftp_username:
2074
- sftp_username = config.get("sftp_username")
2099
+ me = _api_get("/auth/cli/whoami")
2100
+ sftp_username = me.get("sftp_username")
2075
2101
  if not sftp_username:
2076
- console.print("[bold red]No SFTP username provided and not found in config. Please run 'cf config' or provide --sftp-username.[/bold red]")
2102
+ console.print("[bold red]No SFTP account linked to your platform account.[/bold red]")
2103
+ console.print("Contact support or provide --sftp-username.")
2077
2104
  raise click.Abort()
2105
+ config["sftp_username"] = sftp_username
2106
+ save_user_config(config)
2078
2107
  if not sftp_key:
2079
2108
  sftp_key = config.get("sftp_key")
2080
2109
 
@@ -3191,6 +3220,28 @@ def repo_update(project_root, repo_owner, repo_name, branch, dry_run):
3191
3220
  raise click.Abort()
3192
3221
 
3193
3222
 
3223
+ def _upload_precheck_results(project_json_path: Path):
3224
+ """Upload precheck results to the platform (best-effort, never fatal)."""
3225
+ try:
3226
+ with open(project_json_path, "r") as f:
3227
+ pj = json.load(f)
3228
+ precheck_blob = pj.get("precheck")
3229
+ if not precheck_blob:
3230
+ return
3231
+ platform_id = pj.get("project", {}).get("platform_project_id")
3232
+ if not platform_id:
3233
+ return
3234
+ config = load_user_config()
3235
+ if not config.get("api_key"):
3236
+ return
3237
+ _api_put(f"/projects/{platform_id}", {"precheck_results": precheck_blob})
3238
+ console.print("[green]✓ Precheck results synced to platform[/green]")
3239
+ except SystemExit:
3240
+ console.print("[yellow]⚠ Precheck results could not be synced to platform[/yellow]")
3241
+ except Exception:
3242
+ console.print("[yellow]⚠ Precheck results could not be synced to platform[/yellow]")
3243
+
3244
+
3194
3245
  @main.command('precheck')
3195
3246
  @click.option('--project-root', type=click.Path(exists=True, file_okay=False), help='Path to the project directory (defaults to current directory)')
3196
3247
  @click.option('--skip-checks', multiple=True, help='Checks to skip (can be specified multiple times)')
@@ -3275,10 +3326,11 @@ def precheck(project_root, skip_checks, magic_drc, checks, dry_run):
3275
3326
  if checks:
3276
3327
  precheck_args.extend(list(checks))
3277
3328
 
3278
- inner_cmd = 'pip3 install --upgrade -q --root-user-action=ignore cf-precheck 2>/dev/null && cf-precheck ' + ' '.join(precheck_args)
3329
+ inner_cmd = 'pip3 install --upgrade -q --root-user-action=ignore cf-precheck 2>/dev/null && exec cf-precheck ' + ' '.join(precheck_args)
3279
3330
 
3280
3331
  docker_cmd = [
3281
- 'docker', 'run', '--rm',
3332
+ 'docker', 'run', '--rm', '--init',
3333
+ '--platform', 'linux/amd64',
3282
3334
  '-v', f'{project_root_path}:{project_root_path}',
3283
3335
  '-v', f'{pdk_root}:{pdk_root}',
3284
3336
  '-e', f'PDK_ROOT={pdk_root}',
@@ -3309,7 +3361,7 @@ def precheck(project_root, skip_checks, magic_drc, checks, dry_run):
3309
3361
  console.print(f"[cyan]Checking for Docker image updates...[/cyan]")
3310
3362
  try:
3311
3363
  subprocess.run(
3312
- ['docker', 'pull', docker_image],
3364
+ ['docker', 'pull', '--platform', 'linux/amd64', docker_image],
3313
3365
  check=True,
3314
3366
  capture_output=True,
3315
3367
  )
@@ -3328,11 +3380,7 @@ def precheck(project_root, skip_checks, magic_drc, checks, dry_run):
3328
3380
  console.print("[cyan]Running cf-precheck...[/cyan]\n")
3329
3381
 
3330
3382
  try:
3331
- process = subprocess.Popen(
3332
- docker_cmd,
3333
- preexec_fn=os.setsid if os.name != 'nt' else None
3334
- )
3335
-
3383
+ process = subprocess.Popen(docker_cmd)
3336
3384
  returncode = process.wait()
3337
3385
 
3338
3386
  console.print("")
@@ -3343,22 +3391,20 @@ def precheck(project_root, skip_checks, magic_drc, checks, dry_run):
3343
3391
  sys.exit(130)
3344
3392
  else:
3345
3393
  console.print(f"[red]✗[/red] Precheck failed (exit code {returncode})")
3394
+
3395
+ _upload_precheck_results(project_json_path)
3396
+
3397
+ if returncode != 0:
3346
3398
  sys.exit(returncode)
3347
3399
 
3348
3400
  except KeyboardInterrupt:
3349
3401
  console.print("\n[yellow]⚠[/yellow] Precheck interrupted by user")
3350
3402
  try:
3351
- if os.name != 'nt':
3352
- os.killpg(os.getpgid(process.pid), signal.SIGTERM)
3353
- process.wait(timeout=5)
3354
- else:
3355
- process.terminate()
3356
- process.wait(timeout=5)
3357
- except Exception:
3358
- if os.name != 'nt':
3359
- os.killpg(os.getpgid(process.pid), signal.SIGKILL)
3360
- else:
3361
- process.kill()
3403
+ process.terminate()
3404
+ process.wait(timeout=10)
3405
+ except (subprocess.TimeoutExpired, Exception):
3406
+ process.kill()
3407
+ sys.exit(130)
3362
3408
  except Exception as e:
3363
3409
  console.print(f"\n[red]✗[/red] Error running precheck: {e}")
3364
3410
 
@@ -3708,7 +3754,7 @@ def _api_put(path: str, json_data: dict):
3708
3754
  client.close()
3709
3755
 
3710
3756
 
3711
- _SYNC_KEEP_KEYS = {"project", "tapeout"}
3757
+ _SYNC_KEEP_KEYS = {"project", "tapeout", "precheck"}
3712
3758
 
3713
3759
 
3714
3760
  def _slim_project_json(pj: dict) -> dict:
@@ -3843,13 +3889,25 @@ def unlink_cmd():
3843
3889
  console.print("[green]✓ Platform link removed.[/green] The remote project is not deleted.")
3844
3890
 
3845
3891
 
3892
+ DEV_API_URL = 'https://dev-api.chipfoundry.io'
3893
+
3894
+
3846
3895
  @main.command('login')
3847
- def login_cmd():
3896
+ @click.option('--test', is_flag=True, help='Authenticate against the dev/test platform')
3897
+ def login_cmd(test):
3848
3898
  """Authenticate with ChipFoundry platform via browser."""
3849
3899
  import httpx
3850
3900
  import webbrowser
3851
3901
  import time
3852
3902
 
3903
+ config = load_user_config()
3904
+ if test:
3905
+ config['api_url'] = DEV_API_URL
3906
+ save_user_config(config)
3907
+ elif config.get('api_url') == DEV_API_URL:
3908
+ del config['api_url']
3909
+ save_user_config(config)
3910
+
3853
3911
  api_url = _get_api_url()
3854
3912
  console.print("[bold cyan]ChipFoundry CLI Login[/bold cyan]")
3855
3913
  console.print(f"Opening browser to authenticate with [bold]{api_url}[/bold]...\n")
@@ -3896,9 +3954,14 @@ def login_cmd():
3896
3954
  config['api_key'] = api_key
3897
3955
  if user_email:
3898
3956
  config['user_email'] = user_email
3957
+ sftp_username = poll_data.get('sftp_username')
3958
+ if sftp_username:
3959
+ config['sftp_username'] = sftp_username
3899
3960
  save_user_config(config)
3900
3961
 
3901
3962
  console.print(f"\n[green]✓ Logged in as {user_email or 'authenticated user'}[/green]")
3963
+ if sftp_username:
3964
+ console.print(f" SFTP account: {sftp_username}")
3902
3965
  console.print(f" API key saved to {get_config_path()}")
3903
3966
  return
3904
3967
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: chipfoundry-cli
3
- Version: 2.2.2
3
+ Version: 2.3.1
4
4
  Summary: CLI tool to automate ChipFoundry project submission to SFTP server
5
5
  Home-page: https://chipfoundry.io
6
6
  License: Apache-2.0
@@ -0,0 +1,8 @@
1
+ chipfoundry_cli/__init__.py,sha256=cYd16oPuk5XKAm2A4g07dAHBwj5DN2wO-PrKXSh6Gpo,90
2
+ chipfoundry_cli/main.py,sha256=H6hpJvtWdcXlqSZNFBnMDHxotfM7-3_U_HeGCxnFSJk,173030
3
+ chipfoundry_cli/utils.py,sha256=KV-6ySTxP3IJs1WcxVS5PIomNsWBbuh7XkXTSiqrG0w,28958
4
+ chipfoundry_cli-2.3.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
5
+ chipfoundry_cli-2.3.1.dist-info/METADATA,sha256=tNVpdI7YIu1I6hiQ-NaFYwGRJbrTCPuWG_i_iM16Dqc,29951
6
+ chipfoundry_cli-2.3.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
7
+ chipfoundry_cli-2.3.1.dist-info/entry_points.txt,sha256=Kz6Q2a6HSqWHsBUH3CbAOTI5esD9KGul2THYPYUZDwI,86
8
+ chipfoundry_cli-2.3.1.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- chipfoundry_cli/__init__.py,sha256=cYd16oPuk5XKAm2A4g07dAHBwj5DN2wO-PrKXSh6Gpo,90
2
- chipfoundry_cli/main.py,sha256=we4FI52R0Rzj9IzSnW_MC6CHF3TSxvl-WYipeMImpi4,170673
3
- chipfoundry_cli/utils.py,sha256=KV-6ySTxP3IJs1WcxVS5PIomNsWBbuh7XkXTSiqrG0w,28958
4
- chipfoundry_cli-2.2.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
5
- chipfoundry_cli-2.2.2.dist-info/METADATA,sha256=uIZQrwMhofd9aTUtlUVDYwnno9pUKSr2oWG8F77msw8,29951
6
- chipfoundry_cli-2.2.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
7
- chipfoundry_cli-2.2.2.dist-info/entry_points.txt,sha256=Kz6Q2a6HSqWHsBUH3CbAOTI5esD9KGul2THYPYUZDwI,86
8
- chipfoundry_cli-2.2.2.dist-info/RECORD,,