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.
- {morphcloud-0.1.111 → morphcloud-0.1.112}/.gitignore +8 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/PKG-INFO +63 -1
- {morphcloud-0.1.111 → morphcloud-0.1.112}/README.md +62 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/api.py +19 -1
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/cli.py +14 -4
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/config.py +12 -0
- morphcloud-0.1.112/morphcloud/plugins/__init__.py +1 -0
- morphcloud-0.1.112/morphcloud/plugins/cli.py +1164 -0
- morphcloud-0.1.112/morphcloud/plugins/installer.py +569 -0
- morphcloud-0.1.112/morphcloud/simple_index/__init__.py +6 -0
- morphcloud-0.1.112/morphcloud/simple_index/client.py +415 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/pyproject.toml +1 -1
- morphcloud-0.1.112/tests/unit/test_cli_plugins.py +1764 -0
- morphcloud-0.1.112/tests/unit/test_plugin_installer.py +431 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_profiles.py +8 -0
- morphcloud-0.1.112/tests/unit/test_simple_index_client.py +392 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/.github/workflows/publish.yaml +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/LICENSE +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/Makefile +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/conf.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/examples/browser_example.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/examples/devbox_example.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/index.rst +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/make.bat +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_asyncify.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_bash_interpreter.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_http_transport.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_llm.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_scramble.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_ssh.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/_utils.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/cli_helpers.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/cli.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/config.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/admin/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/admin/client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/admin/raw_client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/aliases/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/aliases/client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/aliases/raw_client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/api_error.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/client_wrapper.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/datetime_utils.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/file.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/force_multipart.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/http_client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/http_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/jsonable_encoder.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/pydantic_utilities.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/query_encoder.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/remove_none_from_dict.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/request_options.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/core/serialization.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_actions/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_actions/client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_actions/raw_client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_core/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_core/client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_core/raw_client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_lifecycle/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_lifecycle/client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/devboxes_lifecycle/raw_client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/errors/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/errors/unprocessable_entity_error.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/templates/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/templates/client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/templates/raw_client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/tmux/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/tmux/client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/tmux/raw_client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/alias_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/branch_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/delete_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_detail_list_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_list_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_refs.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_spec.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_ssh_credentials_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/devbox_summary_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/http_service.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/http_service_auth_mode.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/http_validation_error.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/networking.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/secret_value_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/snapshot_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/success_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_cache_request.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_cache_start_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_detail.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_list_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_share_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_status.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_step_model.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_step_status.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/template_summary.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/timeline_event.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_debug_info_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_install_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_new_session_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_send_keys_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_session.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_sessions_list.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_snapshot_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/tmux_target.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/ttl.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/upload_response.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/validation_error.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/validation_error_loc_item.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/types/wake_on.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/uploads/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/uploads/client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/uploads/raw_client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/user_secrets/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/user_secrets/client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/gen/user_secrets/raw_client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/openapi.json +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/template_local_runner.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/template_runner.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/devbox/terminals.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/experimental/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/experimental/browser.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/py.typed +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/volumes/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/volumes/cli.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/morphcloud/volumes/client.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/requirements.txt +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/scripts/abuild_smoke.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/scripts/increment_version.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/__init__.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/conftest.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/pytest.ini +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_as_container.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_branching.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_command_execution.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_file_operations.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_from_tag_multiple.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_http_service.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_metadata.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_simple.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_snapshot_operations.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_ssh_key_rotation.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_ttl.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/integration/test_user.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_cli_devbox.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_cli_devbox_terminal.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_cli_secrets.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_cli_snapshots.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_cli_volumes.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_devbox_terminals.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_http_transport_sharding.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_snapshot_ttl.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_ssh_connect_retry.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_template_local_runner.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_template_runner.py +0 -0
- {morphcloud-0.1.111 → morphcloud-0.1.112}/tests/unit/test_volumes_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: morphcloud
|
|
3
|
-
Version: 0.1.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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."""
|