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.
- {chipfoundry_cli-2.4.4 → chipfoundry_cli-2.4.6}/PKG-INFO +15 -5
- {chipfoundry_cli-2.4.4 → chipfoundry_cli-2.4.6}/README.md +14 -4
- {chipfoundry_cli-2.4.4 → chipfoundry_cli-2.4.6}/chipfoundry_cli/__init__.py +1 -1
- {chipfoundry_cli-2.4.4 → chipfoundry_cli-2.4.6}/chipfoundry_cli/main.py +110 -11
- {chipfoundry_cli-2.4.4 → chipfoundry_cli-2.4.6}/pyproject.toml +1 -1
- {chipfoundry_cli-2.4.4 → chipfoundry_cli-2.4.6}/LICENSE +0 -0
- {chipfoundry_cli-2.4.4 → chipfoundry_cli-2.4.6}/chipfoundry_cli/check_refs.py +0 -0
- {chipfoundry_cli-2.4.4 → chipfoundry_cli-2.4.6}/chipfoundry_cli/remote_precheck_git.py +0 -0
- {chipfoundry_cli-2.4.4 → chipfoundry_cli-2.4.6}/chipfoundry_cli/utils.py +0 -0
- {chipfoundry_cli-2.4.4 → chipfoundry_cli-2.4.6}/chipfoundry_cli/version_check.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: chipfoundry-cli
|
|
3
|
-
Version: 2.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. **
|
|
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
|
-
|
|
903
|
+
4. **Config Update:**
|
|
894
904
|
- **Automatically merges** the pulled `project.json` with your local version (preserving the platform link)
|
|
895
905
|
|
|
896
|
-
|
|
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
|
-
|
|
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. **
|
|
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
|
-
|
|
877
|
+
4. **Config Update:**
|
|
868
878
|
- **Automatically merges** the pulled `project.json` with your local version (preserving the platform link)
|
|
869
879
|
|
|
870
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|