chipfoundry-cli 2.4.4__tar.gz → 2.4.6__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: chipfoundry-cli
3
- Version: 2.4.4
3
+ Version: 2.4.6
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
@@ -681,6 +681,10 @@ cf pull [--project-name NAME]
681
681
  **Prerequisites:** `cf login`, `cf link` (or `cf init`), `cf config`
682
682
 
683
683
  - Downloads project results from SFTP server
684
+ - **Resolves the remote results directory by `platform_project_id` (UUID), not by project name** — survives case changes (e.g. `kyttar` → `Kyttar`) and renames on the platform without manual intervention
685
+ - First asks the platform API for the canonical project name and tries `outgoing/results/<canonical_name>`
686
+ - If that path is missing, falls back to scanning `outgoing/results/*/config/project.json` for a matching `platform_project_id`
687
+ - Pass `--project-name NAME` to bypass UUID resolution and force a literal directory lookup (debugging / unlinked legacy use)
684
688
  - Saves to `sftp-output/<project_name>/`
685
689
  - **Automatically updates** your local `.cf/project.json` with the pulled version (preserving the platform link)
686
690
  - **Syncs with the platform** and displays admin review notes if your project has been reviewed
@@ -885,20 +889,26 @@ The CLI tracks your project submission state through the `submission_state` fiel
885
889
  - Connects to SFTP server securely
886
890
  - Shows clean connection status
887
891
 
888
- 2. **Download:**
892
+ 2. **Resolve remote directory by UUID:**
893
+ - Looks up the canonical project name from the platform via `platform_project_id`
894
+ - Tries `outgoing/results/<canonical_name>` first
895
+ - If that path is missing, scans `outgoing/results/*/config/project.json` for a directory whose embedded `platform_project_id` matches yours
896
+ - Warns if your local project name differs from the canonical platform name (the local copy is corrected automatically in step 4)
897
+
898
+ 3. **Download:**
889
899
  - Downloads all project results recursively
890
900
  - Shows professional download progress
891
901
  - Saves to `sftp-output/<project_name>/`
892
902
 
893
- 3. **Config Update:**
903
+ 4. **Config Update:**
894
904
  - **Automatically merges** the pulled `project.json` with your local version (preserving the platform link)
895
905
 
896
- 4. **Platform Sync:**
906
+ 5. **Platform Sync:**
897
907
  - Sends the updated `project.json` to the platform
898
908
  - Records the pull timestamp on the platform
899
909
  - Fetches and displays any admin review notes
900
910
 
901
- 5. **Success:**
911
+ 6. **Success:**
902
912
  - Shows confirmation of downloaded files, sync status, and review notes
903
913
 
904
914
  ---
@@ -655,6 +655,10 @@ cf pull [--project-name NAME]
655
655
  **Prerequisites:** `cf login`, `cf link` (or `cf init`), `cf config`
656
656
 
657
657
  - Downloads project results from SFTP server
658
+ - **Resolves the remote results directory by `platform_project_id` (UUID), not by project name** — survives case changes (e.g. `kyttar` → `Kyttar`) and renames on the platform without manual intervention
659
+ - First asks the platform API for the canonical project name and tries `outgoing/results/<canonical_name>`
660
+ - If that path is missing, falls back to scanning `outgoing/results/*/config/project.json` for a matching `platform_project_id`
661
+ - Pass `--project-name NAME` to bypass UUID resolution and force a literal directory lookup (debugging / unlinked legacy use)
658
662
  - Saves to `sftp-output/<project_name>/`
659
663
  - **Automatically updates** your local `.cf/project.json` with the pulled version (preserving the platform link)
660
664
  - **Syncs with the platform** and displays admin review notes if your project has been reviewed
@@ -859,20 +863,26 @@ The CLI tracks your project submission state through the `submission_state` fiel
859
863
  - Connects to SFTP server securely
860
864
  - Shows clean connection status
861
865
 
862
- 2. **Download:**
866
+ 2. **Resolve remote directory by UUID:**
867
+ - Looks up the canonical project name from the platform via `platform_project_id`
868
+ - Tries `outgoing/results/<canonical_name>` first
869
+ - If that path is missing, scans `outgoing/results/*/config/project.json` for a directory whose embedded `platform_project_id` matches yours
870
+ - Warns if your local project name differs from the canonical platform name (the local copy is corrected automatically in step 4)
871
+
872
+ 3. **Download:**
863
873
  - Downloads all project results recursively
864
874
  - Shows professional download progress
