apheris-foundry-cli 0.2.0__tar.gz → 0.4.0__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.
Files changed (39) hide show
  1. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/PKG-INFO +36 -17
  2. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/README.md +35 -16
  3. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/pyproject.toml +1 -1
  4. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apheris_foundry_cli.egg-info/PKG-INFO +36 -17
  5. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/commands/foundry.py +67 -52
  6. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/commands/login.py +5 -5
  7. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/core/auth.py +15 -13
  8. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/core/hub_requests.py +20 -3
  9. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/core/result_artifacts.py +16 -0
  10. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/main.py +168 -17
  11. apheris_foundry_cli-0.4.0/src/apherisfold_cli/version.py +1 -0
  12. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/tests/test_auth.py +8 -0
  13. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/tests/test_cli.py +334 -12
  14. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/tests/test_hub_requests.py +32 -0
  15. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/tests/test_main.py +5 -5
  16. apheris_foundry_cli-0.2.0/src/apherisfold_cli/version.py +0 -1
  17. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/LICENSE +0 -0
  18. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/setup.cfg +0 -0
  19. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apheris_foundry_cli.egg-info/SOURCES.txt +0 -0
  20. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apheris_foundry_cli.egg-info/dependency_links.txt +0 -0
  21. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apheris_foundry_cli.egg-info/entry_points.txt +0 -0
  22. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apheris_foundry_cli.egg-info/requires.txt +0 -0
  23. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apheris_foundry_cli.egg-info/top_level.txt +0 -0
  24. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/__init__.py +0 -0
  25. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/commands/__init__.py +0 -0
  26. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/core/__init__.py +0 -0
  27. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/core/config.py +0 -0
  28. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/core/context.py +0 -0
  29. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/core/errors.py +0 -0
  30. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/core/io.py +0 -0
  31. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/core/login_profiles.py +0 -0
  32. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/core/render.py +0 -0
  33. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/src/apherisfold_cli/handlers.py +0 -0
  34. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/tests/test_config.py +0 -0
  35. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/tests/test_foundry_helpers.py +0 -0
  36. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/tests/test_handlers.py +0 -0
  37. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/tests/test_io.py +0 -0
  38. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/tests/test_render.py +0 -0
  39. {apheris_foundry_cli-0.2.0 → apheris_foundry_cli-0.4.0}/tests/test_result_artifacts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apheris-foundry-cli
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Command-line interface for Apheris Foundry workflows.
5
5
  Author: Apheris
6
6
  License-Expression: LicenseRef-Apheris-Foundry-Software-License
@@ -39,37 +39,37 @@ Recommended if you already use conda for scientific Python work. A dedicated env
39
39
  #### Linux / macOS
40
40
 
41
41
  ```bash
42
- conda create -n apherisfold python=3.11 -y
43
- conda activate apherisfold
44
- pip install apherisfold-cli
42
+ conda create -n apheris-foundry python=3.11 -y
43
+ conda activate apheris-foundry
44
+ pip install apheris-foundry-cli
45
45
  ```
46
46
 
47
47
  #### Windows (Anaconda Prompt or PowerShell)
48
48
 
49
49
  ```powershell
50
- conda create -n apherisfold python=3.11 -y
51
- conda activate apherisfold
52
- pip install apherisfold-cli
50
+ conda create -n apheris-foundry python=3.11 -y
51
+ conda activate apheris-foundry
52
+ pip install apheris-foundry-cli
53
53
  ```
54
54
 
55
- Activate the environment at the start of each session with `conda activate apherisfold`.
55
+ Activate the environment at the start of each session with `conda activate apheris-foundry`.
56
56
 
57
57
  ### venv (standard Python)
58
58
 
59
59
  #### Linux / macOS (venv)
60
60
 
61
61
  ```bash
62
- python3 -m venv ~/.venvs/apherisfold
63
- source ~/.venvs/apherisfold/bin/activate
64
- pip install apherisfold-cli
62
+ python3 -m venv ~/.venvs/apheris-foundry
63
+ source ~/.venvs/apheris-foundry/bin/activate
64
+ pip install apheris-foundry-cli
65
65
  ```
66
66
 
67
67
  #### Windows (PowerShell, venv)
68
68
 
69
69
  ```powershell
70
- python -m venv $env:USERPROFILE\.venvs\apherisfold
71
- & $env:USERPROFILE\.venvs\apherisfold\Scripts\Activate.ps1
72
- pip install apherisfold-cli
70
+ python -m venv $env:USERPROFILE\.venvs\apheris-foundry
71
+ & $env:USERPROFILE\.venvs\apheris-foundry\Scripts\Activate.ps1
72
+ pip install apheris-foundry-cli
73
73
  ```
