tamarind-cli 0.2.0__tar.gz → 0.3.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.
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/.github/workflows/ci.yml +35 -0
- tamarind_cli-0.2.0/README.md → tamarind_cli-0.3.0/PKG-INFO +59 -0
- tamarind_cli-0.2.0/PKG-INFO → tamarind_cli-0.3.0/README.md +34 -21
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/docs/architecture.md +39 -2
- tamarind_cli-0.3.0/openapi/openapi-python-client.json +4 -0
- tamarind_cli-0.3.0/openapi/public-v1.json +1820 -0
- tamarind_cli-0.3.0/openapi/public-v1.lock.json +8 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/pyproject.toml +5 -1
- tamarind_cli-0.3.0/scripts/check_custom_tools_provenance.py +40 -0
- tamarind_cli-0.3.0/scripts/sync_custom_tools_contract.py +347 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/__init__.py +8 -1
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/config.py +4 -5
- tamarind_cli-0.3.0/src/tamarind/custom_tools/__init__.py +31 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_contract.py +3 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/__init__.py +8 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/__init__.py +1 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/__init__.py +1 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/build_custom_tool_version.py +220 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/cancel_custom_tool_build.py +213 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/create_custom_tool.py +185 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/create_custom_tool_upload.py +203 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/delete_custom_tool.py +205 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/get_custom_tool.py +177 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/get_custom_tool_version.py +205 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/list_custom_tool_build_logs.py +239 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/list_custom_tool_versions.py +263 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/list_custom_tools.py +252 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/publish_custom_tool_version.py +213 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/api/custom_tools/update_custom_tool.py +233 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/client.py +272 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/errors.py +16 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/__init__.py +57 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_build_error.py +50 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_build_event.py +50 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_build_log_page.py +110 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_build_result.py +59 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_build_result_action.py +10 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_create_custom_tool_request.py +109 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_create_custom_tool_request_gputype.py +13 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_create_custom_tool_request_memory.py +16 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_create_version_request.py +50 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_custom_tool.py +242 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_custom_tool_gputype.py +13 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_custom_tool_memory.py +16 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_custom_tool_page.py +70 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_custom_tool_status.py +10 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_problem.py +137 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_problem_errors_type_0_item.py +47 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_update_custom_tool_request.py +304 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_update_custom_tool_request_gpu_type_type_0.py +13 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_update_custom_tool_request_memory_type_0.py +16 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_upload_session.py +91 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_upload_session_uploadheaders.py +47 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_version.py +160 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_version_page.py +70 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/models/public_version_status.py +11 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/_generated/types.py +54 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/client.py +36 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/packaging.py +584 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/resources.py +755 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/transport.py +319 -0
- tamarind_cli-0.3.0/src/tamarind/custom_tools/validation.py +146 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/errors.py +48 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/http.py +121 -20
- tamarind_cli-0.3.0/src/tamarind/py.typed +0 -0
- tamarind_cli-0.3.0/tests/test_custom_tools_generation.py +314 -0
- tamarind_cli-0.3.0/tests/test_custom_tools_packaging.py +744 -0
- tamarind_cli-0.3.0/tests/test_custom_tools_sdk.py +1005 -0
- tamarind_cli-0.3.0/tests/test_pep561.py +5 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/tests/test_rest.py +120 -17
- tamarind_cli-0.3.0/uv.lock +1018 -0
- tamarind_cli-0.2.0/uv.lock +0 -406
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/.github/workflows/publish.yml +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/.gitignore +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/LICENSE +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/install.sh +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/catalog.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/cli/__init__.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/cli/commands/__init__.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/cli/commands/auth.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/cli/commands/catalog.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/cli/commands/files.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/cli/commands/jobs.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/cli/guidance.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/cli/inputs.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/cli/main.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/cli/output.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/jobs.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/src/tamarind/rest.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/tests/test_agent_contract.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/tests/test_bugbash.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/tests/test_cli_commands.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/tests/test_config.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/tests/test_file_filters.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/tests/test_inputs.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/tests/test_jobs.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.0}/tests/test_output.py +0 -0
|
@@ -9,6 +9,41 @@ permissions:
|
|
|
9
9
|
contents: read
|
|
10
10
|
|
|
11
11
|
jobs:
|
|
12
|
+
generated:
|
|
13
|
+
name: Generated transport is current
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
timeout-minutes: 5
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: astral-sh/setup-uv@v5
|
|
19
|
+
- name: Verify backend provenance and generated client freshness
|
|
20
|
+
run: |
|
|
21
|
+
uv run --extra dev python scripts/check_custom_tools_provenance.py
|
|
22
|
+
tmp_dir=$(mktemp -d .custom-tools-generated.XXXXXX)
|
|
23
|
+
trap 'rm -rf "$tmp_dir"' EXIT
|
|
24
|
+
uv run --extra dev openapi-python-client generate --path openapi/public-v1.json \
|
|
25
|
+
--config openapi/openapi-python-client.json --meta none \
|
|
26
|
+
--output-path "$tmp_dir/generated" --overwrite
|
|
27
|
+
diff -ru --exclude=.ruff_cache src/tamarind/custom_tools/_generated "$tmp_dir/generated"
|
|
28
|
+
|
|
29
|
+
typecheck:
|
|
30
|
+
name: Custom Tools type check
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
timeout-minutes: 5
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
- uses: astral-sh/setup-uv@v5
|
|
36
|
+
with:
|
|
37
|
+
enable-cache: true
|
|
38
|
+
python-version: "3.12"
|
|
39
|
+
- name: Check the public SDK and transport boundary
|
|
40
|
+
run: >-
|
|
41
|
+
uv run --extra dev mypy
|
|
42
|
+
src/tamarind/custom_tools
|
|
43
|
+
src/tamarind/http.py
|
|
44
|
+
src/tamarind/errors.py
|
|
45
|
+
scripts/sync_custom_tools_contract.py
|
|
46
|
+
|
|
12
47
|
package:
|
|
13
48
|
name: Build release artifacts
|
|
14
49
|
runs-on: ubuntu-latest
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tamarind-cli
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Command-line interface for the Tamarind Bio platform — submit, monitor, and download protein/molecule jobs from your terminal or an AI agent.
|
|
5
|
+
Project-URL: Homepage, https://tamarind.bio
|
|
6
|
+
Project-URL: Documentation, https://app.tamarind.bio/api-docs
|
|
7
|
+
Project-URL: Source, https://github.com/Tamarind-Bio/tamarind-cli
|
|
8
|
+
Author: Tamarind Bio
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,alphafold,bioinformatics,boltz,cli,protein
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: attrs>=23.1
|
|
14
|
+
Requires-Dist: httpx>=0.27
|
|
15
|
+
Requires-Dist: pyyaml>=6.0
|
|
16
|
+
Requires-Dist: typer>=0.12
|
|
17
|
+
Requires-Dist: typing-extensions>=4.7
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
20
|
+
Requires-Dist: openapi-python-client==0.28.4; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
1
26
|
# Tamarind CLI
|
|
2
27
|
|
|
3
28
|
[](LICENSE)
|
|
@@ -78,6 +103,40 @@ tamarind submit boltz \
|
|
|
78
103
|
--name quick-fold
|
|
79
104
|
```
|
|
80
105
|
|
|
106
|
+
## Python SDK
|
|
107
|
+
|
|
108
|
+
The package also exposes a typed Custom Tools SDK. It checks archive-local
|
|
109
|
+
safety, packages a folder, uploads it directly to object storage, and submits the
|
|
110
|
+
digest-checked build request. The server
|
|
111
|
+
remains authoritative for source readiness, Versions, logs, and the evolving
|
|
112
|
+
`config.json` contract.
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from tamarind import Tamarind
|
|
116
|
+
from tamarind.errors import CustomToolNotFoundError
|
|
117
|
+
|
|
118
|
+
with Tamarind() as client:
|
|
119
|
+
try:
|
|
120
|
+
tool = client.custom_tools.get("my-esmfold")
|
|
121
|
+
except CustomToolNotFoundError:
|
|
122
|
+
tool = client.custom_tools.create("my-esmfold", display_name="My ESMFold")
|
|
123
|
+
|
|
124
|
+
result = tool.build("./my-esmfold")
|
|
125
|
+
print(result.action) # build, reuse_image, or unchanged
|
|
126
|
+
version = result.version
|
|
127
|
+
if not version.terminal:
|
|
128
|
+
version = version.monitor(timeout=1800, on_event=print)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`build()` returns a typed result describing what the request did and the durable
|
|
132
|
+
Version it produced. It is convenience orchestration, not a durable request
|
|
133
|
+
object. If the connection is lost during the build response, fetch the tool's
|
|
134
|
+
versions before retrying. Interrupting `monitor()` stops local monitoring; it
|
|
135
|
+
does not cancel the remote build.
|
|
136
|
+
|
|
137
|
+
Custom Tool CLI commands are not part of this release. Existing CLI commands
|
|
138
|
+
remain unchanged.
|
|
139
|
+
|
|
81
140
|
## Output for agents
|
|
82
141
|
|
|
83
142
|
Every command emits JSON when stdout is not a TTY, or with `--json`. Result
|
|
@@ -1,24 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: tamarind-cli
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Command-line interface for the Tamarind Bio platform — submit, monitor, and download protein/molecule jobs from your terminal or an AI agent.
|
|
5
|
-
Project-URL: Homepage, https://tamarind.bio
|
|
6
|
-
Project-URL: Documentation, https://app.tamarind.bio/api-docs
|
|
7
|
-
Project-URL: Source, https://github.com/Tamarind-Bio/tamarind-cli
|
|
8
|
-
Author: Tamarind Bio
|
|
9
|
-
License: Apache-2.0
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Keywords: agents,alphafold,bioinformatics,boltz,cli,protein
|
|
12
|
-
Requires-Python: >=3.10
|
|
13
|
-
Requires-Dist: httpx>=0.27
|
|
14
|
-
Requires-Dist: pyyaml>=6.0
|
|
15
|
-
Requires-Dist: typer>=0.12
|
|
16
|
-
Provides-Extra: dev
|
|
17
|
-
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
18
|
-
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
19
|
-
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
20
|
-
Description-Content-Type: text/markdown
|
|
21
|
-
|
|
22
1
|
# Tamarind CLI
|
|
23
2
|
|
|
24
3
|
[](LICENSE)
|
|
@@ -99,6 +78,40 @@ tamarind submit boltz \
|
|
|
99
78
|
--name quick-fold
|
|
100
79
|
```
|
|
101
80
|
|
|
81
|
+
## Python SDK
|
|
82
|
+
|
|
83
|
+
The package also exposes a typed Custom Tools SDK. It checks archive-local
|
|
84
|
+
safety, packages a folder, uploads it directly to object storage, and submits the
|
|
85
|
+
digest-checked build request. The server
|
|
86
|
+
remains authoritative for source readiness, Versions, logs, and the evolving
|
|
87
|
+
`config.json` contract.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from tamarind import Tamarind
|
|
91
|
+
from tamarind.errors import CustomToolNotFoundError
|
|
92
|
+
|
|
93
|
+
with Tamarind() as client:
|
|
94
|
+
try:
|
|
95
|
+
tool = client.custom_tools.get("my-esmfold")
|
|
96
|
+
except CustomToolNotFoundError:
|
|
97
|
+
tool = client.custom_tools.create("my-esmfold", display_name="My ESMFold")
|
|
98
|
+
|
|
99
|
+
result = tool.build("./my-esmfold")
|
|
100
|
+
print(result.action) # build, reuse_image, or unchanged
|
|
101
|
+
version = result.version
|
|
102
|
+
if not version.terminal:
|
|
103
|
+
version = version.monitor(timeout=1800, on_event=print)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`build()` returns a typed result describing what the request did and the durable
|
|
107
|
+
Version it produced. It is convenience orchestration, not a durable request
|
|
108
|
+
object. If the connection is lost during the build response, fetch the tool's
|
|
109
|
+
versions before retrying. Interrupting `monitor()` stops local monitoring; it
|
|
110
|
+
does not cancel the remote build.
|
|
111
|
+
|
|
112
|
+
Custom Tool CLI commands are not part of this release. Existing CLI commands
|
|
113
|
+
remain unchanged.
|
|
114
|
+
|
|
102
115
|
## Output for agents
|
|
103
116
|
|
|
104
117
|
Every command emits JSON when stdout is not a TTY, or with `--json`. Result
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
The CLI, the [MCP server](https://mcp.tamarind.bio), and the web app are all
|
|
4
4
|
**thin clients** over the same platform. The CLI never re-implements business
|
|
5
|
-
logic; it only knows how to call
|
|
5
|
+
logic; it only knows how to call three well-defined surfaces. This is what keeps
|
|
6
6
|
the CLI and the MCP from drifting as the platform evolves.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Three surfaces, three single sources of truth
|
|
9
9
|
|
|
10
10
|
### 1. Job/file REST surface — source of truth: the OpenAPI spec
|
|
11
11
|
|
|
@@ -37,6 +37,43 @@ Because the logic lives in one module, *where* discovery is hosted (the MCP host
|
|
|
37
37
|
today; potentially the main API or a dedicated service later) is a deployment
|
|
38
38
|
detail that can change without any client change and without drift.
|
|
39
39
|
|
|
40
|
+
### 3. Custom Tools surface — source of truth: the public OpenAPI artifact
|
|
41
|
+
|
|
42
|
+
Custom Tool creation, source upload, build, Versions, logs, and cancellation
|
|
43
|
+
are generated from the website backend's dedicated public OpenAPI artifact:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
backend-owned custom-tools-v1.generated.json
|
|
47
|
+
-> Git-object provenance verification
|
|
48
|
+
-> vendored spec + lock
|
|
49
|
+
-> openapi-python-client==0.28.4
|
|
50
|
+
-> generated models/endpoints
|
|
51
|
+
-> thin Tamarind transport and resource adapters
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The Website owns the projection and its referenced-component closure. The CLI sync
|
|
55
|
+
verifies the exact producer repository, commit, path, and bytes before atomically
|
|
56
|
+
installing the spec, provenance lock, generated server metadata, and generated package.
|
|
57
|
+
CI regenerates with the pinned mature generator and rejects drift. The handwritten
|
|
58
|
+
layer contains only HTTP/error normalization and ergonomic resource composition; it
|
|
59
|
+
does not parse OpenAPI or maintain a second schema IR/compiler.
|
|
60
|
+
|
|
61
|
+
The SDK owns archive-local concerns that only the client can decide safely:
|
|
62
|
+
deterministic ZIP construction, symlink and junction rejection, upload limits,
|
|
63
|
+
JSON parseability and top-level object shape, and warnings about the networkless
|
|
64
|
+
runtime. Archives use stored ZIP entries so source digests do not depend on a
|
|
65
|
+
platform zlib build. Modes are also platform-stable: `run.sh` and files beginning
|
|
66
|
+
with a shebang are executable, while all other files are regular. The backend owns
|
|
67
|
+
the evolving `config.json` business contract and
|
|
68
|
+
validates it before accepting a build. The SDK deliberately does not maintain a
|
|
69
|
+
second list of configuration fields, enums, or cross-field rules.
|
|
70
|
+
|
|
71
|
+
`CustomTool.build()` composes the existing requests: create upload, PUT the archive,
|
|
72
|
+
submit the digest-checked build request, and return a typed result containing the server's
|
|
73
|
+
action plus its durable Version. It adds no server-side
|
|
74
|
+
BuildRequest, queue, lease, claim, or repair state. An ambiguous build response
|
|
75
|
+
is therefore handled honestly by listing Versions before a manual retry.
|
|
76
|
+
|
|
40
77
|
## Why not a single binary that re-encodes the API?
|
|
41
78
|
|
|
42
79
|
A from-scratch client in another language would re-encode the request shapes and
|