morphcloud 0.1.111__tar.gz → 0.1.112__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 (163) hide show
  1. {morphcloud-0.1.111 → morphcloud-0.1.112}/.gitignore +8 -0
  2. {morphcloud-0.1.111 → morphcloud-0.1.112}/PKG-INFO +63 -1
  3. {morphcloud-0.1.111 → morphcloud-0.1.112}/README.md +62 -0
  4. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/api.py +19 -1
  5. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/cli.py +14 -4
  6. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/config.py +12 -0
  7. morphcloud-0.1.112/morphcloud/plugins/__init__.py +1 -0
  8. morphcloud-0.1.112/morphcloud/plugins/cli.py +1164 -0
  9. morphcloud-0.1.112/morphcloud/plugins/installer.py +569 -0
  10. morphcloud-0.1.112/morphcloud/simple_index/__init__.py +6 -0
  11. morphcloud-0.1.112/morphcloud/simple_index/client.py +415 -0
  12. {morphcloud-0.1.111 → morphcloud-0.1.112}/pyproject.toml +1 -1
  13. morphcloud-0.1.112/tests/unit/test_cli_plugins.py +1764 -0
  14. morphcloud-0.1.112/tests/unit/test_plugin_installer.py +431 -0
  15. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_profiles.py +8 -0
  16. morphcloud-0.1.112/tests/unit/test_simple_index_client.py +392 -0
  17. {morphcloud-0.1.111 → morphcloud-0.1.112}/.github/workflows/publish.yaml +0 -0
  18. {morphcloud-0.1.111 → morphcloud-0.1.112}/LICENSE +0 -0
  19. {morphcloud-0.1.111 → morphcloud-0.1.112}/Makefile +0 -0
  20. {morphcloud-0.1.111 → morphcloud-0.1.112}/conf.py +0 -0
  21. {morphcloud-0.1.111 → morphcloud-0.1.112}/examples/browser_example.py +0 -0
  22. {morphcloud-0.1.111 → morphcloud-0.1.112}/examples/devbox_example.py +0 -0
  23. {morphcloud-0.1.111 → morphcloud-0.1.112}/index.rst +0 -0
  24. {morphcloud-0.1.111 → morphcloud-0.1.112}/make.bat +0 -0
  25. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/__init__.py +0 -0
  26. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_asyncify.py +0 -0
  27. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_bash_interpreter.py +0 -0
  28. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_http_transport.py +0 -0
  29. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_llm.py +0 -0
  30. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_scramble.py +0 -0
  31. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_ssh.py +0 -0
  32. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_utils.py +0 -0
  33. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/cli_helpers.py +0 -0
  34. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/__init__.py +0 -0
  35. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/cli.py +0 -0
  36. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/client.py +0 -0
  37. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/config.py +0 -0
  38. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/__init__.py +0 -0
  39. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/admin/__init__.py +0 -0
  40. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/admin/client.py +0 -0
  41. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/admin/raw_client.py +0 -0
  42. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/aliases/__init__.py +0 -0
  43. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/aliases/client.py +0 -0
  44. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/aliases/raw_client.py +0 -0
  45. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/client.py +0 -0
  46. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/__init__.py +0 -0
  47. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/api_error.py +0 -0
  48. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/client_wrapper.py +0 -0
  49. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/datetime_utils.py +0 -0
  50. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/file.py +0 -0
  51. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/force_multipart.py +0 -0
  52. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/http_client.py +0 -0
  53. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/http_response.py +0 -0
  54. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/jsonable_encoder.py +0 -0
  55. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/pydantic_utilities.py +0 -0
  56. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/query_encoder.py +0 -0
  57. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/remove_none_from_dict.py +0 -0
  58. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/request_options.py +0 -0
  59. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/serialization.py +0 -0
  60. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_actions/__init__.py +0 -0
  61. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_actions/client.py +0 -0
  62. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_actions/raw_client.py +0 -0
  63. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_core/__init__.py +0 -0
  64. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_core/client.py +0 -0
  65. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_core/raw_client.py +0 -0
  66. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_lifecycle/__init__.py +0 -0
  67. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_lifecycle/client.py +0 -0
  68. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_lifecycle/raw_client.py +0 -0
  69. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/errors/__init__.py +0 -0
  70. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/errors/unprocessable_entity_error.py +0 -0
  71. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/templates/__init__.py +0 -0
  72. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/templates/client.py +0 -0
  73. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/templates/raw_client.py +0 -0
  74. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/tmux/__init__.py +0 -0
  75. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/tmux/client.py +0 -0
  76. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/tmux/raw_client.py +0 -0
  77. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/__init__.py +0 -0
  78. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/alias_response.py +0 -0
  79. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/branch_response.py +0 -0
  80. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/delete_response.py +0 -0
  81. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_detail_list_response.py +0 -0
  82. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_list_response.py +0 -0
  83. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_refs.py +0 -0
  84. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_response.py +0 -0
  85. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_spec.py +0 -0
  86. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_ssh_credentials_response.py +0 -0
  87. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_summary_response.py +0 -0
  88. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/http_service.py +0 -0
  89. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/http_service_auth_mode.py +0 -0
  90. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/http_validation_error.py +0 -0
  91. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/networking.py +0 -0
  92. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/secret_value_response.py +0 -0
  93. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/snapshot_response.py +0 -0
  94. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/success_response.py +0 -0
  95. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_cache_request.py +0 -0
  96. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_cache_start_response.py +0 -0
  97. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_detail.py +0 -0
  98. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_list_response.py +0 -0
  99. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_share_response.py +0 -0
  100. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_status.py +0 -0
  101. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_step_model.py +0 -0
  102. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_step_status.py +0 -0
  103. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_summary.py +0 -0
  104. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/timeline_event.py +0 -0
  105. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_debug_info_response.py +0 -0
  106. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_install_response.py +0 -0
  107. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_new_session_response.py +0 -0
  108. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_send_keys_response.py +0 -0
  109. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_session.py +0 -0
  110. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_sessions_list.py +0 -0
  111. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_snapshot_response.py +0 -0
  112. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_target.py +0 -0
  113. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/ttl.py +0 -0
  114. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/upload_response.py +0 -0
  115. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/validation_error.py +0 -0
  116. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/validation_error_loc_item.py +0 -0
  117. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/wake_on.py +0 -0
  118. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/uploads/__init__.py +0 -0
  119. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/uploads/client.py +0 -0
  120. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/uploads/raw_client.py +0 -0
  121. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/user_secrets/__init__.py +0 -0
  122. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/user_secrets/client.py +0 -0
  123. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/user_secrets/raw_client.py +0 -0
  124. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/openapi.json +0 -0
  125. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/template_local_runner.py +0 -0
  126. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/template_runner.py +0 -0
  127. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/terminals.py +0 -0
  128. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/experimental/__init__.py +0 -0
  129. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/experimental/browser.py +0 -0
  130. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/py.typed +0 -0
  131. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/volumes/__init__.py +0 -0
  132. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/volumes/cli.py +0 -0
  133. {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/volumes/client.py +0 -0
  134. {morphcloud-0.1.111 → morphcloud-0.1.112}/requirements.txt +0 -0
  135. {morphcloud-0.1.111 → morphcloud-0.1.112}/scripts/abuild_smoke.py +0 -0
  136. {morphcloud-0.1.111 → morphcloud-0.1.112}/scripts/increment_version.py +0 -0
  137. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/__init__.py +0 -0
  138. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/conftest.py +0 -0
  139. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/pytest.ini +0 -0
  140. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_as_container.py +0 -0
  141. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_branching.py +0 -0
  142. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_command_execution.py +0 -0
  143. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_file_operations.py +0 -0
  144. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_from_tag_multiple.py +0 -0
  145. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_http_service.py +0 -0
  146. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_metadata.py +0 -0
  147. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_simple.py +0 -0
  148. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_snapshot_operations.py +0 -0
  149. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_ssh_key_rotation.py +0 -0
  150. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_ttl.py +0 -0
  151. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_user.py +0 -0
  152. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_cli_devbox.py +0 -0
  153. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_cli_devbox_terminal.py +0 -0
  154. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_cli_secrets.py +0 -0
  155. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_cli_snapshots.py +0 -0
  156. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_cli_volumes.py +0 -0
  157. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_devbox_terminals.py +0 -0
  158. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_http_transport_sharding.py +0 -0
  159. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_snapshot_ttl.py +0 -0
  160. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_ssh_connect_retry.py +0 -0
  161. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_template_local_runner.py +0 -0
  162. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_template_runner.py +0 -0
  163. {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_volumes_client.py +0 -0
@@ -19,6 +19,7 @@ wheels/
19
19
  *.egg-info/
20
20
  .installed.cfg
21
21
  *.egg
22
+ .ruff_cache/
22
23
 
23
24
  # Poetry
24
25
  poetry.lock
@@ -60,3 +61,10 @@ _build/
60
61
 
61
62
  # uv
62
63
  uv.lock
64
+
65
+ #clanker
66
+ AGENTS.md
67
+ CLAUDE.md
68
+ PLAN.md
69
+ .codex/
70
+ .agents/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: morphcloud
3
- Version: 0.1.111
3
+ Version: 0.1.112
4
4
  Summary: A Python SDK and CLI tool for creating, managing, and interacting with Morph Cloud VMs.
5
5
  Author-email: Morph Labs <jobs@morph.so>
6
6
  License: Apache-2.0
@@ -216,6 +216,68 @@ morphcloud instance copy inst_123:/remote/file.log ./local_dir/
216
216
  morphcloud instance copy -r ./local_dir inst_123:/remote/dir
217
217
  ```
218
218
 
219
+ ### Plugins
220
+
221
+ Plugins are Python packages that register extra `morphcloud` commands through
222
+ the `morphcloud.cli_plugins` entry-point group. Catalog and package operations
223
+ use your normal `MORPH_API_KEY`. A catalog entry can install from the private
224
+ simple-index package name or from an explicit `artifact_url`.
225
+
226
+ ```bash
227
+ # Inspect plugin configuration and installed commands
228
+ morphcloud plugin doctor
229
+
230
+ # List or inspect plugins visible to your organization
231
+ morphcloud plugin list
232
+ morphcloud plugin show intro
233
+
234
+ # Validate install resolution without changing the environment
235
+ morphcloud plugin install intro --dry-run
236
+
237
+ # Install or update a plugin into the current Python environment
238
+ morphcloud plugin install intro
239
+ morphcloud plugin upgrade intro
240
+
241
+ # Run the command exposed by the installed plugin
242
+ morphcloud intro --help
243
+ ```
244
+
245
+ Common commands:
246
+
247
+ | Command | Purpose |
248
+ | --- | --- |
249
+ | `morphcloud plugin list [query]` | List visible enabled plugins. |
250
+ | `morphcloud plugin search <query>` | Search visible plugins by name, package, artifact, or command. |
251
+ | `morphcloud plugin show <name>` / `info` | Show one catalog entry. |
252
+ | `morphcloud plugin install <name>` | Install into the active Python environment. |
253
+ | `morphcloud plugin upgrade <name>` / `update` | Upgrade an installed plugin. |
254
+ | `morphcloud plugin uninstall <name>` | Remove the plugin package. |
255
+ | `morphcloud plugin register <name>` | Upsert a catalog entry from flags or `--manifest`. |
256
+ | `morphcloud plugin publish <name> --wheel dist/pkg-version-py3-none-any.whl` | Upload a wheel to simple-index, then register the catalog entry. |
257
+ | `morphcloud plugin enable/disable/delete <name>` | Manage catalog lifecycle. |
258
+ | `morphcloud plugin doctor` | Show auth, index, catalog, and installed entry-point status. |
259
+
260
+ Management commands accept `--visibility global|org` and `--organization-id`.
261
+ Org-scoped entries require `--organization-id`; global entries must omit it.
262
+
263
+ For local or preview simple-index services, set:
264
+
265
+ ```bash
266
+ export MORPH_SIMPLE_INDEX_BASE_URL="http://127.0.0.1:8912"
267
+ ```
268
+
269
+ The installer uses the authenticated simple-index URL as the primary package
270
+ index for package-backed plugins and keeps PyPI as an extra index for public
271
+ dependencies. Credentials are redacted from CLI output, including direct
272
+ artifact URLs. Direct `artifact_url` installs are allowed only for URLs without
273
+ embedded credentials, query strings, or fragments; use package-backed simple-index
274
+ publishing for private or signed artifacts so secrets are not passed to pip as
275
+ process arguments.
276
+
277
+ For stricter private-package installs, set `MORPH_PLUGIN_DISABLE_EXTRA_INDEX=1`
278
+ to omit the public extra index. To use a controlled mirror instead of PyPI, set
279
+ `MORPH_PLUGIN_EXTRA_INDEX_URL`.
280
+
219
281
  ### Volumes
220
282
 
221
283
  Morph Volumes exposes an S3-compatible object store. Bucket lifecycle and object operations use your normal `MORPH_API_KEY`.
@@ -185,6 +185,68 @@ morphcloud instance copy inst_123:/remote/file.log ./local_dir/
185
185
  morphcloud instance copy -r ./local_dir inst_123:/remote/dir
186
186
  ```
187
187
 
188
+ ### Plugins
189
+
190
+ Plugins are Python packages that register extra `morphcloud` commands through
191
+ the `morphcloud.cli_plugins` entry-point group. Catalog and package operations
192
+ use your normal `MORPH_API_KEY`. A catalog entry can install from the private
193
+ simple-index package name or from an explicit `artifact_url`.
194
+
195
+ ```bash
196
+ # Inspect plugin configuration and installed commands
197
+ morphcloud plugin doctor
198
+
199
+ # List or inspect plugins visible to your organization
200
+ morphcloud plugin list
201
+ morphcloud plugin show intro
202
+
203
+ # Validate install resolution without changing the environment
204
+ morphcloud plugin install intro --dry-run
205
+
206
+ # Install or update a plugin into the current Python environment
207
+ morphcloud plugin install intro
208
+ morphcloud plugin upgrade intro
209
+
210
+ # Run the command exposed by the installed plugin
211
+ morphcloud intro --help
212
+ ```
213
+
214
+ Common commands:
215
+
216
+ | Command | Purpose |
217
+ | --- | --- |
218
+ | `morphcloud plugin list [query]` | List visible enabled plugins. |
219
+ | `morphcloud plugin search <query>` | Search visible plugins by name, package, artifact, or command. |
220
+ | `morphcloud plugin show <name>` / `info` | Show one catalog entry. |
221
+ | `morphcloud plugin install <name>` | Install into the active Python environment. |
222
+ | `morphcloud plugin upgrade <name>` / `update` | Upgrade an installed plugin. |
223
+ | `morphcloud plugin uninstall <name>` | Remove the plugin package. |
224
+ | `morphcloud plugin register <name>` | Upsert a catalog entry from flags or `--manifest`. |
225
+ | `morphcloud plugin publish <name> --wheel dist/pkg-version-py3-none-any.whl` | Upload a wheel to simple-index, then register the catalog entry. |
226
+ | `morphcloud plugin enable/disable/delete <name>` | Manage catalog lifecycle. |
227
+ | `morphcloud plugin doctor` | Show auth, index, catalog, and installed entry-point status. |
228
+
229
+ Management commands accept `--visibility global|org` and `--organization-id`.
230
+ Org-scoped entries require `--organization-id`; global entries must omit it.
231
+
232
+ For local or preview simple-index services, set:
233
+
234
+ ```bash
235
+ export MORPH_SIMPLE_INDEX_BASE_URL="http://127.0.0.1:8912"
236
+ ```
237
+
238
+ The installer uses the authenticated simple-index URL as the primary package
239
+ index for package-backed plugins and keeps PyPI as an extra index for public
240
+ dependencies. Credentials are redacted from CLI output, including direct
241
+ artifact URLs. Direct `artifact_url` installs are allowed only for URLs without
242
+ embedded credentials, query strings, or fragments; use package-backed simple-index
243
+ publishing for private or signed artifacts so secrets are not passed to pip as
244
+ process arguments.
245
+
246
+ For stricter private-package installs, set `MORPH_PLUGIN_DISABLE_EXTRA_INDEX=1`
247
+ to omit the public extra index. To use a controlled mirror instead of PyPI, set
248
+ `MORPH_PLUGIN_EXTRA_INDEX_URL`.
249
+
188
250
  ### Volumes
189
251
 
190
252
  Morph Volumes exposes an S3-compatible object store. Bucket lifecycle and object operations use your normal `MORPH_API_KEY`.
@@ -24,6 +24,7 @@ from morphcloud.config import resolve_settings
24
24
 
25
25
  if typing.TYPE_CHECKING:
26
26
  from morphcloud.devbox.client import AsyncDevboxClient, DevboxClient
27
+ from morphcloud.simple_index.client import SimpleIndexClient
27
28
  from morphcloud.volumes.client import VolumesClient
28
29
 
29
30
  # Global console instance
@@ -167,6 +168,7 @@ class MorphCloudClient:
167
168
  volumes_service_api_key: typing.Optional[str] = None,
168
169
  admin_base_url: typing.Optional[str] = None,
169
170
  db_base_url: typing.Optional[str] = None,
171
+ simple_index_base_url: typing.Optional[str] = None,
170
172
  ssh_hostname: typing.Optional[str] = None,
171
173
  ssh_port: typing.Optional[int] = None,
172
174
  devbox_base_url: typing.Optional[str] = None,
@@ -182,6 +184,7 @@ class MorphCloudClient:
182
184
  "devbox_base_url": devbox_base_url,
183
185
  "admin_base_url": admin_base_url,
184
186
  "db_base_url": db_base_url,
187
+ "simple_index_base_url": simple_index_base_url,
185
188
  "ssh_hostname": ssh_hostname,
186
189
  "ssh_port": ssh_port,
187
190
  },
@@ -200,10 +203,12 @@ class MorphCloudClient:
200
203
  self.devbox_base_url = settings.devbox_base_url
201
204
  self.admin_base_url = settings.admin_base_url
202
205
  self.db_base_url = settings.db_base_url
206
+ self.simple_index_base_url = settings.simple_index_base_url
203
207
  self.profile = settings.profile
204
208
  self._devbox_client = None
205
209
  self._devbox_async_client = None
206
210
  self._volumes_client = None
211
+ self._simple_index_client = None
207
212
  if not self.api_key:
208
213
  raise ValueError(
209
214
  "API key must be provided or set in MORPH_API_KEY environment variable"
@@ -254,9 +259,10 @@ class MorphCloudClient:
254
259
  )
255
260
 
256
261
  self._load_sdk_plugins()
257
- # Ensure first-party devbox clients win even if an external plugin attempted to attach them.
262
+ # Ensure first-party clients win even if an external plugin attempted to attach them.
258
263
  self._devbox_client = None
259
264
  self._devbox_async_client = None
265
+ self._simple_index_client = None
260
266
 
261
267
  def _load_sdk_plugins(self):
262
268
  """Load SDK plugins from entry points."""
@@ -339,6 +345,18 @@ class MorphCloudClient:
339
345
  def volumes(self, value: "VolumesClient") -> None:
340
346
  self._volumes_client = value
341
347
 
348
+ @property
349
+ def simple_index(self) -> "SimpleIndexClient":
350
+ if self._simple_index_client is None:
351
+ from morphcloud.simple_index.client import SimpleIndexClient
352
+
353
+ self._simple_index_client = SimpleIndexClient(self)
354
+ return self._simple_index_client
355
+
356
+ @simple_index.setter
357
+ def simple_index(self, value: "SimpleIndexClient") -> None:
358
+ self._simple_index_client = value
359
+
342
360
 
343
361
  class BaseAPI:
344
362
  def __init__(self, client: MorphCloudClient):
@@ -153,9 +153,8 @@ class VersionCheckGroup(AliasedGroup):
153
153
  except click.exceptions.ClickException:
154
154
  # Let Click handle its own exceptions, but check version after
155
155
  # We need to raise first so Click can display the error
156
- exctype, value, tb = sys.exc_info()
157
156
  raise
158
- except Exception as e:
157
+ except Exception:
159
158
  # For non-Click exceptions, check version before letting the exception propagate
160
159
  check_for_package_update(display_mode="error")
161
160
  raise
@@ -298,6 +297,11 @@ def profile_show(name):
298
297
  )
