ibee-cli 0.1.0__tar.gz → 0.2.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 (28) hide show
  1. {ibee_cli-0.1.0/src/ibee_cli.egg-info → ibee_cli-0.2.0}/PKG-INFO +36 -6
  2. ibee_cli-0.2.0/README.md +95 -0
  3. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/pyproject.toml +2 -2
  4. ibee_cli-0.2.0/src/ibee_cli/commands/compute.py +95 -0
  5. ibee_cli-0.2.0/src/ibee_cli/commands/gpus.py +170 -0
  6. ibee_cli-0.2.0/src/ibee_cli/commands/ops.py +31 -0
  7. ibee_cli-0.2.0/src/ibee_cli/commands/secrets.py +228 -0
  8. ibee_cli-0.2.0/src/ibee_cli/commands/vms.py +162 -0
  9. ibee_cli-0.2.0/src/ibee_cli/helpers.py +106 -0
  10. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/src/ibee_cli/main.py +3 -1
  11. {ibee_cli-0.1.0 → ibee_cli-0.2.0/src/ibee_cli.egg-info}/PKG-INFO +36 -6
  12. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/src/ibee_cli.egg-info/SOURCES.txt +3 -0
  13. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/src/ibee_cli.egg-info/requires.txt +1 -1
  14. ibee_cli-0.1.0/README.md +0 -65
  15. ibee_cli-0.1.0/src/ibee_cli/commands/gpus.py +0 -41
  16. ibee_cli-0.1.0/src/ibee_cli/commands/secrets.py +0 -72
  17. ibee_cli-0.1.0/src/ibee_cli/commands/vms.py +0 -84
  18. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/LICENSE +0 -0
  19. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/setup.cfg +0 -0
  20. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/src/ibee_cli/__init__.py +0 -0
  21. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/src/ibee_cli/commands/__init__.py +0 -0
  22. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/src/ibee_cli/commands/buckets.py +0 -0
  23. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/src/ibee_cli/context.py +0 -0
  24. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/src/ibee_cli/render.py +0 -0
  25. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/src/ibee_cli.egg-info/dependency_links.txt +0 -0
  26. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/src/ibee_cli.egg-info/entry_points.txt +0 -0
  27. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/src/ibee_cli.egg-info/top_level.txt +0 -0
  28. {ibee_cli-0.1.0 → ibee_cli-0.2.0}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ibee-cli
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Official command-line interface for the IBEE Solutions cloud platform
5
5
  Author-email: IBEE Solutions <support@ibee.ai>
6
6
  License: MIT
@@ -14,7 +14,7 @@ Classifier: Environment :: Console
14
14
  Requires-Python: >=3.10
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: ibee>=0.1.2
17
+ Requires-Dist: ibee>=0.1.3
18
18
  Requires-Dist: typer>=0.12
19
19
  Requires-Dist: rich>=13.0
20
20
  Provides-Extra: dev
@@ -53,22 +53,52 @@ ibee buckets list
53
53
  ibee buckets create my-bucket
54
54
  ibee buckets delete my-bucket --yes
55
55
 
56
- # Secret Store
57
- ibee secrets stores
56
+ # Secret Store — stores
57
+ ibee secrets stores list
58
+ ibee secrets stores create production-secrets --description "prod"
59
+ ibee secrets stores get STORE_ID
60
+ ibee secrets stores update STORE_ID --name renamed
61
+ ibee secrets stores archive STORE_ID --yes
62
+
63
+ # Secret Store — secrets
58
64
  ibee secrets list --store-id STORE_ID
59
- ibee secrets value SECRET_ID
65
+ ibee secrets create --store-id STORE_ID --name db-url --value '{"url":"postgres://..."}'
66
+ ibee secrets get SECRET_ID # metadata
67
+ ibee secrets value SECRET_ID # current value
68
+ ibee secrets set-value SECRET_ID --value '{"url":"postgres://new"}'
69
+ ibee secrets delete SECRET_ID --yes
70
+
71
+ # Compute catalog (discover placement / plans / images for `create`)
72
+ ibee compute sites
73
+ ibee compute plans --vm-type cloud
74
+ ibee compute images --vm-type gpu
60
75
 