865
875
  - Saves to `sftp-output/<project_name>/`
866
876
 
867
- 3. **Config Update:**
877
+ 4. **Config Update:**
868
878
  - **Automatically merges** the pulled `project.json` with your local version (preserving the platform link)
869
879
 
870
- 4. **Platform Sync:**
880
+ 5. **Platform Sync:**
871
881
  - Sends the updated `project.json` to the platform
872
882
  - Records the pull timestamp on the platform
873
883
  - Fetches and displays any admin review notes
874
884
 
875
- 5. **Success:**
885
+ 6. **Success:**
876
886
  - Shows confirmation of downloaded files, sync status, and review notes
877
887
 
878
888
  ---
@@ -1,2 +1,2 @@
1
1
  """ChipFoundry CLI package: Automate project submission to SFTP."""
2
- __version__ = "2.4.1"
2
+ __version__ = "2.4.6"
@@ -205,16 +205,33 @@ def config_cmd():
205
205
  def _try_register_ssh_key(public_key: str) -> bool:
206
206
  """Attempt to register the SSH public key on the user's platform profile.
207
207
 
208
- Returns True if the key was registered successfully, False otherwise.
208
+ Calls the CLI-specific ``PUT /auth/cli/ssh-key`` endpoint so the request
209
+ stays on the public API surface. Returns True on success, False otherwise.
210
+ Errors are swallowed silently so the caller can print the manual-registration
211
+ fallback without a scary ``API request failed`` line first.
209
212
  """
213
+ import httpx as _httpx
214
+
210
215
  config = load_user_config()
211
216
  if not config.get("api_key"):
212
217
  return False
218
+
213
219
  try:
214
- _api_put("/users/me", {"ssh_public_key": public_key})
215
- return True
220
+ client, _ = _api_client()
216
221
  except SystemExit:
217
222
  return False
223
+ try:
224
+ resp = client.put("/auth/cli/ssh-key", json={"ssh_public_key": public_key})
225
+ if resp.status_code == 200:
226
+ return True
227
+ return False
228
+ except _httpx.HTTPError:
229
+ return False
230
+ finally:
231
+ try:
232
+ client.close()
233
+ except Exception:
234
+ pass
218
235
 
219
236
 
220
237
  def _print_manual_key_instructions():
