ibee-cli 0.1.0__py3-none-any.whl
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.
- ibee_cli/__init__.py +3 -0
- ibee_cli/commands/__init__.py +0 -0
- ibee_cli/commands/buckets.py +83 -0
- ibee_cli/commands/gpus.py +41 -0
- ibee_cli/commands/secrets.py +72 -0
- ibee_cli/commands/vms.py +84 -0
- ibee_cli/context.py +70 -0
- ibee_cli/main.py +47 -0
- ibee_cli/render.py +64 -0
- ibee_cli-0.1.0.dist-info/METADATA +89 -0
- ibee_cli-0.1.0.dist-info/RECORD +15 -0
- ibee_cli-0.1.0.dist-info/WHEEL +5 -0
- ibee_cli-0.1.0.dist-info/entry_points.txt +2 -0
- ibee_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- ibee_cli-0.1.0.dist-info/top_level.txt +1 -0
ibee_cli/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Object storage bucket commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from ..context import get_client, get_settings, require_workspace
|
|
8
|
+
from ..render import handle_api_errors, print_json, print_table
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="Object storage buckets", no_args_is_help=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _human_size(num: int | None) -> str:
|
|
14
|
+
if num is None:
|
|
15
|
+
return "-"
|
|
16
|
+
size = float(num)
|
|
17
|
+
for unit in ("B", "KB", "MB", "GB", "TB"):
|
|
18
|
+
if size < 1024 or unit == "TB":
|
|
19
|
+
return f"{size:.1f} {unit}" if unit != "B" else f"{int(size)} B"
|
|
20
|
+
size /= 1024
|
|
21
|
+
return str(num)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command("list")
|
|
25
|
+
@handle_api_errors
|
|
26
|
+
def list_buckets(ctx: typer.Context) -> None:
|
|
27
|
+
"""List buckets in the workspace."""
|
|
28
|
+
settings = get_settings(ctx)
|
|
29
|
+
client = get_client(settings)
|
|
30
|
+
result = client.object_storage.list_buckets(workspace_id=require_workspace(settings))
|
|
31
|
+
if settings.as_json:
|
|
32
|
+
print_json(result)
|
|
33
|
+
return
|
|
34
|
+
buckets = result.buckets or []
|
|
35
|
+
print_table(
|
|
36
|
+
"Buckets",
|
|
37
|
+
["Name", "Objects", "Size"],
|
|
38
|
+
[(b.name, b.object_count, _human_size(b.total_size)) for b in buckets],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command("create")
|
|
43
|
+
@handle_api_errors
|
|
44
|
+
def create_bucket(
|
|
45
|
+
ctx: typer.Context,
|
|
46
|
+
name: str = typer.Argument(..., help="Bucket name (unique within the workspace)"),
|
|
47
|
+
public: bool = typer.Option(False, "--public", help="Allow unauthenticated read access"),
|
|
48
|
+
region: str = typer.Option(
|
|
49
|
+
"in-south-1",
|
|
50
|
+
"--region",
|
|
51
|
+
envvar="IBEE_REGION",
|
|
52
|
+
help="Storage region for the bucket",
|
|
53
|
+
),
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Create a bucket."""
|
|
56
|
+
settings = get_settings(ctx)
|
|
57
|
+
client = get_client(settings)
|
|
58
|
+
result = client.object_storage.create_bucket(
|
|
59
|
+
workspace_id=require_workspace(settings),
|
|
60
|
+
name=name,
|
|
61
|
+
region=region,
|
|
62
|
+
is_public=public,
|
|
63
|
+
)
|
|
64
|
+
if settings.as_json:
|
|
65
|
+
print_json(result)
|
|
66
|
+
return
|
|
67
|
+
typer.secho(f"Bucket '{name}' created in {region}.", fg=typer.colors.GREEN)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@app.command("delete")
|
|
71
|
+
@handle_api_errors
|
|
72
|
+
def delete_bucket(
|
|
73
|
+
ctx: typer.Context,
|
|
74
|
+
name: str = typer.Argument(..., help="Bucket name"),
|
|
75
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
76
|
+
) -> None:
|
|
77
|
+
"""Delete a bucket."""
|
|
78
|
+
settings = get_settings(ctx)
|
|
79
|
+
if not yes:
|
|
80
|
+
typer.confirm(f"Delete bucket '{name}'?", abort=True)
|
|
81
|
+
client = get_client(settings)
|
|
82
|
+
client.object_storage.delete_bucket(workspace_id=require_workspace(settings), bucket_name=name)
|
|
83
|
+
typer.secho(f"Bucket '{name}' deleted.", fg=typer.colors.GREEN)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""GPU VM commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from ..context import get_client, get_settings, require_workspace
|
|
8
|
+
from ..render import handle_api_errors, print_json, print_table
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="GPU VMs", no_args_is_help=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command("list")
|
|
14
|
+
@handle_api_errors
|
|
15
|
+
def list_gpu_vms(ctx: typer.Context) -> None:
|
|
16
|
+
"""List GPU VMs in the workspace."""
|
|
17
|
+
settings = get_settings(ctx)
|
|
18
|
+
client = get_client(settings)
|
|
19
|
+
result = client.gpu_vms.list_gpu_vms(workspace_id=require_workspace(settings))
|
|
20
|
+
if settings.as_json:
|
|
21
|
+
print_json(result)
|
|
22
|
+
return
|
|
23
|
+
vms = getattr(result, "items", None) or getattr(result, "vms", None) or []
|
|
24
|
+
print_table(
|
|
25
|
+
"GPU VMs",
|
|
26
|
+
["ID", "Name", "Status", "CPU", "RAM (MB)", "Public IP"],
|
|
27
|
+
[
|
|
28
|
+
(v.id, v.name, getattr(v.status, "value", v.status), v.cpu, v.ram_mb, v.public_ip)
|
|
29
|
+
for v in vms
|
|
30
|
+
],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.command("get")
|
|
35
|
+
@handle_api_errors
|
|
36
|
+
def get_gpu_vm(ctx: typer.Context, vm_id: str = typer.Argument(..., help="GPU VM ID")) -> None:
|
|
37
|
+
"""Show one GPU VM."""
|
|
38
|
+
settings = get_settings(ctx)
|
|
39
|
+
client = get_client(settings)
|
|
40
|
+
result = client.gpu_vms.get_gpu_vm(workspace_id=require_workspace(settings), vm_id=vm_id)
|
|
41
|
+
print_json(result)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Secret Store commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from ..context import get_client, get_settings, require_workspace
|
|
8
|
+
from ..render import handle_api_errors, print_json, print_table
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="Secret Store: stores and secrets", no_args_is_help=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command("stores")
|
|
14
|
+
@handle_api_errors
|
|
15
|
+
def list_stores(ctx: typer.Context) -> None:
|
|
16
|
+
"""List secret stores in the workspace."""
|
|
17
|
+
settings = get_settings(ctx)
|
|
18
|
+
client = get_client(settings)
|
|
19
|
+
result = client.secret_store.list_secret_stores(workspace_id=require_workspace(settings))
|
|
20
|
+
if settings.as_json:
|
|
21
|
+
print_json(result)
|
|
22
|
+
return
|
|
23
|
+
stores = result.stores or []
|
|
24
|
+
print_table(
|
|
25
|
+
"Secret stores",
|
|
26
|
+
["ID", "Name", "Store key", "Status", "Updated"],
|
|
27
|
+
[
|
|
28
|
+
(s.id, s.name, s.store_key, getattr(s.status, "value", s.status), str(s.updated_at or "")[:19])
|
|
29
|
+
for s in stores
|
|
30
|
+
],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.command("list")
|
|
35
|
+
@handle_api_errors
|
|
36
|
+
def list_secrets(
|
|
37
|
+
ctx: typer.Context,
|
|
38
|
+
store_id: str = typer.Option(..., "--store-id", "-s", help="Secret store ID"),
|
|
39
|
+
) -> None:
|
|
40
|
+
"""List secrets inside a store."""
|
|
41
|
+
settings = get_settings(ctx)
|
|
42
|
+
client = get_client(settings)
|
|
43
|
+
result = client.secret_store.list_secrets(
|
|
44
|
+
workspace_id=require_workspace(settings), store_id=store_id
|
|
45
|
+
)
|
|
46
|
+
if settings.as_json:
|
|
47
|
+
print_json(result)
|
|
48
|
+
return
|
|
49
|
+
secrets = getattr(result, "secrets", None) or []
|
|
50
|
+
print_table(
|
|
51
|
+
"Secrets",
|
|
52
|
+
["ID", "Name", "Status", "Updated"],
|
|
53
|
+
[
|
|
54
|
+
(s.id, s.name, getattr(s.status, "value", s.status), str(getattr(s, "updated_at", "") or "")[:19])
|
|
55
|
+
for s in secrets
|
|
56
|
+
],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command("value")
|
|
61
|
+
@handle_api_errors
|
|
62
|
+
def get_value(
|
|
63
|
+
ctx: typer.Context,
|
|
64
|
+
secret_id: str = typer.Argument(..., help="Secret ID"),
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Print a secret's current value as JSON. Requires secret-store.read scope."""
|
|
67
|
+
settings = get_settings(ctx)
|
|
68
|
+
client = get_client(settings)
|
|
69
|
+
result = client.secret_store.get_secret_value(
|
|
70
|
+
workspace_id=require_workspace(settings), secret_id=secret_id
|
|
71
|
+
)
|
|
72
|
+
print_json(result)
|
ibee_cli/commands/vms.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Cloud VM commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from ..context import get_client, get_settings, require_workspace
|
|
10
|
+
from ..render import handle_api_errors, print_json, print_table
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(help="Cloud VMs", no_args_is_help=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command("list")
|
|
16
|
+
@handle_api_errors
|
|
17
|
+
def list_vms(ctx: typer.Context) -> None:
|
|
18
|
+
"""List cloud VMs in the workspace."""
|
|
19
|
+
settings = get_settings(ctx)
|
|
20
|
+
client = get_client(settings)
|
|
21
|
+
result = client.cloud_vms.list_cloud_vms(workspace_id=require_workspace(settings))
|
|
22
|
+
if settings.as_json:
|
|
23
|
+
print_json(result)
|
|
24
|
+
return
|
|
25
|
+
vms = getattr(result, "items", None) or getattr(result, "vms", None) or []
|
|
26
|
+
print_table(
|
|
27
|
+
"Cloud VMs",
|
|
28
|
+
["ID", "Name", "Status", "CPU", "RAM (MB)", "Public IP"],
|
|
29
|
+
[
|
|
30
|
+
(v.id, v.name, getattr(v.status, "value", v.status), v.cpu, v.ram_mb, v.public_ip)
|
|
31
|
+
for v in vms
|
|
32
|
+
],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@app.command("get")
|
|
37
|
+
@handle_api_errors
|
|
38
|
+
def get_vm(ctx: typer.Context, vm_id: str = typer.Argument(..., help="VM ID")) -> None:
|
|
39
|
+
"""Show one cloud VM."""
|
|
40
|
+
settings = get_settings(ctx)
|
|
41
|
+
client = get_client(settings)
|
|
42
|
+
result = client.cloud_vms.get_cloud_vm(workspace_id=require_workspace(settings), vm_id=vm_id)
|
|
43
|
+
print_json(result)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _power_action(ctx: typer.Context, vm_id: str, action: str) -> None:
|
|
47
|
+
settings = get_settings(ctx)
|
|
48
|
+
client = get_client(settings)
|
|
49
|
+
method = getattr(client.cloud_vms, f"{action}_cloud_vm")
|
|
50
|
+
result = method(
|
|
51
|
+
workspace_id=require_workspace(settings),
|
|
52
|
+
vm_id=vm_id,
|
|
53
|
+
idempotency_key=f"cli-{action}-{vm_id}-{uuid.uuid4().hex[:8]}",
|
|
54
|
+
)
|
|
55
|
+
if settings.as_json:
|
|
56
|
+
print_json(result)
|
|
57
|
+
return
|
|
58
|
+
op_id = getattr(result, "operation_id", None) or getattr(result, "id", None)
|
|
59
|
+
typer.secho(
|
|
60
|
+
f"{action.capitalize()} accepted for VM {vm_id}"
|
|
61
|
+
+ (f" (operation {op_id})" if op_id else ""),
|
|
62
|
+
fg=typer.colors.GREEN,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@app.command("start")
|
|
67
|
+
@handle_api_errors
|
|
68
|
+
def start_vm(ctx: typer.Context, vm_id: str = typer.Argument(...)) -> None:
|
|
69
|
+
"""Start a cloud VM."""
|
|
70
|
+
_power_action(ctx, vm_id, "start")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@app.command("stop")
|
|
74
|
+
@handle_api_errors
|
|
75
|
+
def stop_vm(ctx: typer.Context, vm_id: str = typer.Argument(...)) -> None:
|
|
76
|
+
"""Stop a cloud VM."""
|
|
77
|
+
_power_action(ctx, vm_id, "stop")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@app.command("reboot")
|
|
81
|
+
@handle_api_errors
|
|
82
|
+
def reboot_vm(ctx: typer.Context, vm_id: str = typer.Argument(...)) -> None:
|
|
83
|
+
"""Reboot a cloud VM."""
|
|
84
|
+
_power_action(ctx, vm_id, "reboot")
|
ibee_cli/context.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Shared CLI state: authentication, environment, and client construction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from ibee import Ibee
|
|
10
|
+
from ibee.environment import IbeeEnvironment
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Settings:
|
|
15
|
+
token: str | None
|
|
16
|
+
workspace: str | None
|
|
17
|
+
dev: bool
|
|
18
|
+
base_url: str | None
|
|
19
|
+
as_json: bool
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_settings(ctx: typer.Context) -> Settings:
|
|
23
|
+
return ctx.obj
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def require_token(settings: Settings) -> str:
|
|
27
|
+
if settings.token:
|
|
28
|
+
return settings.token
|
|
29
|
+
typer.secho(
|
|
30
|
+
"No API token. Set IBEE_TOKEN (or pass --token). "
|
|
31
|
+
"Create one in the portal under Settings > API Tokens.",
|
|
32
|
+
fg=typer.colors.RED,
|
|
33
|
+
err=True,
|
|
34
|
+
)
|
|
35
|
+
raise typer.Exit(code=2)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def require_workspace(settings: Settings) -> str:
|
|
39
|
+
if settings.workspace:
|
|
40
|
+
return settings.workspace
|
|
41
|
+
typer.secho(
|
|
42
|
+
"No workspace. Set IBEE_WORKSPACE_ID (or pass --workspace).",
|
|
43
|
+
fg=typer.colors.RED,
|
|
44
|
+
err=True,
|
|
45
|
+
)
|
|
46
|
+
raise typer.Exit(code=2)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_client(settings: Settings) -> Ibee:
|
|
50
|
+
token = require_token(settings)
|
|
51
|
+
if settings.base_url:
|
|
52
|
+
return Ibee(token=token, base_url=settings.base_url, timeout=30)
|
|
53
|
+
env = IbeeEnvironment.DEVELOPMENT if settings.dev else IbeeEnvironment.DEFAULT
|
|
54
|
+
return Ibee(token=token, environment=env, timeout=30)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def settings_from_env(
|
|
58
|
+
token: str | None,
|
|
59
|
+
workspace: str | None,
|
|
60
|
+
dev: bool,
|
|
61
|
+
base_url: str | None,
|
|
62
|
+
as_json: bool,
|
|
63
|
+
) -> Settings:
|
|
64
|
+
return Settings(
|
|
65
|
+
token=token or os.environ.get("IBEE_TOKEN") or os.environ.get("IBEE_API_TOKEN"),
|
|
66
|
+
workspace=workspace or os.environ.get("IBEE_WORKSPACE_ID"),
|
|
67
|
+
dev=dev or os.environ.get("IBEE_ENV", "").lower() in ("dev", "development"),
|
|
68
|
+
base_url=base_url or os.environ.get("IBEE_BASE_URL"),
|
|
69
|
+
as_json=as_json,
|
|
70
|
+
)
|
ibee_cli/main.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""ibee — the IBEE Solutions CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from . import __version__
|
|
8
|
+
from .commands import buckets, gpus, secrets, vms
|
|
9
|
+
from .context import settings_from_env
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(
|
|
12
|
+
name="ibee",
|
|
13
|
+
help="IBEE Solutions cloud platform CLI. Auth: set IBEE_TOKEN and IBEE_WORKSPACE_ID.",
|
|
14
|
+
no_args_is_help=True,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
app.add_typer(buckets.app, name="buckets")
|
|
18
|
+
app.add_typer(secrets.app, name="secrets")
|
|
19
|
+
app.add_typer(vms.app, name="vms")
|
|
20
|
+
app.add_typer(gpus.app, name="gpus")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _version_callback(value: bool) -> None:
|
|
24
|
+
if value:
|
|
25
|
+
typer.echo(f"ibee-cli {__version__}")
|
|
26
|
+
raise typer.Exit()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.callback()
|
|
30
|
+
def main(
|
|
31
|
+
ctx: typer.Context,
|
|
32
|
+
token: str = typer.Option(None, "--token", help="API token (default: IBEE_TOKEN env)"),
|
|
33
|
+
workspace: str = typer.Option(
|
|
34
|
+
None, "--workspace", "-w", help="Workspace ID (default: IBEE_WORKSPACE_ID env)"
|
|
35
|
+
),
|
|
36
|
+
dev: bool = typer.Option(False, "--dev", help="Use the development environment"),
|
|
37
|
+
base_url: str = typer.Option(None, "--base-url", help="Override the API base URL"),
|
|
38
|
+
as_json: bool = typer.Option(False, "--json", help="Output raw JSON"),
|
|
39
|
+
version: bool = typer.Option(
|
|
40
|
+
None, "--version", callback=_version_callback, is_eager=True, help="Show version"
|
|
41
|
+
),
|
|
42
|
+
) -> None:
|
|
43
|
+
ctx.obj = settings_from_env(token, workspace, dev, base_url, as_json)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
app()
|
ibee_cli/render.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Output rendering: rich tables by default, raw JSON with --json."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any, Callable, Iterable, Sequence
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from ibee.core.api_error import ApiError
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def print_json(payload: Any) -> None:
|
|
18
|
+
if hasattr(payload, "dict"):
|
|
19
|
+
payload = payload.dict()
|
|
20
|
+
console.print_json(json.dumps(payload, default=str))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def print_table(title: str, columns: Sequence[str], rows: Iterable[Sequence[Any]]) -> None:
|
|
24
|
+
table = Table(title=title, title_justify="left")
|
|
25
|
+
for col in columns:
|
|
26
|
+
table.add_column(col)
|
|
27
|
+
count = 0
|
|
28
|
+
for row in rows:
|
|
29
|
+
table.add_row(*[("-" if v is None else str(v)) for v in row])
|
|
30
|
+
count += 1
|
|
31
|
+
if count == 0:
|
|
32
|
+
console.print(f"{title}: none found")
|
|
33
|
+
return
|
|
34
|
+
console.print(table)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def handle_api_errors(fn: Callable) -> Callable:
|
|
38
|
+
"""Convert ApiError / connection failures into clean CLI errors."""
|
|
39
|
+
|
|
40
|
+
@functools.wraps(fn)
|
|
41
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
42
|
+
try:
|
|
43
|
+
return fn(*args, **kwargs)
|
|
44
|
+
except ApiError as exc:
|
|
45
|
+
if exc.status_code == 401:
|
|
46
|
+
msg = "Unauthorized (401): the API token is invalid or revoked."
|
|
47
|
+
elif exc.status_code == 403:
|
|
48
|
+
msg = f"Forbidden (403): the token is missing a required scope. body={exc.body!r}"
|
|
49
|
+
elif exc.status_code == 404:
|
|
50
|
+
msg = (
|
|
51
|
+
"Not found (404): this API route is not enabled on the gateway yet "
|
|
52
|
+
"(compute routes are rolling out) or the resource does not exist."
|
|
53
|
+
)
|
|
54
|
+
else:
|
|
55
|
+
msg = f"API error {exc.status_code}: {exc.body!r}"
|
|
56
|
+
typer.secho(msg, fg=typer.colors.RED, err=True)
|
|
57
|
+
raise typer.Exit(code=1)
|
|
58
|
+
except Exception as exc: # httpx connection errors etc.
|
|
59
|
+
if type(exc).__module__.startswith("httpx"):
|
|
60
|
+
typer.secho(f"Connection error: {exc}", fg=typer.colors.RED, err=True)
|
|
61
|
+
raise typer.Exit(code=1)
|
|
62
|
+
raise
|
|
63
|
+
|
|
64
|
+
return wrapper
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ibee-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official command-line interface for the IBEE Solutions cloud platform
|
|
5
|
+
Author-email: IBEE Solutions <support@ibee.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://ibee.ai
|
|
8
|
+
Project-URL: Documentation, https://ibee.ai/docs/api-reference
|
|
9
|
+
Project-URL: Repository, https://github.com/devs-ibee/ibee-cli
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: ibee>=0.1.2
|
|
18
|
+
Requires-Dist: typer>=0.12
|
|
19
|
+
Requires-Dist: rich>=13.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
22
|
+
Requires-Dist: build; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# IBEE Solutions CLI
|
|
26
|
+
|
|
27
|
+
Official command-line interface for the IBEE Solutions cloud platform. Built on the [`ibee` Python SDK](https://github.com/devs-ibee/ibee-python).
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install ibee-cli
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Python 3.10+.
|
|
36
|
+
|
|
37
|
+
## Authentication
|
|
38
|
+
|
|
39
|
+
Create an API token in the portal under **Settings > API Tokens**, then:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
export IBEE_TOKEN="ibee_live_xxxxxxxxxxxx"
|
|
43
|
+
export IBEE_WORKSPACE_ID="710995"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Both can also be passed per command with `--token` and `--workspace`.
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Object storage
|
|
52
|
+
ibee buckets list
|
|
53
|
+
ibee buckets create my-bucket
|
|
54
|
+
ibee buckets delete my-bucket --yes
|
|
55
|
+
|
|
56
|
+
# Secret Store
|
|
57
|
+
ibee secrets stores
|
|
58
|
+
ibee secrets list --store-id STORE_ID
|
|
59
|
+
ibee secrets value SECRET_ID
|
|
60
|
+
|
|
61
|
+
# Cloud VMs
|
|
62
|
+
ibee vms list
|
|
63
|
+
ibee vms get VM_ID
|
|
64
|
+
ibee vms start VM_ID
|
|
65
|
+
ibee vms stop VM_ID
|
|
66
|
+
ibee vms reboot VM_ID
|
|
67
|
+
|
|
68
|
+
# GPU VMs
|
|
69
|
+
ibee gpus list
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Every command accepts `--json` for raw output:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
ibee --json buckets list
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Environments
|
|
79
|
+
|
|
80
|
+
The CLI targets production by default. Use `--dev` (or `IBEE_ENV=dev`) for the development environment, or `--base-url` for a custom endpoint:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
ibee --dev buckets list
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Related
|
|
87
|
+
|
|
88
|
+
- [Python SDK](https://github.com/devs-ibee/ibee-python) — `pip install ibee`
|
|
89
|
+
- [API documentation](https://ibee.ai/docs/api-reference)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ibee_cli/__init__.py,sha256=yR6Elo-jC1yPR2Wh0amOleuJW7sReozjpN5U-YW2KYI,68
|
|
2
|
+
ibee_cli/context.py,sha256=hodAfKSq1jyplfMDXOqW4ni8HpOLVIYxcx24ysOYKS4,1879
|
|
3
|
+
ibee_cli/main.py,sha256=TxU-ZLJjhpRyvLZaJ9Aqd8U9eQIGvhn4UzFdBYw2u4E,1431
|
|
4
|
+
ibee_cli/render.py,sha256=m7rlE_Bi0bHfK5QZ4e5Y6qiYGh1OAcJNBYlJHx9rTP8,2193
|
|
5
|
+
ibee_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
ibee_cli/commands/buckets.py,sha256=ZwtUhB8mdbsKhk_5ZUq4UJ-hnvVh8xn7wq-1UagmQGg,2571
|
|
7
|
+
ibee_cli/commands/gpus.py,sha256=9wCDDtXhBcC_gw5slQj04ENL2e1JfwaQfyNKXD1lc3g,1283
|
|
8
|
+
ibee_cli/commands/secrets.py,sha256=JNBk8coxbCHbLuZfBSnzWkBuHa8akGPiqVOtoqXqFLI,2179
|
|
9
|
+
ibee_cli/commands/vms.py,sha256=M3e9_8jOeu8ZQdYt2d7kvTT9C2I_H5cmnjtYIEoB3EM,2545
|
|
10
|
+
ibee_cli-0.1.0.dist-info/licenses/LICENSE,sha256=X_PXeOEmTSLBRI0ICuQ_YSyl8mb9HZ5OcrX7315cf84,1071
|
|
11
|
+
ibee_cli-0.1.0.dist-info/METADATA,sha256=sHdwg9I3GAjAo8f1UQ7c6xMAtXMWUgG00tYnjqgfAFg,2121
|
|
12
|
+
ibee_cli-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
13
|
+
ibee_cli-0.1.0.dist-info/entry_points.txt,sha256=WyBnzgSCyIxebC5IIAZkRAcFo4RbXz3dB3V7sJbDaIg,43
|
|
14
|
+
ibee_cli-0.1.0.dist-info/top_level.txt,sha256=ciWcLaIHTM2nXVH8-QqyYH9avexVL8lPocaTECmMNW0,9
|
|
15
|
+
ibee_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 IBEE Solutions
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ibee_cli
|