dyno-phi 0.3.2__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 (159) hide show
  1. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/.gitignore +13 -2
  2. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/PKG-INFO +20 -22
  3. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/README.md +17 -16
  4. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/pyproject.toml +3 -6
  5. dyno_phi-0.4.0/src/phi/_version.py +1 -0
  6. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/auth.py +19 -7
  7. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/doctor.py +6 -1
  8. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/models.py +40 -0
  9. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/skills.py +35 -12
  10. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/document_sync.py +1 -1
  11. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/config.py +6 -4
  12. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/graph.py +1 -1
  13. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/_generated.py +5 -1
  14. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/job_types.py +5 -1
  15. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/runs.py +12 -2
  16. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/oauth.py +1 -1
  17. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/runs.py +31 -6
  18. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/sequences.py +1 -0
  19. dyno_phi-0.3.2/CHANGELOG.md +0 -134
  20. dyno_phi-0.3.2/src/phi/_version.py +0 -1
  21. dyno_phi-0.3.2/tests/__init__.py +0 -0
  22. dyno_phi-0.3.2/tests/cli/__init__.py +0 -0
  23. dyno_phi-0.3.2/tests/cli/conftest.py +0 -20
  24. dyno_phi-0.3.2/tests/cli/test_artifacts.py +0 -201
  25. dyno_phi-0.3.2/tests/cli/test_audit.py +0 -125
  26. dyno_phi-0.3.2/tests/cli/test_auth.py +0 -502
  27. dyno_phi-0.3.2/tests/cli/test_cli_scaffold.py +0 -329
  28. dyno_phi-0.3.2/tests/cli/test_completion.py +0 -122
  29. dyno_phi-0.3.2/tests/cli/test_datasets.py +0 -263
  30. dyno_phi-0.3.2/tests/cli/test_doctor.py +0 -190
  31. dyno_phi-0.3.2/tests/cli/test_document_sync.py +0 -336
  32. dyno_phi-0.3.2/tests/cli/test_documents.py +0 -278
  33. dyno_phi-0.3.2/tests/cli/test_export.py +0 -162
  34. dyno_phi-0.3.2/tests/cli/test_jobs.py +0 -411
  35. dyno_phi-0.3.2/tests/cli/test_json_contract.py +0 -443
  36. dyno_phi-0.3.2/tests/cli/test_logs.py +0 -271
  37. dyno_phi-0.3.2/tests/cli/test_models_cmd.py +0 -117
  38. dyno_phi-0.3.2/tests/cli/test_params.py +0 -326
  39. dyno_phi-0.3.2/tests/cli/test_pipeline.py +0 -263
  40. dyno_phi-0.3.2/tests/cli/test_projects.py +0 -160
  41. dyno_phi-0.3.2/tests/cli/test_quickstart.py +0 -198
  42. dyno_phi-0.3.2/tests/cli/test_quota.py +0 -103
  43. dyno_phi-0.3.2/tests/cli/test_runs.py +0 -393
  44. dyno_phi-0.3.2/tests/cli/test_skills.py +0 -175
  45. dyno_phi-0.3.2/tests/conftest.py +0 -32
  46. dyno_phi-0.3.2/tests/live/__init__.py +0 -0
  47. dyno_phi-0.3.2/tests/live/conftest.py +0 -260
  48. dyno_phi-0.3.2/tests/live/test_live_artifacts_logs.py +0 -161
  49. dyno_phi-0.3.2/tests/live/test_live_auth_discovery.py +0 -126
  50. dyno_phi-0.3.2/tests/live/test_live_datasets_export.py +0 -150
  51. dyno_phi-0.3.2/tests/live/test_live_errors.py +0 -111
  52. dyno_phi-0.3.2/tests/live/test_live_jobs.py +0 -162
  53. dyno_phi-0.3.2/tests/live/test_live_pipelines.py +0 -177
  54. dyno_phi-0.3.2/tests/live/test_live_quickstart_sdk.py +0 -130
  55. dyno_phi-0.3.2/tests/test_artifacts.py +0 -81
  56. dyno_phi-0.3.2/tests/test_audit.py +0 -108
  57. dyno_phi-0.3.2/tests/test_billing.py +0 -87
  58. dyno_phi-0.3.2/tests/test_config.py +0 -142
  59. dyno_phi-0.3.2/tests/test_datasets.py +0 -400
  60. dyno_phi-0.3.2/tests/test_designs.py +0 -178
  61. dyno_phi-0.3.2/tests/test_docs_examples.py +0 -154
  62. dyno_phi-0.3.2/tests/test_documents.py +0 -290
  63. dyno_phi-0.3.2/tests/test_errors.py +0 -117
  64. dyno_phi-0.3.2/tests/test_files.py +0 -340
  65. dyno_phi-0.3.2/tests/test_graph.py +0 -409
  66. dyno_phi-0.3.2/tests/test_imports.py +0 -69
  67. dyno_phi-0.3.2/tests/test_job_types.py +0 -157
  68. dyno_phi-0.3.2/tests/test_jobs.py +0 -455
  69. dyno_phi-0.3.2/tests/test_logs.py +0 -144
  70. dyno_phi-0.3.2/tests/test_oauth.py +0 -657
  71. dyno_phi-0.3.2/tests/test_packaging.py +0 -152
  72. dyno_phi-0.3.2/tests/test_parity.py +0 -46
  73. dyno_phi-0.3.2/tests/test_projects.py +0 -201
  74. dyno_phi-0.3.2/tests/test_quota.py +0 -129
  75. dyno_phi-0.3.2/tests/test_run_admin.py +0 -74
  76. dyno_phi-0.3.2/tests/test_runs.py +0 -438
  77. dyno_phi-0.3.2/tests/test_schema_validation.py +0 -98
  78. dyno_phi-0.3.2/tests/test_sequences.py +0 -383
  79. dyno_phi-0.3.2/tests/test_tool_configs.py +0 -42
  80. dyno_phi-0.3.2/tests/test_transport.py +0 -351
  81. dyno_phi-0.3.2/tests/test_uploads.py +0 -56
  82. dyno_phi-0.3.2/tests/test_waiters.py +0 -318
  83. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/examples/README.md +0 -0
  84. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/examples/async_concurrent.py +0 -0
  85. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/examples/compose_dag.py +0 -0
  86. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/examples/download_results.py +0 -0
  87. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/examples/pipelines/fold_and_analyze.yaml +0 -0
  88. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/examples/pipelines/mpnn_esmfold_alphafold.yaml +0 -0
  89. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/examples/quickstart/README.md +0 -0
  90. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/examples/quickstart/trp_cage.fasta +0 -0
  91. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/examples/run_pipeline.py +0 -0
  92. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/examples/submit_fold.py +0 -0
  93. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/__init__.py +0 -0
  94. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/_schema_validation.py +0 -0
  95. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/_transport.py +0 -0
  96. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/async_client.py +0 -0
  97. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/__init__.py +0 -0
  98. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/__main__.py +0 -0
  99. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/__init__.py +0 -0
  100. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/artifacts.py +0 -0
  101. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/audit.py +0 -0
  102. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/billing.py +0 -0
  103. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/datasets.py +0 -0
  104. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/documents.py +0 -0
  105. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/export.py +0 -0
  106. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/jobs.py +0 -0
  107. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/logs.py +0 -0
  108. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/pipeline.py +0 -0
  109. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/projects.py +0 -0
  110. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/quickstart.py +0 -0
  111. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/quota.py +0 -0
  112. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/runs.py +0 -0
  113. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/commands/use.py +0 -0
  114. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/completion.py +0 -0
  115. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/errors.py +0 -0
  116. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/main.py +0 -0
  117. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/params.py +0 -0
  118. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/render.py +0 -0
  119. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/cli/state.py +0 -0
  120. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/client.py +0 -0
  121. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/errors.py +0 -0
  122. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/files.py +0 -0
  123. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/__init__.py +0 -0
  124. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/artifacts.py +0 -0
  125. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/audit.py +0 -0
  126. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/billing.py +0 -0
  127. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/common.py +0 -0
  128. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/datasets.py +0 -0
  129. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/designs.py +0 -0
  130. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/documents.py +0 -0
  131. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/imports.py +0 -0
  132. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/jobs.py +0 -0
  133. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/logs.py +0 -0
  134. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/projects.py +0 -0
  135. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/quota.py +0 -0
  136. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/run_admin.py +0 -0
  137. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/sequences.py +0 -0
  138. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/models/tool_configs.py +0 -0
  139. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/profiles.py +0 -0
  140. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/py.typed +0 -0
  141. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/__init__.py +0 -0
  142. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/artifacts.py +0 -0
  143. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/audit.py +0 -0
  144. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/billing.py +0 -0
  145. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/datasets.py +0 -0
  146. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/designs.py +0 -0
  147. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/documents.py +0 -0
  148. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/files.py +0 -0
  149. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/imports.py +0 -0
  150. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/job_types.py +0 -0
  151. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/jobs.py +0 -0
  152. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/logs.py +0 -0
  153. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/projects.py +0 -0
  154. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/quota.py +0 -0
  155. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/run_admin.py +0 -0
  156. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/tool_configs.py +0 -0
  157. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/resources/uploads.py +0 -0
  158. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/schema_cache.py +0 -0
  159. {dyno_phi-0.3.2 → dyno_phi-0.4.0}/src/phi/waiters.py +0 -0