61
76
  # Cloud VMs
62
77
  ibee vms list
63
78
  ibee vms get VM_ID
79
+ ibee vms create web-01 --plan-id PLAN_ID --template-id IMAGE_ID --ssh-key-id KEY_ID --wait
64
80
  ibee vms start VM_ID
65
81
  ibee vms stop VM_ID
66
82
  ibee vms reboot VM_ID
83
+ ibee vms metrics VM_ID
84
+ ibee vms delete VM_ID --yes --wait
67
85
 
68
- # GPU VMs
86
+ # GPU VMs (same verbs as Cloud VMs)
69
87
  ibee gpus list
88
+ ibee gpus create train-01 --gpu-model A100 --gpu-count 1 --plan-id PLAN_ID --wait
89
+ ibee gpus start VM_ID
90
+ ibee gpus stop VM_ID
91
+ ibee gpus reboot VM_ID
92
+ ibee gpus metrics VM_ID
93
+ ibee gpus delete VM_ID --yes
94
+
95
+ # Async operations — poll a create/delete/power action to completion
96
+ ibee ops get OPERATION_ID --wait
70
97
  ```
71
98
 
99
+ Create/delete/power actions are asynchronous; add `--wait` to block until the
100
+ operation finishes, or poll it later with `ibee ops get`.
101
+
72
102
  Every command accepts `--json` for raw output:
73
103
 
74
104
  ```bash