74
74
 
75
75
  Activate the environment at the start of each session with the `activate` command above.
@@ -84,7 +84,7 @@ apheris-foundry --help
84
84
  ### Upgrade
85
85
 
86
86
  ```bash
87
- pip install --upgrade apherisfold-cli
87
+ pip install --upgrade apheris-foundry-cli
88
88
  ```
89
89
 
90
90
  ## Quick start
@@ -99,6 +99,22 @@ apheris-foundry workflows list
99
99
  # Submit a run
100
100
  apheris-foundry workflows run --workflow predict --input ./request.json
101
101
 
102
+ # Inspect the model IDs, weights, and default parameters advertised by Hub
103
+ apheris-foundry workflows models --workflow predict
104
+
105
+ # Override prediction parameters with a Hub-schema-validated JSON object
106
+ apheris-foundry workflows run --workflow predict --input ./request.json \
107
+ --model-params '{"seeds":[7,9],"num_diffusion_samples":3}'
108
+
109
+ # Or provide the same JSON object from a file
110
+ apheris-foundry workflows run --workflow predict --input ./request.json \
111
+ --model-params @./model-params.json
112
+
113
+ # Select a non-default model/weight advertised by the workflow
114
+ apheris-foundry workflows run --workflow predict --input ./request.json \
115
+ --param model=<model-id> --weight <weight-version> \
116
+ --model-params @./model-params.json
117
+
102
118
  # List submitted runs
103
119
  apheris-foundry jobs list
104
120
 
@@ -132,9 +148,10 @@ apheris-foundry jobs error --id <job-id>
132
148
  | Command | Description |
133
149
  | ------- | ----------- |
134
150
  | `apheris-foundry workflows list` | List workflow definitions exposed by Hub |
135
- | `apheris-foundry workflows get --workflow <id>` | Inspect one workflow definition |
151
+ | `apheris-foundry workflows get --workflow <id>` | Inspect one workflow definition and advertised model defaults |
152
+ | `apheris-foundry workflows models --workflow <id>` | List models, weights, and default prediction parameters advertised by Hub (`--json` includes full metadata) |
136
153
  | `apheris-foundry workflows graph --workflow <id>` | Show the workflow DAG |
137
- | `apheris-foundry workflows run --workflow <id>` | Submit a workflow run |
154
+ | `apheris-foundry workflows run --workflow <id> [--model-params <JSON\|@file>]` | Submit a workflow run with Hub-schema-validated model parameters |
138
155
 
139
156
  ### Run instances (`runs`)
140
157
 
@@ -153,6 +170,8 @@ apheris-foundry workflows run --help
153
170
  apheris-foundry jobs get --help
154
171
  ```
155
172
 
173
+ `--model-params` accepts one JSON object. Use an inline object for short overrides, or prefix a JSON filename with `@` to load the object from that file. Before submitting the run, the CLI retrieves the selected model version, weight, and capability schema from Hub and validates the object. Use `workflows models` to discover available model IDs and weights.
174
+
156
175
  ## License
157
176
 
158
177
  This package is licensed under the [Apheris Foundry Software License](https://www.apheris.com/docs/hub/hub-license.html). The full license text is also included in every published wheel as `LICENSE`.
@@ -16,37 +16,37 @@ Recommended if you already use conda for scientific Python work. A dedicated env
16
16
  #### Linux / macOS
17
17
 
18
18
  ```bash
19
- conda create -n apherisfold python=3.11 -y
20
- conda activate apherisfold
21
- pip install apherisfold-cli
19
+ conda create -n apheris-foundry python=3.11 -y
20
+ conda activate apheris-foundry
21
+ pip install apheris-foundry-cli
22
22
  ```
23
23
 
24
24
  #### Windows (Anaconda Prompt or PowerShell)
25
25
 
26
26
  ```powershell
27
- conda create -n apherisfold python=3.11 -y
28
- conda activate apherisfold
29
- pip install apherisfold-cli
27
+ conda create -n apheris-foundry python=3.11 -y
28
+ conda activate apheris-foundry
29
+ pip install apheris-foundry-cli
30
30
  ```
31
31
 
32
- Activate the environment at the start of each session with `conda activate apherisfold`.
32
+ Activate the environment at the start of each session with `conda activate apheris-foundry`.
33
33
 
34
34
  ### venv (standard Python)
35
35
 
36
36
  #### Linux / macOS (venv)
37
37
 
38
38
  ```bash