@@ -39,11 +39,15 @@ htmlcov/
39
39
  *.json
40
40
  !pyproject.json
41
41
  !.cursor/environment.json
42
+ !tests/billing/quotes.json
42
43
  !tests/fixtures/metrics/ipsae_nonzero_multisubchain.json
43
44
  !tests/fixtures/bindcraft/run_metadata.json
44
45
  !clients/phi/tests/fixtures/job_types.json
45
46
  !clients/phi/tests/fixtures/run_pipelines.json
46
47
  !experiments/campaigns/y7_design/config/score_hit_calibration.json
48
+ !infrastructure/terraform/*/release.auto.tfvars.json
49
+ !.github/deploy-environments.json
50
+ !infrastructure/k8s/runner/h100-reservations.json
47
51
  # Anchored to the repo root so it only blocks a top-level credential dir,
48
52
  # not source packages that happen to be named `secrets/`.
49
53
  /secrets/
@@ -77,14 +81,21 @@ infrastructure/**/terraform.tfstate.backup
77
81
  infrastructure/**/*.tfvars
78
82
  !infrastructure/**/terraform.tfvars.example
79
83
  # Committed env var files — no secrets here; image_tag is always CI-supplied
80
- !infrastructure/terraform/staging.tfvars
81
- !infrastructure/terraform/production.tfvars
84
+ !infrastructure/terraform/legacy/staging.tfvars
85
+ !infrastructure/terraform/legacy/production.tfvars
86
+ !infrastructure/terraform/*/terraform.tfvars
82
87
  infrastructure/**/.terraform.lock.hcl