299
298
  @click.option("--admin-base-url", default=None, help="Override the admin API base URL.")
300
299
  @click.option("--db-base-url", default=None, help="Override the db API base URL.")
300
+ @click.option(
301
+ "--simple-index-base-url",
302
+ default=None,
303
+ help="Override the simple-index base URL.",
304
+ )
301
305
  def profile_set(
302
306
  name,
303
307
  api_key,
@@ -309,6 +313,7 @@ def profile_set(
309
313
  volumes_base_url,
310
314
  admin_base_url,
311
315
  db_base_url,
316
+ simple_index_base_url,
312
317
  ):
313
318
  updates = {
314
319
  "api_key": api_key,
@@ -320,6 +325,7 @@ def profile_set(
320
325
  "volumes_base_url": volumes_base_url,
321
326
  "admin_base_url": admin_base_url,
322
327
  "db_base_url": db_base_url,
328
+ "simple_index_base_url": simple_index_base_url,
323
329
  }
324
330
  updates = {k: v for k, v in updates.items() if v is not None}
325
331
  if not updates:
@@ -510,7 +516,7 @@ def _interactive_pagination(
510
516
  content_height = max(5, height - 12)
511
517
 
512
518
  if not current_items:
513
- spinner = _start_spinner("Fetching data")
519
+ _start_spinner("Fetching data")
514
520
  try:
515
521
  result = get_data_func(page=page, limit=limit, **kwargs)
516
522
  current_items = getattr(result, items_attr, [])
@@ -617,7 +623,7 @@ def _interactive_pagination(
617
623
  # Temporarily exit alt screen for input
618
624
  click.echo("\033[?1049l", nl=False)
619
625
  try:
620
- changed = on_search()
626
+ on_search()
621
627
  finally:
622
628
  # Re-enter alt screen
623
629
  click.echo("\033[?1049h", nl=False)
@@ -2709,6 +2715,10 @@ def _register_builtin_cli_extensions() -> None:
2709
2715
 
2710
2716
  register_volumes_cli_plugin(cli)
2711
2717
 
2718
+ from morphcloud.plugins.cli import register_cli_plugin as register_plugin_cli_plugin
2719
+
2720
+ register_plugin_cli_plugin(cli)
2721
+
2712
2722
  # Register built-in devbox commands last (so they win over any external
2713
2723
  # plugin).
2714
2724
  from morphcloud.devbox.cli import devbox as devbox_group
@@ -17,6 +17,7 @@ DEFAULT_VOLUMES_BASE_URL = "https://volumes.svc.cloud.morph.so"
17
17
  DEFAULT_DEVBOX_BASE_URL = "https://devbox.svc.cloud.morph.so"
18
18
  DEFAULT_ADMIN_BASE_URL = "https://admin.svc.cloud.morph.so"
19
19
  DEFAULT_DB_BASE_URL = "https://db.svc.cloud.morph.so"
20
+ DEFAULT_SIMPLE_INDEX_BASE_URL = "https://simple-index.svc.cloud.morph.so"
20
21
 
21
22
 
22
23
  @dataclass(frozen=True)
@@ -32,6 +33,7 @@ class ResolvedSettings:
32
33
  devbox_base_url: str
33
34
  admin_base_url: str
34
35
  db_base_url: str
36
+ simple_index_base_url: str
35
37
 
36
38
  def as_env(self, *, include_api_key: bool = True) -> Dict[str, str]:
37
39
  env: Dict[str, str] = {
@@ -44,6 +46,7 @@ class ResolvedSettings:
44
46
  "MORPH_DEVBOX_BASE_URL": self.devbox_base_url,
45
47
  "MORPH_ADMIN_BASE_URL": self.admin_base_url,
46
48
  "MORPH_DB_BASE_URL": self.db_base_url,
49
+ "MORPH_SIMPLE_INDEX_BASE_URL": self.simple_index_base_url,
47
50
  }
48
51
  if include_api_key and self.api_key:
49
52
  env["MORPH_API_KEY"] = self.api_key
@@ -255,6 +258,11 @@ def resolve_settings(
255
258
  env.get("MORPH_DB_BASE_URL"),
256
259
  profile_data.get("db_base_url"),
257
260
  )
261
+ simple_index_base_url = _coalesce(
262
+ overrides.get("simple_index_base_url"),
263
+ env.get("MORPH_SIMPLE_INDEX_BASE_URL"),
264
+ profile_data.get("simple_index_base_url"),
265
+ )
258
266
 
259
267
  if not api_host:
260
268
  api_host = _api_host_from_base_url(base_url)
@@ -277,6 +285,8 @@ def resolve_settings(
277
285
  admin_base_url = f"https://admin.svc.{api_host}"
278
286
  if not db_base_url:
279
287
  db_base_url = f"https://db.svc.{api_host}"
288
+ if not simple_index_base_url:
289
+ simple_index_base_url = f"https://simple-index.svc.{api_host}"
280
290
 
281
291
  base_url = base_url or DEFAULT_BASE_URL
282
292
  ssh_hostname = ssh_hostname or DEFAULT_SSH_HOSTNAME
@@ -286,6 +296,7 @@ def resolve_settings(
286
296
  devbox_base_url = devbox_base_url or DEFAULT_DEVBOX_BASE_URL
287
297
  admin_base_url = admin_base_url or DEFAULT_ADMIN_BASE_URL
288
298
  db_base_url = db_base_url or DEFAULT_DB_BASE_URL
299
+ simple_index_base_url = simple_index_base_url or DEFAULT_SIMPLE_INDEX_BASE_URL
289
300
 
290
301
  if not api_host:
291
302
  api_host = _api_host_from_base_url(base_url) or DEFAULT_API_HOST
@@ -302,6 +313,7 @@ def resolve_settings(
302
313
  devbox_base_url=devbox_base_url,
303
314
  admin_base_url=admin_base_url,
304
315
  db_base_url=db_base_url,
316
+ simple_index_base_url=simple_index_base_url,
305
317
  )
306
318
 
307
319
 
@@ -0,0 +1 @@
1
+ """Built-in plugin management commands for the Morph Cloud CLI."""