39
- python3 -m venv ~/.venvs/apherisfold
40
- source ~/.venvs/apherisfold/bin/activate
41
- pip install apherisfold-cli
39
+ python3 -m venv ~/.venvs/apheris-foundry
40
+ source ~/.venvs/apheris-foundry/bin/activate
41
+ pip install apheris-foundry-cli
42
42
  ```
43
43
 
44
44
  #### Windows (PowerShell, venv)
45
45
 
46
46
  ```powershell
47
- python -m venv $env:USERPROFILE\.venvs\apherisfold
48
- & $env:USERPROFILE\.venvs\apherisfold\Scripts\Activate.ps1
49
- pip install apherisfold-cli
47
+ python -m venv $env:USERPROFILE\.venvs\apheris-foundry
48
+ & $env:USERPROFILE\.venvs\apheris-foundry\Scripts\Activate.ps1
49
+ pip install apheris-foundry-cli
50
50
  ```
51
51
 
52
52
  Activate the environment at the start of each session with the `activate` command above.
@@ -61,7 +61,7 @@ apheris-foundry --help
61
61
  ### Upgrade
62
62
 
63
63
  ```bash
64
- pip install --upgrade apherisfold-cli
64
+ pip install --upgrade apheris-foundry-cli
65
65
  ```
66
66
 
67
67
  ## Quick start
@@ -76,6 +76,22 @@ apheris-foundry workflows list
76
76
  # Submit a run
77
77
  apheris-foundry workflows run --workflow predict --input ./request.json
78
78
 
79
+ # Inspect the model IDs, weights, and default parameters advertised by Hub
80
+ apheris-foundry workflows models --workflow predict
81
+
82
+ # Override prediction parameters with a Hub-schema-validated JSON object
83
+ apheris-foundry workflows run --workflow predict --input ./request.json \
84
+ --model-params '{"seeds":[7,9],"num_diffusion_samples":3}'
85
+
86
+ # Or provide the same JSON object from a file
87
+ apheris-foundry workflows run --workflow predict --input ./request.json \
88
+ --model-params @./model-params.json
89
+
90
+ # Select a non-default model/weight advertised by the workflow
91
+ apheris-foundry workflows run --workflow predict --input ./request.json \
92
+ --param model=<model-id> --weight <weight-version> \
93
+ --model-params @./model-params.json
94
+
79
95
  # List submitted runs
80
96
  apheris-foundry jobs list
81
97
 
@@ -109,9 +125,10 @@ apheris-foundry jobs error --id <job-id>
109
125
  | Command | Description |
110
126
  | ------- | ----------- |
111
127
  | `apheris-foundry workflows list` | List workflow definitions exposed by Hub |
112
- | `apheris-foundry workflows get --workflow <id>` | Inspect one workflow definition |
128
+ | `apheris-foundry workflows get --workflow <id>` | Inspect one workflow definition and advertised model defaults |
129
+ | `apheris-foundry workflows models --workflow <id>` | List models, weights, and default prediction parameters advertised by Hub (`--json` includes full metadata) |
113
130
  | `apheris-foundry workflows graph --workflow <id>` | Show the workflow DAG |
114
- | `apheris-foundry workflows run --workflow <id>` | Submit a workflow run |
131
+ | `apheris-foundry workflows run --workflow <id> [--model-params <JSON\|@file>]` | Submit a workflow run with Hub-schema-validated model parameters |
115
132
 
116
133
  ### Run instances (`runs`)
117
134
 
@@ -130,6 +147,8 @@ apheris-foundry workflows run --help
130
147
  apheris-foundry jobs get --help
131
148
  ```
132
149
 
150
+ `--model-params` accepts one JSON object. Use an inline object for short overrides, or prefix a JSON filename with `@` to load the object from that file. Before submitting the run, the CLI retrieves the selected model version, weight, and capability schema from Hub and validates the object. Use `workflows models` to discover available model IDs and weights.
151
+
133
152
  ## License
134
153
 