@@ -2061,6 +2078,9 @@ def push(project_root, sftp_host, sftp_username, sftp_key, project_id, project_n
2061
2078
  @click.option('--sftp-key', type=click.Path(exists=True, dir_okay=False), help='Path to SFTP private key file (defaults to config).', default=None, show_default=False)
2062
2079
  def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
2063
2080
  """Download results/artifacts from SFTP output dir to local sftp-output/<project_name>."""
2081
+ # Track whether the user explicitly passed --project-name (overrides
2082
+ # canonical-name resolution via the platform API below).
2083
+ explicit_project_name = project_name
2064
2084
  # If .cf/project.json exists in cwd, use its project name as default
2065
2085
  _, cwd_project_name = get_project_json_from_cwd()
2066
2086
  if not project_name and cwd_project_name:
@@ -2125,16 +2145,67 @@ def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
2125
2145
  raise click.Abort()
2126
2146
 
2127
2147
  try:
2148
+ # Resolve the remote results directory.
2149
+ #
2150
+ # Priority:
2151
+ # 1. If the user passed --project-name explicitly, honor that name
2152
+ # verbatim (escape hatch / debugging).
2153
+ # 2. Otherwise, ask the platform API for the canonical project name
2154
+ # via the platform_project_id (UUID) and try that name first.
2155
+ # 3. If that directory does not exist on SFTP (e.g. the platform was
2156
+ # renamed but the old export directory still has the previous
2157
+ # name), scan `outgoing/results/*/config/project.json` and match
2158
+ # on `platform_project_id`. This is the authoritative UUID match
2159
+ # and survives case changes and renames.
2160
+ if explicit_project_name:
2161
+ resolved_name = explicit_project_name
2162
+ try:
2163
+ sftp.stat(f"outgoing/results/{resolved_name}")
2164
+ except Exception:
2165
+ console.print(f"[yellow]No results found for project '{resolved_name}' on SFTP server.[/yellow]")
2166
+ return
2167
+ else:
2168
+ try:
2169
+ platform_proj = _api_get(f"/projects/{platform_id}")
2170
+ except SystemExit:
2171
+ console.print(f"[red]Could not resolve canonical project name for platform_project_id={platform_id} from the platform API.[/red]")
2172
+ raise click.Abort()
2173
+ canonical_name = platform_proj.get("name") if isinstance(platform_proj, dict) else None
2174
+ if not canonical_name:
2175
+ console.print(f"[red]Platform did not return a name for project {platform_id}; cannot resolve SFTP directory.[/red]")
2176
+ raise click.Abort()
2177
+
2178
+ try:
2179
+ sftp.stat(f"outgoing/results/{canonical_name}")
2180
+ resolved_name = canonical_name
2181
+ if cwd_project_name and cwd_project_name != canonical_name:
2182
+ console.print(
2183
+ f"[yellow]Local project name '{cwd_project_name}' does not match the platform "
2184
+ f"name '{canonical_name}'. Using the platform name; your local .cf/project.json "
2185
+ f"will be updated after the pull completes.[/yellow]"
2186
+ )
2187
+ except Exception:
2188
+ console.print(
2189
+ f"[yellow]'outgoing/results/{canonical_name}' not found on SFTP. "
2190
+ f"Searching by project UUID ({platform_id})...[/yellow]"
2191
+ )
2192
+ matched_dir = _find_remote_results_dir_by_uuid(sftp, platform_id)
2193
+ if matched_dir is None:
2194
+ console.print(
2195
+ f"[yellow]No results found for project '{canonical_name}' (UUID {platform_id}) on SFTP server.[/yellow]"
2196
+ )
2197
+ return
2198
+ resolved_name = matched_dir
2199
+ console.print(
2200
+ f"[yellow]Found a results directory matching this project's UUID at "
2201
+ f"'outgoing/results/{matched_dir}'. The directory name on SFTP differs from the "
2202
+ f"platform name '{canonical_name}' — using the SFTP directory.[/yellow]"
2203
+ )
2204
+
2205
+ project_name = resolved_name
2128
2206
  remote_dir = f"outgoing/results/{project_name}"
2129
2207
  output_dir = os.path.join(os.getcwd(), "sftp-output", project_name)
2130
-
2131
- # Check if remote directory exists
2132
- try:
2133
- sftp.stat(remote_dir)
2134
- except Exception:
2135
- console.print(f"[yellow]No results found for project '{project_name}' on SFTP server.[/yellow]")
2136
- return
2137
-
2208
+
2138
2209
  # Create output directory
2139
2210
  os.makedirs(output_dir, exist_ok=True)
2140
2211
 
@@ -4718,6 +4789,34 @@ def _load_project_platform_id(project_root: str):
4718
4789
  return data.get('project', {}).get('platform_project_id')
4719
4790
 
4720
4791
 
4792
+ def _find_remote_results_dir_by_uuid(sftp, platform_id: str) -> Optional[str]:
4793
+ """Scan outgoing/results/*/config/project.json for a directory whose embedded
4794
+ platform_project_id matches `platform_id`. Returns the bare directory name
4795
+ (not the full path) of the first match, or None if no match is found.
4796
+
4797
+ Used by `cf pull` as a UUID-based fallback when the canonical project
4798
+ name from the platform does not resolve to an SFTP directory (e.g. the
4799
+ project was renamed on the platform but the old SFTP results directory
4800
+ still has the previous name on disk).
4801
+ """
4802
+ try:
4803
+ dirs = sftp.listdir("outgoing/results")
4804
+ except Exception:
4805
+ return None
4806
+
4807
+ for d in dirs:
4808
+ cfg_path = f"outgoing/results/{d}/config/project.json"
4809
+ try:
4810
+ with sftp.open(cfg_path, "r") as f:
4811
+ data = json.loads(f.read().decode("utf-8"))
4812
+ except Exception:
4813
+ continue
4814
+ proj = data.get("project", {}) if isinstance(data, dict) else {}
4815
+ if isinstance(proj, dict) and proj.get("platform_project_id") == platform_id:
4816
+ return d
4817
+ return None
4818
+
4819
+
4721
4820
  def _save_platform_id(project_root: str, platform_id: str, project_name: str = None):
4722
4821
  """Write platform_project_id (and optionally project name) into .cf/project.json."""
4723
4822
  pj = Path(project_root) / '.cf' / 'project.json'
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "chipfoundry-cli"
3
- version = "2.4.4"
3
+ version = "2.4.6"
4
4
  description = "CLI tool to automate ChipFoundry project submission to SFTP server"
5
5
  authors = ["ChipFoundry <marwan.abbas@chipfoundry.io>"]
6
6
  readme = "README.md"
File without changes