88
+ # Digger workspaces pin their providers
89
+ !infrastructure/terraform/dev/.terraform.lock.hcl
90
+ !infrastructure/terraform/staging/.terraform.lock.hcl
91
+ !infrastructure/terraform/prod/.terraform.lock.hcl
83
92
 
84
93
  # Cloud Build local artifacts
85
94
  cloudbuild/*.log
86
95
  /runners/capsid-fold-esmfold2-base/source-artifact.zip
87
96
  /runners/capsid-fold-esmfold2-base/source-artifact.sha256
97
+ /runners/capsid-fold-openfold3-base/source-artifact.zip
98
+ /runners/capsid-fold-openfold3-base/source-artifact.sha256
88
99
 
89
100
  # Docker
90
101
  .dockerignore.local
@@ -1,12 +1,9 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: dyno-phi
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: Phi — Python SDK and CLI for the Phi protein design API
5
- Project-URL: Homepage, https://design.dynotx.com
6
- Project-URL: Documentation, https://design.dynotx.com/docs
7
- Project-URL: Source, https://github.com/dynotx/phi-api/tree/main/clients/phi
8
- Project-URL: Changelog, https://github.com/dynotx/phi-api/blob/main/clients/phi/CHANGELOG.md
9
- Project-URL: Issues, https://github.com/dynotx/phi-api/issues
5
+ Project-URL: Homepage, https://dynophi.com
6
+ Project-URL: Documentation, https://dynophi.com/docs
10
7
  Author: Dyno Therapeutics
11
8
  License-Expression: MIT
12
9
  Keywords: binder-design,bioinformatics,cli,protein-design,protein-engineering,sdk,structure-prediction
@@ -31,11 +28,9 @@ Description-Content-Type: text/markdown
31
28
 
32
29
  # dyno-phi
33
30
 
34
- Python SDK and CLI for the [Phi](https://design.dynotx.com) protein design API.
31
+ Python SDK and CLI for the [Phi](https://dynophi.com) protein design API.
35
32
  The `phi` package exposes synchronous (`PhiClient`) and asynchronous
36
- (`AsyncPhiClient`) clients and the `phi` CLI, installed together. It is
37
- published to PyPI from `clients/phi` in
38
- [`dynotx/phi-api`](https://github.com/dynotx/phi-api).
33
+ (`AsyncPhiClient`) clients and the `phi` CLI, installed together.
39
34
 
40
35
  ## Install
41
36
 
@@ -48,8 +43,7 @@ uv tool install dyno-phi
48
43
  `uv tool` provisions its own Python, which covers the client's **Python 3.12+**
49
44
  requirement on stock macOS, where the system Python is 3.9. Re-run with
50
45
  `--upgrade` to upgrade. `pip install dyno-phi` into a Python 3.12+ of your own
51
- does the same. From a `phi-api` checkout, `just install-cli` runs the first
52
- form and `just install-cli-dev` installs the working tree.
46
+ does the same.
53
47
 
54
48
  The SDK inside an existing project (the CLI comes with it; the `[cli]` extra
55
49
  from 0.3.0 still installs and adds nothing):
@@ -64,8 +58,7 @@ client is first on your `PATH` (`which -a phi` lists every match) and
64
58
  `pip install --upgrade dyno-phi` in the Python that owns it replaces it.
65
59
  Coming from 0.2.x: the key is `PHI_API_KEY` (or `PHI_KEY`) and the base URL
66
60
  `PHI_API`, the commands are `phi <group> <command>` as listed under
67
- [CLI](#cli), and Python 3.12+ is required; see
68
- [`CHANGELOG.md`](https://github.com/dynotx/phi-api/blob/main/clients/phi/CHANGELOG.md).
61
+ [CLI](#cli), and Python 3.12+ is required.
69
62
 
70
63
  If `uv`'s tool directory is behind another `phi` on `PATH`, sourcing uv's shell
71
64
  shim is not enough to move it: the shim prepends the directory only when `PATH`
@@ -93,7 +86,7 @@ device. `phi auth whoami` prints who you are and which organization and
93
86
  environment you are on; `phi doctor` checks the install when something is off.
94
87
 
95
88
  An agent, CI or a service account uses an API key from
96
- [design.dynotx.com](https://design.dynotx.com) instead, either saved as a profile
89
+ [app.dynophi.com](https://app.dynophi.com) instead, either saved as a profile
97
90
  or read from the environment:
98
91
 
99
92
  ```bash
@@ -108,9 +101,9 @@ export PHI_API_KEY=ak_... # or per process
108
101
  | `PHI_ORG` | Organization ID. Required with a static key. |
109
102
 
110
103
  ```bash
111
- export PHI_API=https://phi-api-staging-917387676927.us-central1.run.app
112
- export PHI_KEY=... # static staging key
113
- export PHI_ORG=dynotx # literal static tenant, not the Clerk org
104
+ export PHI_API=https://<your-deployment>
105
+ export PHI_KEY=... # static key
106
+ export PHI_ORG=<tenant> # literal static tenant, not the Clerk org
114
107
  ```
115
108
 
116
109
  With a Clerk key (`ak_...`) the tenant comes from the key, so `PHI_ORG` is
@@ -118,7 +111,7 @@ ignored; it is required only for a static key.
118
111
 
119
112
  Pass `base_url=`, `api_key=`, and `org=` to the client constructor to override
120
113
  the environment. `api_key=` takes a sign-in's access token as readily as a key.
121
- The three ways and when to use each: [`docs/sdk-and-cli.md`](https://github.com/dynotx/phi-api/blob/main/docs/sdk-and-cli.md#authentication).
114
+ The three ways and when to use each: [documentation](https://dynophi.com/docs).
122
115
 
123
116
  ## 60-second quickstart
124
117
 
@@ -223,8 +216,13 @@ authored Markdown layer after the checkout's deny-list guard passes, defaults
223
216
  platform documents to internal access, and skips unchanged content and access.
224
217
  It does not delete remote-only documents.
225
218
 
219
+ ## MCP
220
+
221
+ Phi also serves an MCP endpoint for agents at `https://api.dynophi.com/mcp`. Add it
222
+ to your MCP client as a remote server, or run `phi auth login`, which signs in
223
+ against `https://api.dynophi.com` and the same `/mcp` resource.
224
+
226
225
  ## Full reference
227
226
 
228
- See [`docs/sdk-and-cli.md`](https://github.com/dynotx/phi-api/blob/main/docs/sdk-and-cli.md)
229
- for environment and profile resolution, sync/async parity, the complete CLI
230
- reference, the error taxonomy and how a release is cut.
227
+ See the [documentation](https://dynophi.com/docs) for environment and profile
228
+ resolution, sync/async parity, the complete CLI reference and the error taxonomy.
@@ -1,10 +1,8 @@
1
1
  # dyno-phi
2
2
 
3
- Python SDK and CLI for the [Phi](https://design.dynotx.com) protein design API.
3
+ Python SDK and CLI for the [Phi](https://dynophi.com) protein design API.
4
4
  The `phi` package exposes synchronous (`PhiClient`) and asynchronous
5
- (`AsyncPhiClient`) clients and the `phi` CLI, installed together. It is
6
- published to PyPI from `clients/phi` in
7
- [`dynotx/phi-api`](https://github.com/dynotx/phi-api).
5
+ (`AsyncPhiClient`) clients and the `phi` CLI, installed together.
8
6
 
9
7
  ## Install
10
8
 
@@ -17,8 +15,7 @@ uv tool install dyno-phi
17
15
  `uv tool` provisions its own Python, which covers the client's **Python 3.12+**
18
16
  requirement on stock macOS, where the system Python is 3.9. Re-run with
19
17
  `--upgrade` to upgrade. `pip install dyno-phi` into a Python 3.12+ of your own
20
- does the same. From a `phi-api` checkout, `just install-cli` runs the first
21
- form and `just install-cli-dev` installs the working tree.
18
+ does the same.
22
19
 
23
20
  The SDK inside an existing project (the CLI comes with it; the `[cli]` extra
24
21
  from 0.3.0 still installs and adds nothing):
@@ -33,8 +30,7 @@ client is first on your `PATH` (`which -a phi` lists every match) and
33
30
  `pip install --upgrade dyno-phi` in the Python that owns it replaces it.
34
31
  Coming from 0.2.x: the key is `PHI_API_KEY` (or `PHI_KEY`) and the base URL
35
32
  `PHI_API`, the commands are `phi <group> <command>` as listed under
36
- [CLI](#cli), and Python 3.12+ is required; see
37
- [`CHANGELOG.md`](https://github.com/dynotx/phi-api/blob/main/clients/phi/CHANGELOG.md).
33
+ [CLI](#cli), and Python 3.12+ is required.
38
34
 
39
35
  If `uv`'s tool directory is behind another `phi` on `PATH`, sourcing uv's shell
40
36
  shim is not enough to move it: the shim prepends the directory only when `PATH`
@@ -62,7 +58,7 @@ device. `phi auth whoami` prints who you are and which organization and
62
58
  environment you are on; `phi doctor` checks the install when something is off.
63
59
 
64
60
  An agent, CI or a service account uses an API key from
65
- [design.dynotx.com](https://design.dynotx.com) instead, either saved as a profile
61
+ [app.dynophi.com](https://app.dynophi.com) instead, either saved as a profile
66
62
  or read from the environment:
67
63
 
68
64
  ```bash
@@ -77,9 +73,9 @@ export PHI_API_KEY=ak_... # or per process
77
73
  | `PHI_ORG` | Organization ID. Required with a static key. |
78
74
 
79
75
  ```bash
80
- export PHI_API=https://phi-api-staging-917387676927.us-central1.run.app
81
- export PHI_KEY=... # static staging key
82
- export PHI_ORG=dynotx # literal static tenant, not the Clerk org
76
+ export PHI_API=https://<your-deployment>
77
+ export PHI_KEY=... # static key
78
+ export PHI_ORG=<tenant> # literal static tenant, not the Clerk org
83
79
  ```
84
80
 
85
81
  With a Clerk key (`ak_...`) the tenant comes from the key, so `PHI_ORG` is
@@ -87,7 +83,7 @@ ignored; it is required only for a static key.
87
83
 
88
84
  Pass `base_url=`, `api_key=`, and `org=` to the client constructor to override
89
85
  the environment. `api_key=` takes a sign-in's access token as readily as a key.
90
- The three ways and when to use each: [`docs/sdk-and-cli.md`](https://github.com/dynotx/phi-api/blob/main/docs/sdk-and-cli.md#authentication).
86
+ The three ways and when to use each: [documentation](https://dynophi.com/docs).
91
87
 
92
88
  ## 60-second quickstart
93
89
 
@@ -192,8 +188,13 @@ authored Markdown layer after the checkout's deny-list guard passes, defaults
192
188
  platform documents to internal access, and skips unchanged content and access.
193
189
  It does not delete remote-only documents.
194
190
 
191
+ ## MCP
192
+
193
+ Phi also serves an MCP endpoint for agents at `https://api.dynophi.com/mcp`. Add it
194
+ to your MCP client as a remote server, or run `phi auth login`, which signs in
195
+ against `https://api.dynophi.com` and the same `/mcp` resource.
196
+
195
197
  ## Full reference
196
198
 
197
- See [`docs/sdk-and-cli.md`](https://github.com/dynotx/phi-api/blob/main/docs/sdk-and-cli.md)
198
- for environment and profile resolution, sync/async parity, the complete CLI
199
- reference, the error taxonomy and how a release is cut.
199
+ See the [documentation](https://dynophi.com/docs) for environment and profile
200
+ resolution, sync/async parity, the complete CLI reference and the error taxonomy.
@@ -49,11 +49,8 @@ cli = []
49
49
  phi = "phi.cli:main"
50
50
 
51
51
  [project.urls]
52
- Homepage = "https://design.dynotx.com"
53
- Documentation = "https://design.dynotx.com/docs"
54
- Source = "https://github.com/dynotx/phi-api/tree/main/clients/phi"
55
- Changelog = "https://github.com/dynotx/phi-api/blob/main/clients/phi/CHANGELOG.md"
56
- Issues = "https://github.com/dynotx/phi-api/issues"
52
+ Homepage = "https://dynophi.com"
53
+ Documentation = "https://dynophi.com/docs"
57
54
 
58
55
  [dependency-groups]
59
56
  dev = [
@@ -73,7 +70,7 @@ path = "src/phi/_version.py"
73
70
  packages = ["src/phi"]
74
71
 
75
72
  [tool.hatch.build.targets.sdist]
76
- include = ["src/phi", "tests", "examples", "README.md", "CHANGELOG.md", "pyproject.toml"]
73
+ include = ["src/phi", "examples", "README.md", "pyproject.toml"]
77
74
 
78
75
  [tool.ruff]
79
76
  line-length = 100
@@ -0,0 +1 @@
1
+ __version__ = "0.4.0"
@@ -4,6 +4,7 @@ import os
4
4
  from collections.abc import Callable
5
5
  from typing import Any
6
6
 
7
+ import httpx
7
8
  import typer
8
9
 
9
10
  from phi import config, oauth, profiles
@@ -19,8 +20,7 @@ from ..state import USER_AGENT, CliState
19
20
  app = typer.Typer(no_args_is_help=True, help="Sign in and inspect the active identity.")
20
21
 
21
22
  PRESETS = {
22
- "hosted": "https://phi.dynotx.com",
23
- "staging": "https://phi-api-staging-917387676927.us-central1.run.app",
23
+ "hosted": "https://api.dynophi.com",
24
24
  "local": "http://localhost:8080",
25
25
  }
26
26
 
@@ -109,11 +109,23 @@ def login(
109
109
  say = _say(state)
110
110
  say("Phi")
111
111
  say(f"Signing in to {base_url}")
112
- with oauth.http_client() as http:
113
- if device:
114
- tokens, server = oauth.sign_in_with_device_code(base_url, http=http, say=say)
115
- else:
116
- tokens, server = oauth.sign_in_with_browser(base_url, http=http, say=say)
112
+ try:
113
+ with oauth.http_client() as http:
114
+ if device:
115
+ tokens, server = oauth.sign_in_with_device_code(base_url, http=http, say=say)
116
+ else:
117
+ tokens, server = oauth.sign_in_with_browser(base_url, http=http, say=say)
118
+ except oauth.OAuthError as exc:
119
+ if (
120
+ api is None
121
+ and config.classify_environment(base_url) == "unknown"
122
+ and isinstance(exc.__cause__, httpx.RequestError)
123
+ ):
124
+ raise oauth.OAuthError(
125
+ f"{exc}; if profile '{name}' was saved by an earlier release, "
126
+ f"run phi auth login --api {PRESETS['hosted']}"
127
+ ) from exc
128
+ raise
117
129
  cred = OAuthCredential.from_tokens(
118
130
  name, tokens, server, client_id=oauth.client_id_for(base_url)
119
131
  )
@@ -58,7 +58,12 @@ def check_base_url(base_url: str) -> Check:
58
58
  f"{base_url} answered {exc.response.status_code} on /readyz",
59
59
  )
60
60
  except (httpx.HTTPError, ValueError) as exc:
61
- hint = f" ({source} names it)" if environment == "unknown" else ""
61
+ hint = ""
62
+ if environment == "unknown":
63
+ hint = (
64
+ f" ({source} names it); if this profile was saved by an earlier release, "
65
+ f"run phi auth login --api {config.DEFAULT_BASE_URL}"
66
+ )
62
67
  return Check("api", False, f"{base_url} is not reachable: {exc}{hint}")
63
68
  if environment == "unknown":
64
69
  return Check(
@@ -52,6 +52,7 @@ def show(
52
52
  if state.json_output:
53
53
  render.print_json(schema.model_dump())
54
54
  return
55
+ render.kv_panel(overview(schema), title=schema.job_type, no_color=state.no_color)
55
56
  _render_schema(schema.params_schema, "Parameters", state.no_color)
56
57
  _render_schema(schema.inputs_schema, "Inputs", state.no_color)
57
58
 
@@ -80,7 +81,46 @@ def format_typical(seconds: int | None) -> str:
80
81
  return f"about {max(1, round(seconds / 60))} min"
81
82
 
82
83
 
84
+ def overview(schema: JobTypeSchema) -> dict[str, str]:
85
+ """What to know before submitting: purpose, price, availability, limits, usual wall time."""
86
+ rows: dict[str, str] = {}
87
+ if schema.purpose:
88
+ rows["purpose"] = schema.purpose
89
+ price = f"{schema.credit_cost} credits at the default params"
90
+ if schema.catalog_price is not None:
91
+ basket = schema.catalog_price
92
+ price += f"; {basket.credits} for {basket.count} {basket.unit}"
93
+ rows["price"] = price + " (POST /v1/jobs/estimate quotes a request)"
94
+ if schema.available:
95
+ rows["available"] = "yes"
96
+ else:
97
+ rows["available"] = f"no, {schema.gate_reason or 'withheld on this deployment'}"
98
+ rows["environments"] = ", ".join(schema.environments)
99
+ limits = _limits(schema)
100
+ if limits:
101
+ rows["limits"] = limits
102
+ if schema.typical_seconds is not None:
103
+ rows["typical time"] = format_typical(schema.typical_seconds)
104
+ if schema.notes:
105
+ rows["notes"] = schema.notes
106
+ return rows
107
+
108
+
109
+ def _limits(schema: JobTypeSchema) -> str:
110
+ constraints = schema.input_constraints
111
+ parts: list[str] = []
112
+ if constraints.max_chains is not None:
113
+ parts.append(f"at most {constraints.max_chains} chain(s)")
114
+ if constraints.max_length is not None:
115
+ parts.append(f"at most {constraints.max_length} residues per sequence")
116
+ if constraints.accepts_ligands is not None:
117
+ parts.append("ligands accepted" if constraints.accepts_ligands else "no ligands")
118
+ return "; ".join(parts)
119
+
120
+
83
121
  def _summary(schema: JobTypeSchema) -> str:
122
+ if schema.purpose:
123
+ return schema.purpose
84
124
  description = schema.params_schema.get("description")
85
125
  if isinstance(description, str) and description:
86
126
  return description.splitlines()[0]
@@ -1,10 +1,10 @@
1
1
  """`phi skills`: the skills Phi publishes, listed, shown and installed where agents read them.
2
2
 
3
- The source is dynotx/phi-skills; the site serves its index at /skills.json and each
4
- skill as Markdown. `install` prefers the `skills` installer (`npx skills add`), which
5
- puts a skill where every agent on the machine looks and keeps a lock file; without
6
- `npx` it writes the Markdown itself into the agents' skill directories, so a machine
7
- with only `pip install dyno-phi` and HTTPS still gets them.
3
+ The site serves the index at /skills.json and each skill as Markdown. `install` prefers
4
+ the `skills` installer (`npx skills add`), which puts a skill where every agent on the
5
+ machine looks and keeps a lock file; without `npx` it writes the Markdown itself into
6
+ the agents' skill directories, so a machine with only `pip install dyno-phi` and HTTPS
7
+ still gets them.
8
8
  """
9
9
 
10
10
  from __future__ import annotations
@@ -26,7 +26,7 @@ from ..state import CliState
26
26
 
27
27
  app = typer.Typer(no_args_is_help=True, help="Phi's agent skills: list, show, install.")
28
28
 
29
- DEFAULT_INDEX_URL = "https://phi.dynotx.com/skills.json"
29
+ DEFAULT_INDEX_URL = "https://dynophi.com/skills.json"
30
30
  INDEX_ENV = "PHI_SKILLS_INDEX"
31
31
  FETCH_TIMEOUT = 20.0
32
32
 
@@ -121,7 +121,7 @@ def npx_path() -> str | None:
121
121
 
122
122
 
123
123
  def run_installer(index: Index, names: list[str], *, global_: bool, agents: list[str]) -> int:
124
- """`npx skills add dynotx/phi-skills [--skill name]... [-g] [-a agent]... -y`."""
124
+ """`npx skills add <repo> [--skill name]... [-g] [-a agent]... -y`, <repo> from the index."""
125
125
  command = ["npx", "-y", "skills", "add", index.repo]
126
126
  for name in names:
127
127
  command += ["--skill", name]
@@ -171,27 +171,50 @@ def installed_skills(*, cwd: Path | None = None) -> dict[str, list[str]]:
171
171
  return found
172
172
 
173
173
 
174
+ def installed_names() -> set[str]:
175
+ """Every skill name sitting in an agent's project or global directory."""
176
+ return {name for names in installed_skills().values() for name in names}
177
+
178
+
174
179
  @app.command("list")
175
180
  def list_(ctx: typer.Context) -> None:
176
- """The skills Phi publishes, with their versions."""
181
+ """The skills Phi publishes, their versions, and which are installed for your agents."""
177
182
  state: CliState = ctx.obj
178
183
  index = read_index()
184
+ installed = installed_names()
179
185
  if state.json_output:
180
186
  render.print_json(
181
187
  {
182
188
  "repo": index.repo,
183
189
  "install": index.install,
184
- "skills": [skill.__dict__ for skill in index.skills],
190
+ "skills": [
191
+ {**skill.__dict__, "installed": skill.name in installed}
192
+ for skill in index.skills
193
+ ],
185
194
  }
186
195
  )
187
196
  return
188
197
  render.table(
189
- ["name", "version", "description"],
190
- [[skill.name, skill.version, skill.description.split(". ")[0]] for skill in index.skills],
198
+ ["name", "version", "installed", "description"],
199
+ [
200
+ [
201
+ skill.name,
202
+ skill.version,
203
+ "yes" if skill.name in installed else "no",
204
+ skill.description.split(". ")[0],
205
+ ]
206
+ for skill in index.skills
207
+ ],
191
208
  no_color=state.no_color,
192
209
  )
193
210
  printer = render.console(no_color=state.no_color)
194
- printer.print(f"\nInstall every one: {index.install}", markup=False, highlight=False)
211
+ printer.print(
212
+ "\n`installed` means the skill is in this project's or your home Claude Code, Cursor "
213
+ "or Codex skills directory.",
214
+ markup=False,
215
+ highlight=False,
216
+ )
217
+ printer.print(f"Install every one: {index.install}", markup=False, highlight=False)
195
218
  printer.print("Or: phi skills install [name]...", markup=False, highlight=False)
196
219
 
197
220
 
@@ -57,7 +57,7 @@ def prepare_sync(
57
57
  if guard.is_file():
58
58
  _run_guard(root, guard)
59
59
  else:
60
- # A checkout with no deny-list guard is a public one (dynotx/phi-skills): every document
60
+ # A checkout with no deny-list guard is a public one: every document
61
61
  # it publishes says so itself with `access: general` in its frontmatter, or nothing goes.
62
62
  undeclared = [
63
63
  document.name
@@ -4,6 +4,7 @@ import os
4
4
  from dataclasses import dataclass, replace
5
5
  from datetime import UTC, datetime
6
6
  from pathlib import Path
7
+ from urllib.parse import urlparse
7
8
 
8
9
  import httpx
9
10
 
@@ -11,7 +12,7 @@ from . import oauth, profiles
11
12
  from .errors import PhiConfigError
12
13
  from .oauth import PhiSignedOutError, TokenSet
13
14
 
14
- DEFAULT_BASE_URL = "https://phi.dynotx.com"
15
+ DEFAULT_BASE_URL = "https://api.dynophi.com"
15
16
  CONFIG_PATH = Path.home() / ".config" / "phi" / "config.toml"
16
17
 
17
18
  SIGNED_OUT = "Signed out. Run phi auth login."
@@ -31,11 +32,12 @@ ENVIRONMENT_NAMES = {"hosted": "production", "staging": "staging", "local": "dev
31
32
 
32
33
 
33
34
  def classify_environment(base_url: str) -> str:
34
- if "phi.dynotx.com" in base_url:
35
+ host = urlparse(base_url).hostname or ""
36
+ if host == urlparse(DEFAULT_BASE_URL).hostname:
35
37
  return "hosted"
36
- if "phi-api-staging" in base_url:
38
+ if "staging" in host:
37
39
  return "staging"
38
- if "localhost" in base_url or "127.0.0.1" in base_url:
40
+ if host in ("localhost", "127.0.0.1"):
39
41
  return "local"
40
42
  return "unknown"
41
43
 
@@ -83,7 +83,7 @@ _TOOL_REGISTRY: dict[str, _ToolSpec] = {
83
83
  "genie3": _ToolSpec("GENIE3", _NO_PORTS, _BACKBONES_OUT),
84
84
  "boltz": _ToolSpec("BOLTZ", _SEQUENCES_IN, _FOLD_OUT),
85
85
  "boltz2": _ToolSpec("BOLTZ2", _NO_PORTS, _FOLD_OUT),
86
- "dynopsi1": _ToolSpec("DYNOPSI1", _NO_PORTS, _BACKBONES_OUT),
86
+ "dynopsi": _ToolSpec("DYNOPSI", _NO_PORTS, _BACKBONES_OUT),
87
87
  "dynopsi_dev": _ToolSpec("DYNOPSI_DEV", _NO_PORTS, _BACKBONES_OUT),
88
88
  "boltzgen": _ToolSpec("BOLTZGEN", _NO_PORTS, _NO_PORTS, typed_ports=False),
89
89
  # Terminal co-design like the monolithic boltzgen stage: no typed ports yet.
@@ -16,21 +16,25 @@ JobType = Literal[
16
16
  "BOLTZGEN_DESIGN_ONLY",
17
17
  "BOLTZGEN_FULL",
18
18
  "CAPSID_FOLD_ESMFOLD2",
19
+ "CAPSID_FOLD_OPENFOLD3",
19
20
  "CHAI1",
20
21
  "DOCKQ",
21
- "DYNOPSI1",
22
+ "DYNOPSI",
22
23
  "DYNOPSI_DEV",
23
24
  "ESMC",
24
25
  "ESMFOLD",
25
26
  "ESMFOLD2",
26
27
  "ESMFOLD2_FAST",
27
28
  "ESM_IF1",
29
+ "EVO2",
30
+ "EVO2_GENERATE",
28
31
  "FOLD_AND_SCORE_NOVELTY",
29
32
  "FREE_BINDCRAFT",
30
33
  "GENIE3",
31
34
  "GERMINAL",
32
35
  "LACUNA",
33
36
  "MASIF_SITE",
37
+ "MOL1",
34
38
  "MSA_PAIRFORMER",
35
39
  "OPENDDE",
36
40
  "OPENFOLD3",
@@ -5,7 +5,7 @@ from typing import Any
5
5
  from pydantic import Field
6
6
 
7
7
  from .common import PhiModel
8
- from .runs import StagePricing
8
+ from .runs import CatalogPrice, StagePricing
9
9
 
10
10
 
11
11
  class InputConstraints(PhiModel):
@@ -17,6 +17,8 @@ class InputConstraints(PhiModel):
17
17
  class JobTypeSchema(PhiModel):
18
18
  job_type: str
19
19
  credit_cost: int
20
+ catalog_price: CatalogPrice | None = None
21
+ rate_revision: str | None = None
20
22
  pricing: list[StagePricing] = Field(default_factory=list)
21
23
  params_schema: dict[str, Any]
22
24
  inputs_schema: dict[str, Any]
@@ -25,3 +27,5 @@ class JobTypeSchema(PhiModel):
25
27
  environments: list[str]
26
28
  input_constraints: InputConstraints
27
29
  typical_seconds: int | None = None
30
+ purpose: str | None = None
31
+ notes: str | None = None
@@ -52,11 +52,19 @@ class QuoteLine(PhiModel):
52
52
  class StagePricing(PhiModel):
53
53
  stage: str
54
54
  unit: str
55
- minutes: int
55
+ cents: int
56
56
  cap: int | None = None
57
57
  gpu: str
58
- startup_minutes: int = 0
59
58
  per: int = 1
59
+ included: int = 0
60
+ start_cents: int = 0
61
+ basket: int = 1
62
+
63
+
64
+ class CatalogPrice(PhiModel):
65
+ credits: int
66
+ count: int
67
+ unit: str
60
68
 
61
69
 
62
70
  class RunEstimate(BaseModel):
@@ -150,6 +158,8 @@ class RunTimelineResponse(PhiModel):
150
158
  class PipelineTypeResponse(PhiModel):
151
159
  pipeline_type: str
152
160
  credit_cost: int
161
+ catalog_price: CatalogPrice | None = None
162
+ rate_revision: str | None = None
153
163
  pricing: list[StagePricing] = Field(default_factory=list)
154
164
  available_as_job: bool
155
165
  default_params: dict[str, Any]
@@ -490,7 +490,7 @@ def device_authorization(
490
490
  if not server.device_authorization_endpoint:
491
491
  raise OAuthError(
492
492
  f"{server.issuer} offers no device authorization; on a machine without a "
493
- "browser, run phi auth login --key with an API key from design.dynotx.com."
493
+ "browser, run phi auth login --key with an API key from dynophi.com."
494
494
  )
495
495
  try:
496
496
  response = http.post(