135
154
  This package is licensed under the [Apheris Foundry Software License](https://www.apheris.com/docs/hub/hub-license.html). The full license text is also included in every published wheel as `LICENSE`.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "apheris-foundry-cli"
7
- version = "0.2.0"
7
+ version = "0.4.0"
8
8
  description = "Command-line interface for Apheris Foundry workflows."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apheris-foundry-cli
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Command-line interface for Apheris Foundry workflows.
5
5
  Author: Apheris
6
6
  License-Expression: LicenseRef-Apheris-Foundry-Software-License
@@ -39,37 +39,37 @@ Recommended if you already use conda for scientific Python work. A dedicated env
39
39
  #### Linux / macOS
40
40
 
41
41
  ```bash
42
- conda create -n apherisfold python=3.11 -y
43
- conda activate apherisfold
44
- pip install apherisfold-cli
42
+ conda create -n apheris-foundry python=3.11 -y
43
+ conda activate apheris-foundry
44
+ pip install apheris-foundry-cli
45
45
  ```
46
46
 
47
47
  #### Windows (Anaconda Prompt or PowerShell)
48
48
 
49
49
  ```powershell
50
- conda create -n apherisfold python=3.11 -y
51
- conda activate apherisfold
52
- pip install apherisfold-cli
50
+ conda create -n apheris-foundry python=3.11 -y
51
+ conda activate apheris-foundry
52
+ pip install apheris-foundry-cli
53
53
  ```
54
54
 
55
- Activate the environment at the start of each session with `conda activate apherisfold`.
55
+ Activate the environment at the start of each session with `conda activate apheris-foundry`.
56
56
 
57
57
  ### venv (standard Python)
58
58
 
59
59
  #### Linux / macOS (venv)
60
60
 
61
61
  ```bash
62
- python3 -m venv ~/.venvs/apherisfold
63
- source ~/.venvs/apherisfold/bin/activate
64
- pip install apherisfold-cli
62
+ python3 -m venv ~/.venvs/apheris-foundry
63
+ source ~/.venvs/apheris-foundry/bin/activate
64
+ pip install apheris-foundry-cli
65
65
  ```
66
66
 
67
67
  #### Windows (PowerShell, venv)
68
68
 
69
69
  ```powershell
70
- python -m venv $env:USERPROFILE\.venvs\apherisfold
71
- & $env:USERPROFILE\.venvs\apherisfold\Scripts\Activate.ps1
72
- pip install apherisfold-cli
70
+ python -m venv $env:USERPROFILE\.venvs\apheris-foundry
71
+ & $env:USERPROFILE\.venvs\apheris-foundry\Scripts\Activate.ps1
72
+ pip install apheris-foundry-cli
73
73
  ```
74
74
 
75
75
  Activate the environment at the start of each session with the `activate` command above.
@@ -84,7 +84,7 @@ apheris-foundry --help
84
84
  ### Upgrade
85
85
 
86
86
  ```bash
87
- pip install --upgrade apherisfold-cli
87
+ pip install --upgrade apheris-foundry-cli
88
88
  ```
89
89
 
90
90
  ## Quick start
@@ -99,6 +99,22 @@ apheris-foundry workflows list
99
99
  # Submit a run
100
100
  apheris-foundry workflows run --workflow predict --input ./request.json
101
101
 
102
+ # Inspect the model IDs, weights, and default parameters advertised by Hub
103
+ apheris-foundry workflows models --workflow predict
104
+
105
+ # Override prediction parameters with a Hub-schema-validated JSON object
106
+ apheris-foundry workflows run --workflow predict --input ./request.json \
107
+ --model-params '{"seeds":[7,9],"num_diffusion_samples":3}'
108
+
109
+ # Or provide the same JSON object from a file
110
+ apheris-foundry workflows run --workflow predict --input ./request.json \
111
+ --model-params @./model-params.json
112
+
113
+ # Select a non-default model/weight advertised by the workflow
114
+ apheris-foundry workflows run --workflow predict --input ./request.json \
115
+ --param model=<model-id> --weight <weight-version> \
116
+ --model-params @./model-params.json
117
+
102
118
  # List submitted runs
103
119
  apheris-foundry jobs list
104
120
 
@@ -132,9 +148,10 @@ apheris-foundry jobs error --id <job-id>
132
148
  | Command | Description |
133
149
  | ------- | ----------- |
134
150
  | `apheris-foundry workflows list` | List workflow definitions exposed by Hub |
135
- | `apheris-foundry workflows get --workflow <id>` | Inspect one workflow definition |
151
+ | `apheris-foundry workflows get --workflow <id>` | Inspect one workflow definition and advertised model defaults |
152
+ | `apheris-foundry workflows models --workflow <id>` | List models, weights, and default prediction parameters advertised by Hub (`--json` includes full metadata) |
136
153
  | `apheris-foundry workflows graph --workflow <id>` | Show the workflow DAG |
137
- | `apheris-foundry workflows run --workflow <id>` | Submit a workflow run |
154
+ | `apheris-foundry workflows run --workflow <id> [--model-params <JSON\|@file>]` | Submit a workflow run with Hub-schema-validated model parameters |
138
155
 
139
156
  ### Run instances (`runs`)
140
157
 
@@ -153,6 +170,8 @@ apheris-foundry workflows run --help
153
170
  apheris-foundry jobs get --help
154
171
  ```
155
172
 
173
+ `--model-params` accepts one JSON object. Use an inline object for short overrides, or prefix a JSON filename with `@` to load the object from that file. Before submitting the run, the CLI retrieves the selected model version, weight, and capability schema from Hub and validates the object. Use `workflows models` to discover available model IDs and weights.
174
+
156
175
  ## License
157
176
 
158
177
  This package is licensed under the [Apheris Foundry Software License](https://www.apheris.com/docs/hub/hub-license.html). The full license text is also included in every published wheel as `LICENSE`.
@@ -9,6 +9,7 @@ from io import BytesIO
9
9
  from pathlib import Path
10
10
  from typing import Any
11
11
 
12
+ import typer
12
13
  from rich import box
13
14
  from rich.console import Console, Group
14
15
  from rich.panel import Panel
@@ -16,9 +17,10 @@ from rich.table import Table
16
17
  from rich.text import Text
17
18
  from rich.tree import Tree
18
19
 
19
- from apherisfold_cli.core.errors import EXIT_PRECONDITION, EXIT_USAGE, CLIError
20
+ from apherisfold_cli.core.errors import EXIT_INTERNAL, EXIT_PRECONDITION, EXIT_USAGE, CLIError
20
21
  from apherisfold_cli.core.hub_requests import (
21
22
  create_foundry_run,
23
+ download_foundry_run_archive,
22
24
  download_foundry_run_artifact,
23
25
  get_foundry_run,
24
26
  get_foundry_run_logs,
@@ -706,7 +708,7 @@ def foundry_run(
706
708
  final = _poll_until_terminal(run_id)
707
709
  if json_output:
708
710
  run_simple_listing(task_name="foundry.run", payload=final)
709
-
711
+ _exit_if_failed_run(final)
710
712
  return
711
713
 
712
714
  final_status = str(final.get("status", ""))
@@ -714,6 +716,7 @@ def foundry_run(
714
716
  _CONSOLE.print(f"[{style}]Job {run_id} finished: {final_status}[/{style}]")
715
717
  if final.get("error"):
716
718
  _CONSOLE.print(f"[red]Error: {final['error']}[/red]")
719
+ _exit_if_failed_run(final)
717
720
 
718
721
 
719
722
  _DEFAULT_LIST_LIMIT = 30
@@ -783,10 +786,14 @@ def foundry_models(workflow: str, *, json_output: bool) -> None:
783
786
  table.add_column("VERSION", overflow="fold")
784
787
  table.add_column("WEIGHT", no_wrap=True)
785
788
  table.add_column("CAPABILITIES", overflow="fold")
789
+ table.add_column("DEFAULT PARAMETERS", overflow="fold")
786
790
  table.add_column("DESCRIPTION", overflow="fold")
787
791
  for model in models:
788
792
  if not isinstance(model, dict):
789
793
  continue
794
+ wrapper = model.get("wrapper") if isinstance(model.get("wrapper"), dict) else {}
795
+ defaults = wrapper.get("predictParamsDefaults") if isinstance(wrapper, dict) else {}
796
+ defaults_text = json.dumps(defaults, sort_keys=True) if isinstance(defaults, dict) and defaults else ""
790
797
  weights = model.get("weights") if isinstance(model.get("weights"), list) else []
791
798
  if not weights:
792
799
  table.add_row(
@@ -794,6 +801,7 @@ def foundry_models(workflow: str, *, json_output: bool) -> None:
794
801
  str(model.get("version", "")),
795
802
  "",
796
803
  ", ".join(str(item) for item in model.get("capabilities", []) if item),
804
+ defaults_text,
797
805
  "",
798
806
  )
799
807
  continue
@@ -810,14 +818,20 @@ def foundry_models(workflow: str, *, json_output: bool) -> None:
810
818
  str(model.get("version", "")),
811
819
  f"{weight.get('version', '')}{marker}",
812
820
  ", ".join(str(item) for item in capabilities if item),
821
+ defaults_text,
813
822
  str(weight.get("description", "")),
814
823
  )
815
824
 
816
825
  examples = Table(box=box.SIMPLE_HEAD, show_header=False, padding=(0, 1), expand=False)
817
826
  examples.add_column("COMMAND", style="cyan", overflow="fold")
827
+ examples.add_row(f"apheris-foundry workflows run --workflow {workflow} --input request.json")
828
+ examples.add_row(
829
+ f"apheris-foundry workflows run --workflow {workflow} --weight <weight-version> --input request.json"
830
+ )
818
831
  examples.add_row(
819
- f"apheris-foundry workflows run --workflow {workflow} --param model=mock --param weight=0.0.0 --input request.json"
832
+ f'apheris-foundry workflows run --workflow {workflow} --model-params \'{{"seeds":[42],"num_diffusion_samples":1}}\''
820
833
  )
834
+ examples.add_row(f"apheris-foundry workflows run --workflow {workflow} --model-params @model-params.json")
821
835
  examples.add_row(f"apheris-foundry workflows get --workflow {workflow}")
822
836
  run_simple_listing(
823
837
  task_name="foundry.models",
@@ -948,19 +962,9 @@ def foundry_download_result(
948
962
  task_name = "foundry.predict.download-result"
949
963
  selected_result_view = validate_result_view(result_view, task=task_name)
950
964
  run = get_foundry_run(run_id)
951
- artifact = _resolve_download_artifact(run, artifact_id)
952
- if artifact is None:
953
- raise CLIError(
954
- code=EXIT_PRECONDITION,
955
- category="precondition",
956
- message=(
957
- f"No results.zip output is available for Foundry job {run_id}. "
958
- f"Check files with: apheris-foundry jobs get --id {run_id}"
959
- ),
960
- task=task_name,
961
- )
962
965
 
963
- if not _is_results_archive_artifact(artifact):
966
+ if artifact_id:
967
+ artifact = _resolve_artifact(run, artifact_id)
964
968
  _download_selected_artifact(
965
969
  run_id=run_id,
966
970
  artifact=artifact,
@@ -970,10 +974,9 @@ def foundry_download_result(
970
974
  )
971
975
  return
972
976
 
973
- archive_path = str(artifact.get("path", ""))
974
977
  destination = (output_dir or default_results_dir("foundry-predict", run_id)).expanduser().resolve()
975
978
  _ensure_download_dir(destination, task_name=task_name)
976
- archive_bytes = download_foundry_run_artifact(run_id, archive_path)
979
+ archive_bytes = download_foundry_run_archive(run_id)
977
980
  results = extract_results_archive(
978
981
  archive_bytes=archive_bytes,
979
982
  output_dir=destination,
@@ -1077,13 +1080,6 @@ def foundry_error(run_id: str) -> None:
1077
1080
  _CONSOLE.print(Group(*panels))
1078
1081
 
1079
1082
 
1080
- def _foundry_results_archive_path(run: dict[str, Any]) -> str:
1081
- artifact = _resolve_download_artifact(run, None)
1082
- if artifact is not None:
1083
- return str(artifact.get("path", ""))
1084
- return ""
1085
-
1086
-
1087
1083
  def _foundry_file_panels(run: dict[str, Any], *, result_preview: dict[str, Any] | None) -> list[Panel]:
1088
1084
  artifacts = _indexed_artifacts(run)
1089
1085
  workflow = str(run.get("workflow", ""))
@@ -1126,7 +1122,7 @@ def _foundry_commands_panel(run: dict[str, Any], *, result_preview: dict[str, An
1126
1122
  status = str(run.get("status", "")).lower()
1127
1123
  table = Table(box=box.SIMPLE_HEAD, show_header=False, padding=(0, 1), expand=False)
1128
1124
  table.add_column("command", style="cyan", overflow="fold")
1129
- has_results = _resolve_download_artifact(run, None) is not None
1125
+ has_results = _has_downloadable_results(run)
1130
1126
  if has_results:
1131
1127
  table.add_row(f"{command_prefix} jobs download --id {run_id}")
1132
1128
  table.add_row(f"{command_prefix} jobs get --id {run_id} --artifact-id <id>")
@@ -1206,13 +1202,10 @@ def _slug(value: str) -> str:
1206
1202
  return slug or "artifact"
1207
1203
 
1208
1204
 
1209
- def _resolve_download_artifact(run: dict[str, Any], artifact_id: str | None) -> dict[str, Any] | None:
1210
- if artifact_id:
1211
- return _resolve_artifact(run, artifact_id)
1212
- for artifact in _indexed_artifacts(run):
1213
- if _is_results_archive_artifact(artifact):
1214
- return artifact
1215
- return None
1205
+ def _has_downloadable_results(run: dict[str, Any]) -> bool:
1206
+ observability = run.get("observability") if isinstance(run.get("observability"), dict) else {}
1207
+ outputs = observability.get("outputs") if isinstance(observability.get("outputs"), list) else []
1208
+ return any(isinstance(artifact, dict) and str(artifact.get("path", "")).strip() for artifact in outputs)
1216
1209
 
1217
1210
 
1218
1211
  def _resolve_artifact(run: dict[str, Any], artifact_id: str) -> dict[str, Any]:
@@ -1230,10 +1223,6 @@ def _resolve_artifact(run: dict[str, Any], artifact_id: str) -> dict[str, Any]:
1230
1223
  )
1231
1224
 
1232
1225
 
1233
- def _is_results_archive_artifact(artifact: dict[str, Any]) -> bool:
1234
- return Path(str(artifact.get("path", ""))).name == "results.zip"
1235
-
1236
-
1237
1226
  def _download_selected_artifact(
1238
1227
  *,
1239
1228
  run_id: str,
@@ -1413,9 +1402,7 @@ def _artifact_action_panel(run: dict[str, Any], artifact: dict[str, Any]) -> Pan
1413
1402
  workflow = str(run.get("workflow", ""))
1414
1403
  command_prefix = _foundry_command_prefix(workflow)
1415
1404
  artifact_id = str(artifact.get("id", ""))
1416
- if _is_results_archive_artifact(artifact):
1417
- command = f"{command_prefix} jobs download --id {run_id}"
1418
- elif artifact_id:
1405
+ if artifact_id:
1419
1406
  command = f"{command_prefix} jobs download --id {run_id} --artifact-id {artifact_id}"
1420
1407
  else:
1421
1408
  command = f"{command_prefix} jobs download --id {run_id}"
@@ -1478,26 +1465,22 @@ def _foundry_queries_panel(request_data: dict[str, Any] | None) -> Panel | None:
1478
1465
 
1479
1466
  def _load_foundry_result_preview(run: dict[str, Any]) -> dict[str, Any] | None:
1480
1467
  run_id = str(run.get("id", ""))
1481
- archive_path = _foundry_results_archive_path(run)
1482
1468
  archive_result: dict[str, Any] | None = None
1483
- if archive_path:
1469
+ if _has_downloadable_results(run):
1484
1470
  try:
1485
- archive_bytes = download_foundry_run_artifact(run_id, archive_path)
1471
+ archive_bytes = download_foundry_run_archive(run_id)
1486
1472
  archive_result = _inspect_foundry_results_archive(archive_bytes)
1487
1473
  except CLIError:
1488
1474
  pass
1489
1475
 
1490
- # The mock model (and Foundry's execution path) writes meta/*.json as
1491
- # separate filesystem artifacts rather than packing them into results.zip.
1492
- # Fall back to loading those individual metadata artifacts when the zip
1493
- # contains no meta/ entries.
1476
+ # Fall back to individual metadata artifacts when the Hub archive endpoint
1477
+ # is unavailable or contains no meta/ entries.
1494
1478
  if archive_result is not None and not archive_result.get("metadata"):
1495
1479
  metadata = _load_metadata_artifacts(run_id, run)
1496
1480
  if metadata:
1497
1481
  archive_result["metadata"] = metadata
1498
1482
 
1499
1483
  if archive_result is None:
1500
- # No results.zip at all — try to build a minimal preview from loose artifacts.
1501
1484
  metadata = _load_metadata_artifacts(run_id, run)
1502
1485
  if metadata:
1503
1486
  archive_result = {"predictions": [], "metadata": metadata, "other_files": []}
@@ -1673,12 +1656,10 @@ def _foundry_result_guidance_panel(run: dict[str, Any], *, result_preview: dict[
1673
1656
  run_id = str(run.get("id", "")).strip()
1674
1657
  if not run_id:
1675
1658
  return None
1676
- archive_path = _foundry_results_archive_path(run)
1677
- if archive_path:
1659
+ if _has_downloadable_results(run):
1678
1660
  table = Table(box=box.SIMPLE_HEAD, show_header=True, padding=(0, 1), expand=True)
1679
1661
  table.add_column("FIELD", style="bold", no_wrap=True)
1680
1662
  table.add_column("VALUE", overflow="fold")
1681
- table.add_row("Archive", archive_path)
1682
1663
  table.add_row("Download", f"apheris-foundry jobs download --id {run_id}")
1683
1664
  if result_preview:
1684
1665
  predictions = result_preview.get("predictions", [])
@@ -1691,8 +1672,8 @@ def _foundry_result_guidance_panel(run: dict[str, Any], *, result_preview: dict[
1691
1672
 
1692
1673
  if str(run.get("status", "")).lower() == "succeeded":
1693
1674
  message = (
1694
- "No published output files are present in this Hub response yet. "
1695
- "After deploying the updated Hub, rerun the workflow or fetch this run again."
1675
+ "No output artifacts are present in this Hub response. "
1676
+ "Use the artifact IDs above to download individual files when available."
1696
1677
  )
1697
1678
  return Panel(Text(message, style="dim"), title="Results", border_style="dim")
1698
1679
  return None
@@ -1983,3 +1964,37 @@ def _poll_until_terminal(run_id: str) -> dict[str, Any]:
1983
1964
  if status in _TERMINAL_STATUSES:
1984
1965
  return run
1985
1966
  time.sleep(_POLL_INTERVAL)
1967
+
1968
+
1969
+ def _exit_if_failed_run(run: dict[str, Any]) -> None:
1970
+ if str(run.get("status", "")).lower() != "failed":
1971
+ return
1972
+ raise typer.Exit(code=_failed_run_exit_code(run))
1973
+
1974
+
1975
+ def _failed_run_exit_code(run: dict[str, Any]) -> int:
1976
+ for value in _run_exit_code_candidates(run):
1977
+ try:
1978
+ code = int(str(value).strip())
1979
+ except (TypeError, ValueError):
1980
+ continue
1981
+ if 0 < code < 256:
1982
+ return code
1983
+ return EXIT_INTERNAL
1984
+
1985
+
1986
+ def _run_exit_code_candidates(run: dict[str, Any]) -> Iterable[Any]:
1987
+ yield run.get("exitCode")
1988
+ observability = run.get("observability")
1989
+ if not isinstance(observability, dict):
1990
+ return
1991
+ steps = observability.get("steps")
1992
+ if not isinstance(steps, list):
1993
+ return
1994
+ for step in steps:
1995
+ if not isinstance(step, dict):
1996
+ continue
1997
+ status = str(step.get("status", "")).upper()
1998
+ exit_code = str(step.get("exitCode", "")).strip()
1999
+ if status in {"FAILED", "ERROR"} or exit_code not in {"", "0", "-"}:
2000
+ yield step.get("exitCode")
@@ -43,15 +43,15 @@ def _is_verbose() -> bool:
43
43
  def _build_examples_table() -> Table:
44
44
  table = Table(box=box.SIMPLE_HEAD, show_header=False, padding=(0, 1))
45
45
  table.add_column("command")
46
- table.add_row(Text("apherisfold login --url https://hub.example.com", style="cyan"))
47
- table.add_row(Text("apherisfold login --url https://hub.example.com --callback-host localhost", style="cyan"))
46
+ table.add_row(Text("apheris-foundry login --url https://hub.example.com", style="cyan"))
47
+ table.add_row(Text("apheris-foundry login --url https://hub.example.com --callback-host localhost", style="cyan"))
48
48
  table.add_row(
49
49
  Text(
50
- "apherisfold login --url https://hub.example.com --issuer https://dex.example.com --client-id apheris-hub",
50
+ "apheris-foundry login --url https://hub.example.com --issuer https://dex.example.com --client-id apheris-hub",
51
51
  style="cyan",
52
52
  )
53
53
  )
54
- table.add_row(Text("apherisfold login status", style="cyan"))
54
+ table.add_row(Text("apheris-foundry login status", style="cyan"))
55
55
  return table
56
56
 
57
57
 
@@ -70,7 +70,7 @@ def _render_login_overview() -> None:
70
70
 
71
71
  console.print(
72
72
  Group(
73
- Text("Usage: apherisfold login --url HUB_URL [--callback-host HOST]", style="bold"),
73
+ Text("Usage: apheris-foundry login --url HUB_URL [--callback-host HOST]", style="bold"),
74
74
  Text(""),
75
75
  Panel(options_table, title="Login Options", border_style="dim"),
76
76
  Panel(_build_examples_table(), title="Examples", border_style="dim"),