@@ -0,0 +1,95 @@
1
+ # IBEE Solutions CLI
2
+
3
+ Official command-line interface for the IBEE Solutions cloud platform. Built on the [`ibee` Python SDK](https://github.com/devs-ibee/ibee-python).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install ibee-cli
9
+ ```
10
+
11
+ Requires Python 3.10+.
12
+
13
+ ## Authentication
14
+
15
+ Create an API token in the portal under **Settings > API Tokens**, then:
16
+
17
+ ```bash
18
+ export IBEE_TOKEN="ibee_live_xxxxxxxxxxxx"
19
+ export IBEE_WORKSPACE_ID="710995"
20
+ ```
21
+
22
+ Both can also be passed per command with `--token` and `--workspace`.
23
+
24
+ ## Usage
25
+
26
+ ```bash
27
+ # Object storage
28
+ ibee buckets list
29
+ ibee buckets create my-bucket
30
+ ibee buckets delete my-bucket --yes
31
+
32
+ # Secret Store — stores
33
+ ibee secrets stores list
34
+ ibee secrets stores create production-secrets --description "prod"
35
+ ibee secrets stores get STORE_ID
36
+ ibee secrets stores update STORE_ID --name renamed
37
+ ibee secrets stores archive STORE_ID --yes
38
+
39
+ # Secret Store — secrets
40
+ ibee secrets list --store-id STORE_ID
41
+ ibee secrets create --store-id STORE_ID --name db-url --value '{"url":"postgres://..."}'
42
+ ibee secrets get SECRET_ID # metadata
43
+ ibee secrets value SECRET_ID # current value
44
+ ibee secrets set-value SECRET_ID --value '{"url":"postgres://new"}'
45
+ ibee secrets delete SECRET_ID --yes
46
+
47
+ # Compute catalog (discover placement / plans / images for `create`)
48
+ ibee compute sites
49
+ ibee compute plans --vm-type cloud
50
+ ibee compute images --vm-type gpu
51
+
52
+ # Cloud VMs
53
+ ibee vms list
54
+ ibee vms get VM_ID
55
+ ibee vms create web-01 --plan-id PLAN_ID --template-id IMAGE_ID --ssh-key-id KEY_ID --wait
56
+ ibee vms start VM_ID
57
+ ibee vms stop VM_ID
58
+ ibee vms reboot VM_ID
59
+ ibee vms metrics VM_ID
60
+ ibee vms delete VM_ID --yes --wait
61
+
62
+ # GPU VMs (same verbs as Cloud VMs)
63
+ ibee gpus list
64
+ ibee gpus create train-01 --gpu-model A100 --gpu-count 1 --plan-id PLAN_ID --wait
65
+ ibee gpus start VM_ID
66
+ ibee gpus stop VM_ID
67
+ ibee gpus reboot VM_ID
68
+ ibee gpus metrics VM_ID
69
+ ibee gpus delete VM_ID --yes
70
+
71
+ # Async operations — poll a create/delete/power action to completion
72
+ ibee ops get OPERATION_ID --wait
73
+ ```
74
+
75
+ Create/delete/power actions are asynchronous; add `--wait` to block until the
76
+ operation finishes, or poll it later with `ibee ops get`.
77
+
78
+ Every command accepts `--json` for raw output:
79
+
80
+ ```bash
81
+ ibee --json buckets list
82
+ ```
83
+
84
+ ## Environments
85
+
86
+ The CLI targets production by default. Use `--dev` (or `IBEE_ENV=dev`) for the development environment, or `--base-url` for a custom endpoint:
87
+
88
+ ```bash
89
+ ibee --dev buckets list
90
+ ```
91
+
92
+ ## Related
93
+
94
+ - [Python SDK](https://github.com/devs-ibee/ibee-python) — `pip install ibee`
95
+ - [API documentation](https://ibee.ai/docs/api-reference)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ibee-cli"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Official command-line interface for the IBEE Solutions cloud platform"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -19,7 +19,7 @@ classifiers = [
19
19
  "Environment :: Console",
20
20
  ]
21
21
  dependencies = [
22
- "ibee>=0.1.2",
22
+ "ibee>=0.1.3",
23
23
  "typer>=0.12",
24
24
  "rich>=13.0",
25
25
  ]
@@ -0,0 +1,95 @@
1
+ """Compute catalog (discovery) commands: sites, plans, images.
2
+
3
+ These back the choices needed to create a VM (`--plan-id`, `--template-id`,
4
+ placement). They call the SDK's ``compute_catalog`` resource when the installed
5
+ ``ibee`` exposes it (>= 0.2.0); on older SDKs they fall back to a direct GET
6
+ against the same gateway, so the commands work regardless of SDK version.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Optional
12
+
13
+ import httpx
14
+ import typer
15
+ from ibee.environment import IbeeEnvironment
16
+
17
+ from ..context import get_client, get_settings, require_token, require_workspace
18
+ from ..render import handle_api_errors, print_json
19
+
20
+ app = typer.Typer(help="Compute catalog: sites, plans, images", no_args_is_help=True)
21
+
22
+
23
+ def _base_url(settings) -> str:
24
+ if settings.base_url:
25
+ return settings.base_url.rstrip("/")
26
+ env = IbeeEnvironment.DEVELOPMENT if settings.dev else IbeeEnvironment.DEFAULT
27
+ return env.value.rstrip("/")
28
+
29
+
30
+ def _fetch(ctx: typer.Context, sdk_method: str, path: str, filters: dict):
31
+ """Return catalog data via the SDK method if available, else a direct GET."""
32
+ settings = get_settings(ctx)
33
+ workspace = require_workspace(settings)
34
+ client = get_client(settings)
35
+ params = {k: v for k, v in filters.items() if v is not None}
36
+
37
+ catalog = getattr(client, "compute_catalog", None)
38
+ if catalog is not None:
39
+ return getattr(catalog, sdk_method)(workspace_id=workspace, **params)
40
+
41
+ # Fallback for SDKs without compute_catalog (< 0.2.0): same gateway + auth.
42
+ token = require_token(settings)
43
+ resp = httpx.get(
44
+ f"{_base_url(settings)}/{path}",
45
+ params={"workspace_id": workspace, **params},
46
+ headers={"Authorization": f"Bearer {token}", "accept": "application/json"},
47
+ timeout=30,
48
+ )
49
+ if resp.status_code >= 400:
50
+ typer.secho(f"API error {resp.status_code}: {resp.text[:300]}", fg=typer.colors.RED, err=True)
51
+ raise typer.Exit(code=1)
52
+ return resp.json()
53
+
54
+
55
+ @app.command("sites")
56
+ @handle_api_errors
57
+ def list_sites(
58
+ ctx: typer.Context,
59
+ region_id: Optional[str] = typer.Option(None, "--region-id", help="Filter by region"),
60
+ country_id: Optional[str] = typer.Option(None, "--country-id", help="Filter by country"),
61
+ ) -> None:
62
+ """List sites where cloud and GPU VMs can be placed."""
63
+ result = _fetch(ctx, "list_compute_sites", "compute/sites",
64
+ {"region_id": region_id, "country_id": country_id})
65
+ print_json(result)
66
+
67
+
68
+ @app.command("plans")
69
+ @handle_api_errors
70
+ def list_plans(
71
+ ctx: typer.Context,
72
+ vm_type: Optional[str] = typer.Option(None, "--vm-type", help="cloud or gpu"),
73
+ site_id: Optional[str] = typer.Option(None, "--site-id", help="Filter by site"),
74
+ currency: Optional[str] = typer.Option(None, "--currency", help="Price currency (e.g. INR, USD)"),
75
+ billing_interval: Optional[str] = typer.Option(None, "--billing-interval", help="e.g. hourly, monthly"),
76
+ ) -> None:
77
+ """List billable plans for cloud or GPU VMs (use the IDs with `vms/gpus create --plan-id`)."""
78
+ result = _fetch(ctx, "list_compute_plans", "compute/plans", {
79
+ "vm_type": vm_type, "site_id": site_id,
80
+ "currency": currency, "billing_interval": billing_interval,
81
+ })
82
+ print_json(result)
83
+
84
+
85
+ @app.command("images")
86
+ @handle_api_errors
87
+ def list_images(
88
+ ctx: typer.Context,
89
+ vm_type: Optional[str] = typer.Option(None, "--vm-type", help="cloud or gpu"),
90
+ currency: Optional[str] = typer.Option(None, "--currency", help="Price currency (e.g. INR, USD)"),
91
+ ) -> None:
92
+ """List OS templates/images (use the IDs with `vms/gpus create --template-id`)."""
93
+ result = _fetch(ctx, "list_compute_images", "compute/images",
94
+ {"vm_type": vm_type, "currency": currency})
95
+ print_json(result)
@@ -0,0 +1,170 @@
1
+ """GPU VM commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import List, Optional
6
+
7
+ import typer
8
+
9
+ from ..context import get_client, get_settings, require_workspace
10
+ from ..helpers import finish_operation, new_idempotency_key
11
+ from ..render import handle_api_errors, print_json, print_table
12
+
13
+ app = typer.Typer(help="GPU VMs", no_args_is_help=True)
14
+
15
+
16
+ @app.command("list")
17
+ @handle_api_errors
18
+ def list_gpu_vms(ctx: typer.Context) -> None:
19
+ """List GPU VMs in the workspace."""
20
+ settings = get_settings(ctx)
21
+ client = get_client(settings)
22
+ result = client.gpu_vms.list_gpu_vms(workspace_id=require_workspace(settings))
23
+ if settings.as_json:
24
+ print_json(result)
25
+ return
26
+ vms = getattr(result, "items", None) or getattr(result, "vms", None) or []
27
+ print_table(
28
+ "GPU VMs",
29
+ ["ID", "Name", "Status", "GPU", "GPUs", "CPU", "RAM (MB)", "Public IP"],
30
+ [
31
+ (
32
+ v.id, v.name, getattr(v.status, "value", v.status),
33
+ getattr(v, "gpu_model", None), getattr(v, "gpu_count", None),
34
+ v.cpu, v.ram_mb, v.public_ip,
35
+ )
36
+ for v in vms
37
+ ],
38
+ )
39
+
40
+
41
+ @app.command("get")
42
+ @handle_api_errors
43
+ def get_gpu_vm(ctx: typer.Context, vm_id: str = typer.Argument(..., help="GPU VM ID")) -> None:
44
+ """Show one GPU VM."""
45
+ settings = get_settings(ctx)
46
+ client = get_client(settings)
47
+ result = client.gpu_vms.get_gpu_vm(workspace_id=require_workspace(settings), vm_id=vm_id)
48
+ print_json(result)
49
+
50
+
51
+ @app.command("create")
52
+ @handle_api_errors
53
+ def create_gpu_vm(
54
+ ctx: typer.Context,
55
+ name: str = typer.Argument(..., help="Display name for the GPU VM"),
56
+ gpu_model: str = typer.Option(..., "--gpu-model", help="GPU model (A100, H100, L40S, RTX4090)"),
57
+ gpu_count: int = typer.Option(1, "--gpu-count", help="Number of GPUs to attach"),
58
+ os_distro: str = typer.Option("ubuntu", "--os-distro", help="OS distribution"),
59
+ os_type: str = typer.Option("linux", "--os-type", help="OS family (linux, windows)"),
60
+ cpu: int = typer.Option(8, "--cpu", help="vCPUs (fallback when no plan)"),
61
+ ram_mb: int = typer.Option(32768, "--ram-mb", help="RAM in MB (fallback when no plan)"),
62
+ plan_id: Optional[str] = typer.Option(None, "--plan-id", help="GPU plan ID. See `ibee compute plans`."),
63
+ template_id: Optional[str] = typer.Option(None, "--template-id", help="OS template/image ID. See `ibee compute images`."),
64
+ disk_gb: Optional[int] = typer.Option(None, "--disk-gb", help="Root disk size in GB"),
65
+ ssh_key_id: Optional[List[str]] = typer.Option(None, "--ssh-key-id", help="SSH key ID to inject (repeatable)"),
66
+ tag: Optional[List[str]] = typer.Option(None, "--tag", help="Tag (repeatable)"),
67
+ wait: bool = typer.Option(False, "--wait", help="Poll until the VM is provisioned"),
68
+ ) -> None:
69
+ """Create a GPU VM (async — returns an operation)."""
70
+ settings = get_settings(ctx)
71
+ workspace = require_workspace(settings)
72
+ client = get_client(settings)
73
+ result = client.gpu_vms.create_gpu_vm(
74
+ workspace_id=workspace,
75
+ idempotency_key=new_idempotency_key("gpu-create", name),
76
+ name=name,
77
+ os_distro=os_distro,
78
+ os_type=os_type,
79
+ cpu=cpu,
80
+ ram_mb=ram_mb,
81
+ gpu_count=gpu_count,
82
+ gpu_model=gpu_model,
83
+ plan_id=plan_id,
84
+ template_id=template_id,
85
+ disk_gb=disk_gb,
86
+ ssh_key_ids=ssh_key_id or None,
87
+ tags=tag or None,
88
+ )
89
+ finish_operation(settings, client, workspace, result, "Create", name, wait)
90
+
91
+
92
+ @app.command("delete")
93
+ @handle_api_errors
94
+ def delete_gpu_vm(
95
+ ctx: typer.Context,
96
+ vm_id: str = typer.Argument(..., help="GPU VM ID"),
97
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
98
+ wait: bool = typer.Option(False, "--wait", help="Poll until the VM is deleted"),
99
+ ) -> None:
100
+ """Delete a GPU VM (async — returns an operation)."""
101
+ settings = get_settings(ctx)
102
+ if not yes:
103
+ typer.confirm(f"Delete GPU VM '{vm_id}'?", abort=True)
104
+ workspace = require_workspace(settings)
105
+ client = get_client(settings)
106
+ result = client.gpu_vms.delete_gpu_vm(
107
+ vm_id=vm_id,
108
+ workspace_id=workspace,
109
+ idempotency_key=new_idempotency_key("gpu-delete", vm_id),
110
+ )
111
+ finish_operation(settings, client, workspace, result, "Delete", vm_id, wait)
112
+
113
+
114
+ @app.command("metrics")
115
+ @handle_api_errors
116
+ def gpu_metrics(ctx: typer.Context, vm_id: str = typer.Argument(..., help="GPU VM ID")) -> None:
117
+ """Show current resource-usage metrics for a GPU VM."""
118
+ settings = get_settings(ctx)
119
+ client = get_client(settings)
120
+ # SDK method was renamed get_gpu_vm_metrics -> get_gpu_vm_metrics_overview
121
+ # (ibee 0.2.0); accept either so the CLI works across SDK versions.
122
+ method = getattr(client.gpu_vms, "get_gpu_vm_metrics_overview", None) or client.gpu_vms.get_gpu_vm_metrics
123
+ result = method(workspace_id=require_workspace(settings), vm_id=vm_id)
124
+ print_json(result)
125
+
126
+
127
+ def _power_action(ctx: typer.Context, vm_id: str, action: str, wait: bool) -> None:
128
+ settings = get_settings(ctx)
129
+ workspace = require_workspace(settings)
130
+ client = get_client(settings)
131
+ method = getattr(client.gpu_vms, f"{action}_gpu_vm")
132
+ result = method(
133
+ workspace_id=workspace,
134
+ vm_id=vm_id,
135
+ idempotency_key=new_idempotency_key(action, vm_id),
136
+ )
137
+ finish_operation(settings, client, workspace, result, action.capitalize(), vm_id, wait)
138
+
139
+
140
+ @app.command("start")
141
+ @handle_api_errors
142
+ def start_gpu_vm(
143
+ ctx: typer.Context,
144
+ vm_id: str = typer.Argument(...),
145
+ wait: bool = typer.Option(False, "--wait", help="Poll until started"),
146
+ ) -> None:
147
+ """Start a GPU VM."""
148
+ _power_action(ctx, vm_id, "start", wait)
149
+
150
+
151
+ @app.command("stop")
152
+ @handle_api_errors
153
+ def stop_gpu_vm(
154
+ ctx: typer.Context,
155
+ vm_id: str = typer.Argument(...),
156
+ wait: bool = typer.Option(False, "--wait", help="Poll until stopped"),
157
+ ) -> None:
158
+ """Stop a GPU VM."""
159
+ _power_action(ctx, vm_id, "stop", wait)
160
+
161
+
162
+ @app.command("reboot")
163
+ @handle_api_errors
164
+ def reboot_gpu_vm(
165
+ ctx: typer.Context,
166
+ vm_id: str = typer.Argument(...),
167
+ wait: bool = typer.Option(False, "--wait", help="Poll until rebooted"),
168
+ ) -> None:
169
+ """Reboot a GPU VM."""
170
+ _power_action(ctx, vm_id, "reboot", wait)
@@ -0,0 +1,31 @@
1
+ """Compute operation (async task) commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from ..context import get_client, get_settings, require_workspace
8
+ from ..helpers import wait_for_operation
9
+ from ..render import handle_api_errors, print_json
10
+
11
+ app = typer.Typer(help="Async compute operations (VM create/delete/power)", no_args_is_help=True)
12
+
13
+
14
+ @app.command("get")
15
+ @handle_api_errors
16
+ def get_operation(
17
+ ctx: typer.Context,
18
+ operation_id: str = typer.Argument(..., help="Operation ID from a create/delete/power action"),
19
+ wait: bool = typer.Option(False, "--wait", help="Poll until the operation reaches a terminal state"),
20
+ ) -> None:
21
+ """Show the status of an async compute operation (cloud or GPU VM)."""
22
+ settings = get_settings(ctx)
23
+ workspace = require_workspace(settings)
24
+ client = get_client(settings)
25
+ if wait:
26
+ result = wait_for_operation(client, workspace, operation_id)
27
+ else:
28
+ result = client.cloud_vms.get_compute_operation(
29
+ operation_id=operation_id, workspace_id=workspace
30
+ )
31
+ print_json(result)