tamarind-cli 0.2.0__tar.gz → 0.3.1__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 (126) hide show
  1. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/.github/workflows/ci.yml +41 -0
  2. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/PKG-INFO +53 -2
  3. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/README.md +47 -0
  4. tamarind_cli-0.3.1/docs/architecture.md +102 -0
  5. tamarind_cli-0.3.1/openapi/openapi-python-client.json +4 -0
  6. tamarind_cli-0.3.1/openapi/pipelines-openapi-python-client.json +4 -0
  7. tamarind_cli-0.3.1/openapi/pipelines-v1.json +555 -0
  8. tamarind_cli-0.3.1/openapi/pipelines-v1.lock.json +8 -0
  9. tamarind_cli-0.3.1/openapi/public-v1.json +1820 -0
  10. tamarind_cli-0.3.1/openapi/public-v1.lock.json +8 -0
  11. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/pyproject.toml +5 -1
  12. tamarind_cli-0.3.1/scripts/check_custom_tools_provenance.py +53 -0
  13. tamarind_cli-0.3.1/scripts/sync_custom_tools_contract.py +349 -0
  14. tamarind_cli-0.3.1/scripts/sync_pipelines_contract.py +184 -0
  15. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/__init__.py +10 -1
  16. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/config.py +4 -5
  17. tamarind_cli-0.3.1/src/tamarind/custom_tools/__init__.py +31 -0
  18. tamarind_cli-0.3.1/src/tamarind/custom_tools/_contract.py +3 -0
  19. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/__init__.py +8 -0
  20. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/__init__.py +1 -0
  21. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/__init__.py +1 -0
  22. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/build_custom_tool_version.py +228 -0
  23. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/cancel_custom_tool_build.py +224 -0
  24. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/create_custom_tool.py +185 -0
  25. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/create_custom_tool_upload.py +214 -0
  26. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/delete_custom_tool.py +205 -0
  27. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/get_custom_tool.py +177 -0
  28. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/get_custom_tool_version.py +216 -0
  29. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/list_custom_tool_build_logs.py +249 -0
  30. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/list_custom_tool_versions.py +273 -0
  31. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/list_custom_tools.py +252 -0
  32. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/publish_custom_tool_version.py +224 -0
  33. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/update_custom_tool.py +233 -0
  34. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/client.py +272 -0
  35. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/errors.py +16 -0
  36. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/__init__.py +57 -0
  37. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_build_error.py +50 -0
  38. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_build_event.py +50 -0
  39. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_build_log_page.py +110 -0
  40. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_build_result.py +59 -0
  41. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_build_result_action.py +10 -0
  42. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_create_custom_tool_request.py +109 -0
  43. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_create_custom_tool_request_gputype.py +13 -0
  44. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_create_custom_tool_request_memory.py +16 -0
  45. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_create_version_request.py +50 -0
  46. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_custom_tool.py +242 -0
  47. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_custom_tool_gputype.py +13 -0
  48. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_custom_tool_memory.py +16 -0
  49. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_custom_tool_page.py +70 -0
  50. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_custom_tool_status.py +10 -0
  51. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_problem.py +137 -0
  52. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_problem_errors_type_0_item.py +47 -0
  53. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_update_custom_tool_request.py +304 -0
  54. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_update_custom_tool_request_gpu_type_type_0.py +13 -0
  55. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_update_custom_tool_request_memory_type_0.py +16 -0
  56. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_upload_session.py +91 -0
  57. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_upload_session_uploadheaders.py +47 -0
  58. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_version.py +160 -0
  59. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_version_page.py +70 -0
  60. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_version_status.py +11 -0
  61. tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/types.py +54 -0
  62. tamarind_cli-0.3.1/src/tamarind/custom_tools/client.py +39 -0
  63. tamarind_cli-0.3.1/src/tamarind/custom_tools/packaging.py +584 -0
  64. tamarind_cli-0.3.1/src/tamarind/custom_tools/resources.py +755 -0
  65. tamarind_cli-0.3.1/src/tamarind/custom_tools/transport.py +337 -0
  66. tamarind_cli-0.3.1/src/tamarind/custom_tools/validation.py +146 -0
  67. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/errors.py +48 -0
  68. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/http.py +121 -20
  69. tamarind_cli-0.3.1/src/tamarind/pipelines/__init__.py +17 -0
  70. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/__init__.py +8 -0
  71. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/api/__init__.py +1 -0
  72. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/api/pipelines_public/__init__.py +1 -0
  73. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/api/pipelines_public/get_run.py +177 -0
  74. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/api/pipelines_public/list_node_run_molecules.py +268 -0
  75. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/client.py +272 -0
  76. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/errors.py +16 -0
  77. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/__init__.py +27 -0
  78. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/node_run_status.py +15 -0
  79. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_node_run.py +168 -0
  80. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_node_run_molecule.py +127 -0
  81. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_node_run_molecule_page.py +89 -0
  82. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_node_run_molecule_scores.py +47 -0
  83. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_problem.py +137 -0
  84. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_problem_errors_type_0_item.py +47 -0
  85. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_run.py +171 -0
  86. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_run_inputs.py +47 -0
  87. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/run_status.py +13 -0
  88. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/source.py +9 -0
  89. tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/types.py +54 -0
  90. tamarind_cli-0.3.1/src/tamarind/pipelines/resources.py +204 -0
  91. tamarind_cli-0.3.1/src/tamarind/pipelines/transport.py +67 -0
  92. tamarind_cli-0.3.1/src/tamarind/py.typed +0 -0
  93. tamarind_cli-0.3.1/tests/test_custom_tools_generation.py +322 -0
  94. tamarind_cli-0.3.1/tests/test_custom_tools_packaging.py +744 -0
  95. tamarind_cli-0.3.1/tests/test_custom_tools_sdk.py +1009 -0
  96. tamarind_cli-0.3.1/tests/test_pep561.py +5 -0
  97. tamarind_cli-0.3.1/tests/test_pipelines_sdk.py +166 -0
  98. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_rest.py +120 -17
  99. tamarind_cli-0.3.1/uv.lock +1018 -0
  100. tamarind_cli-0.2.0/docs/architecture.md +0 -52
  101. tamarind_cli-0.2.0/uv.lock +0 -406
  102. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/.github/workflows/publish.yml +0 -0
  103. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/.gitignore +0 -0
  104. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/LICENSE +0 -0
  105. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/install.sh +0 -0
  106. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/catalog.py +0 -0
  107. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/__init__.py +0 -0
  108. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/commands/__init__.py +0 -0
  109. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/commands/auth.py +0 -0
  110. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/commands/catalog.py +0 -0
  111. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/commands/files.py +0 -0
  112. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/commands/jobs.py +0 -0
  113. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/guidance.py +0 -0
  114. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/inputs.py +0 -0
  115. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/main.py +0 -0
  116. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/output.py +0 -0
  117. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/jobs.py +0 -0
  118. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/rest.py +0 -0
  119. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_agent_contract.py +0 -0
  120. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_bugbash.py +0 -0
  121. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_cli_commands.py +0 -0
  122. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_config.py +0 -0
  123. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_file_filters.py +0 -0
  124. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_inputs.py +0 -0
  125. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_jobs.py +0 -0
  126. {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_output.py +0 -0
@@ -9,6 +9,47 @@ 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
+ uv run --extra dev openapi-python-client generate --path openapi/pipelines-v1.json \
29
+ --config openapi/pipelines-openapi-python-client.json --meta none \
30
+ --output-path "$tmp_dir/pipelines-generated" --overwrite
31
+ diff -ru --exclude=.ruff_cache src/tamarind/pipelines/_generated "$tmp_dir/pipelines-generated"
32
+
33
+ typecheck:
34
+ name: SDK type check
35
+ runs-on: ubuntu-latest
36
+ timeout-minutes: 5
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ - uses: astral-sh/setup-uv@v5
40
+ with:
41
+ enable-cache: true
42
+ python-version: "3.12"
43
+ - name: Check the public SDK and transport boundary
44
+ run: >-
45
+ uv run --extra dev mypy
46
+ src/tamarind/custom_tools
47
+ src/tamarind/pipelines
48
+ src/tamarind/http.py
49
+ src/tamarind/errors.py
50
+ scripts/sync_custom_tools_contract.py
51
+ scripts/sync_pipelines_contract.py
52
+
12
53
  package:
13
54
  name: Build release artifacts
14
55
  runs-on: ubuntu-latest
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: tamarind-cli
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
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
5
  Project-URL: Homepage, https://tamarind.bio
6
6
  Project-URL: Documentation, https://app.tamarind.bio/api-docs
@@ -10,10 +10,14 @@ License: Apache-2.0
10
10
  License-File: LICENSE
11
11
  Keywords: agents,alphafold,bioinformatics,boltz,cli,protein
12
12
  Requires-Python: >=3.10
13
+ Requires-Dist: attrs>=23.1
13
14
  Requires-Dist: httpx>=0.27
14
15
  Requires-Dist: pyyaml>=6.0
15
16
  Requires-Dist: typer>=0.12
17
+ Requires-Dist: typing-extensions>=4.7
16
18
  Provides-Extra: dev
19
+ Requires-Dist: mypy>=1.11; extra == 'dev'
20
+ Requires-Dist: openapi-python-client==0.28.4; extra == 'dev'
17
21
  Requires-Dist: pytest>=8.0; extra == 'dev'
18
22
  Requires-Dist: respx>=0.21; extra == 'dev'
19
23
  Requires-Dist: ruff>=0.5; extra == 'dev'
@@ -99,6 +103,53 @@ tamarind submit boltz \
99
103
  --name quick-fold
100
104
  ```
101
105
 
106
+ ## Python SDK
107
+
108
+ The package exposes typed Pipelines and Custom Tools resources. Pipeline runs use
109
+ the same node-run terminology as the web app and public REST API:
110
+
111
+ ```python
112
+ from tamarind import Tamarind
113
+
114
+ with Tamarind() as client:
115
+ run = client.pipelines.get_run("run-id")
116
+ for node_run in run.node_runs:
117
+ page = node_run.molecules(limit=100)
118
+ print(node_run.label, page.items)
119
+ ```
120
+
121
+ The Custom Tools resource checks archive-local
122
+ safety, packages a folder, uploads it directly to object storage, and submits the
123
+ digest-checked build request. The server
124
+ remains authoritative for source readiness, Versions, logs, and the evolving
125
+ `config.json` contract.
126
+
127
+ ```python
128
+ from tamarind import Tamarind
129
+ from tamarind.errors import CustomToolNotFoundError
130
+
131
+ with Tamarind() as client:
132
+ try:
133
+ tool = client.custom_tools.get("my-esmfold")
134
+ except CustomToolNotFoundError:
135
+ tool = client.custom_tools.create("my-esmfold", display_name="My ESMFold")
136
+
137
+ result = tool.build("./my-esmfold")
138
+ print(result.action) # build, reuse_image, or unchanged
139
+ version = result.version
140
+ if not version.terminal:
141
+ version = version.monitor(timeout=1800, on_event=print)
142
+ ```
143
+
144
+ `build()` returns a typed result describing what the request did and the durable
145
+ Version it produced. It is convenience orchestration, not a durable request
146
+ object. If the connection is lost during the build response, fetch the tool's
147
+ versions before retrying. Interrupting `monitor()` stops local monitoring; it
148
+ does not cancel the remote build.
149
+
150
+ Custom Tool CLI commands are not part of this release. Existing CLI commands
151
+ remain unchanged.
152
+
102
153
  ## Output for agents
103
154
 
104
155
  Every command emits JSON when stdout is not a TTY, or with `--json`. Result
@@ -78,6 +78,53 @@ tamarind submit boltz \
78
78
  --name quick-fold
79
79
  ```
80
80
 
81
+ ## Python SDK
82
+
83
+ The package exposes typed Pipelines and Custom Tools resources. Pipeline runs use
84
+ the same node-run terminology as the web app and public REST API:
85
+
86
+ ```python
87
+ from tamarind import Tamarind
88
+
89
+ with Tamarind() as client:
90
+ run = client.pipelines.get_run("run-id")
91
+ for node_run in run.node_runs:
92
+ page = node_run.molecules(limit=100)
93
+ print(node_run.label, page.items)
94
+ ```
95
+
96
+ The Custom Tools resource checks archive-local
97
+ safety, packages a folder, uploads it directly to object storage, and submits the
98
+ digest-checked build request. The server
99
+ remains authoritative for source readiness, Versions, logs, and the evolving
100
+ `config.json` contract.
101
+
102
+ ```python
103
+ from tamarind import Tamarind
104
+ from tamarind.errors import CustomToolNotFoundError
105
+
106
+ with Tamarind() as client:
107
+ try:
108
+ tool = client.custom_tools.get("my-esmfold")
109
+ except CustomToolNotFoundError:
110
+ tool = client.custom_tools.create("my-esmfold", display_name="My ESMFold")
111
+
112
+ result = tool.build("./my-esmfold")
113
+ print(result.action) # build, reuse_image, or unchanged
114
+ version = result.version
115
+ if not version.terminal:
116
+ version = version.monitor(timeout=1800, on_event=print)
117
+ ```
118
+
119
+ `build()` returns a typed result describing what the request did and the durable
120
+ Version it produced. It is convenience orchestration, not a durable request
121
+ object. If the connection is lost during the build response, fetch the tool's
122
+ versions before retrying. Interrupting `monitor()` stops local monitoring; it
123
+ does not cancel the remote build.
124
+
125
+ Custom Tool CLI commands are not part of this release. Existing CLI commands
126
+ remain unchanged.
127
+
81
128
  ## Output for agents
82
129
 
83
130
  Every command emits JSON when stdout is not a TTY, or with `--json`. Result
@@ -0,0 +1,102 @@
1
+ # Architecture & no-drift design
2
+
3
+ The CLI, the [MCP server](https://mcp.tamarind.bio), and the web app are all
4
+ **thin clients** over the same platform. The CLI never re-implements business
5
+ logic; it only knows how to call four well-defined surfaces. This is what keeps
6
+ the CLI and the MCP from drifting as the platform evolves.
7
+
8
+ ## Four surfaces, four single sources of truth
9
+
10
+ ### 1. Job/file REST surface — source of truth: the OpenAPI spec
11
+
12
+ `submit`, `validate`, `batch`, `jobs`, `status`/`wait`, `results`, `files`,
13
+ `cancel`, and `delete` map onto operations in `openapi-mcp.yaml` — the same
14
+ server contract used to generate the MCP surface. The CLI's
15
+ [`rest.py`](../src/tamarind/rest.py) is intentionally a small mapping of those
16
+ operations.
17
+
18
+ The CLI mapping is still client code and can drift. Unit tests cover request and
19
+ response shapes, and authenticated no-spend smoke tests should exercise auth,
20
+ catalog lookup, schema lookup, and validation before a release. Changes to the
21
+ server contract should update the CLI tests in the same change or release train.
22
+
23
+ These calls go directly to the job API (`https://app.tamarind.bio/api/`) with
24
+ the `x-api-key` header.
25
+
26
+ ### 2. Discovery surface — source of truth: a shared catalog module
27
+
28
+ `tools`, `modalities`, `functions`, and `schema` need per-org visibility logic
29
+ (which tools an account may see, per-parameter gating, example generation) that
30
+ must run server-side. So the CLI does **not** read the catalog database; it
31
+ calls the `/catalog/*` HTTP routes ([`catalog.py`](../src/tamarind/catalog.py)),
32
+ which return exactly the JSON the MCP's discovery tools return.
33
+
34
+ The MCP tools and the `/catalog/*` routes are served by the **same shared
35
+ implementation**, so a tool looks identical no matter which client you use.
36
+ Because the logic lives in one module, *where* discovery is hosted (the MCP host
37
+ today; potentially the main API or a dedicated service later) is a deployment
38
+ detail that can change without any client change and without drift.
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
+
77
+ ### 4. Pipelines read surface — source of truth: the public OpenAPI artifact
78
+
79
+ `Tamarind().pipelines` reads a run and pages through the molecules produced by one
80
+ of its node runs. The sync script selects only those operations and their referenced
81
+ schemas from the website backend's frozen public OpenAPI artifact, verifies the exact
82
+ producer Git object, and records that commit in a dedicated lock. Keeping this
83
+ projection separate prevents Pipelines changes from broadening or perturbing the
84
+ Custom Tools contract.
85
+
86
+ Generated endpoint code owns URL encoding and wire-model parsing. A small resource
87
+ adapter adds strict runtime checks and composes `PipelineRun` → `NodeRun` → molecule
88
+ pages without introducing another public vocabulary.
89
+
90
+ ## Why not a single binary that re-encodes the API?
91
+
92
+ A from-scratch client in another language would re-encode the request shapes and
93
+ the catalog logic — two copies that drift the moment the platform changes.
94
+ Keeping the CLI thin and anchored to the same OpenAPI contract (and,
95
+ server-side, the same catalog module) reduces drift and makes it testable. It
96
+ does not eliminate the need for compatibility tests and coordinated releases.
97
+
98
+ ## Configuration indirection
99
+
100
+ Endpoints are configurable (`--api-base`, `--catalog-base`, profiles), so the
101
+ same binary points at prod or staging, and the discovery host can move later
102
+ without a new release.
@@ -0,0 +1,4 @@
1
+ {
2
+ "package_name_override": "_generated",
3
+ "project_name_override": "tamarind-custom-tools-generated"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "package_name_override": "_generated",
3
+ "project_name_override": "tamarind-pipelines-generated"
4
+ }