wayscloud-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 (34) hide show
  1. wayscloud_cli-0.2.0/PKG-INFO +149 -0
  2. wayscloud_cli-0.2.0/README.md +123 -0
  3. wayscloud_cli-0.2.0/pyproject.toml +42 -0
  4. {wayscloud_cli-0.1.0 → wayscloud_cli-0.2.0}/setup.cfg +5 -1
  5. wayscloud_cli-0.2.0/tests/test_smoke.py +146 -0
  6. wayscloud_cli-0.2.0/wayscloud_cli/__init__.py +1 -0
  7. {wayscloud_cli-0.1.0 → wayscloud_cli-0.2.0}/wayscloud_cli/__main__.py +20 -6
  8. wayscloud_cli-0.2.0/wayscloud_cli/commands/__init__.py +0 -0
  9. wayscloud_cli-0.2.0/wayscloud_cli/commands/app.py +136 -0
  10. wayscloud_cli-0.2.0/wayscloud_cli/commands/db.py +103 -0
  11. wayscloud_cli-0.2.0/wayscloud_cli/commands/dns.py +171 -0
  12. wayscloud_cli-0.2.0/wayscloud_cli/commands/iot.py +165 -0
  13. {wayscloud_cli-0.1.0 → wayscloud_cli-0.2.0}/wayscloud_cli/commands/login.py +25 -35
  14. wayscloud_cli-0.2.0/wayscloud_cli/commands/redis.py +127 -0
  15. wayscloud_cli-0.2.0/wayscloud_cli/commands/storage.py +87 -0
  16. wayscloud_cli-0.2.0/wayscloud_cli/commands/vps.py +508 -0
  17. wayscloud_cli-0.2.0/wayscloud_cli/sdk.py +52 -0
  18. wayscloud_cli-0.2.0/wayscloud_cli.egg-info/PKG-INFO +149 -0
  19. {wayscloud_cli-0.1.0 → wayscloud_cli-0.2.0}/wayscloud_cli.egg-info/SOURCES.txt +9 -1
  20. {wayscloud_cli-0.1.0 → wayscloud_cli-0.2.0}/wayscloud_cli.egg-info/requires.txt +1 -1
  21. wayscloud_cli-0.1.0/PKG-INFO +0 -39
  22. wayscloud_cli-0.1.0/README.md +0 -23
  23. wayscloud_cli-0.1.0/pyproject.toml +0 -28
  24. wayscloud_cli-0.1.0/wayscloud_cli/__init__.py +0 -2
  25. wayscloud_cli-0.1.0/wayscloud_cli/client.py +0 -94
  26. wayscloud_cli-0.1.0/wayscloud_cli/commands/vps.py +0 -194
  27. wayscloud_cli-0.1.0/wayscloud_cli.egg-info/PKG-INFO +0 -39
  28. {wayscloud_cli-0.1.0/wayscloud_cli/commands → wayscloud_cli-0.2.0/tests}/__init__.py +0 -0
  29. {wayscloud_cli-0.1.0 → wayscloud_cli-0.2.0}/wayscloud_cli/commands/shell.py +0 -0
  30. {wayscloud_cli-0.1.0 → wayscloud_cli-0.2.0}/wayscloud_cli/config.py +0 -0
  31. {wayscloud_cli-0.1.0 → wayscloud_cli-0.2.0}/wayscloud_cli/output.py +0 -0
  32. {wayscloud_cli-0.1.0 → wayscloud_cli-0.2.0}/wayscloud_cli.egg-info/dependency_links.txt +0 -0
  33. {wayscloud_cli-0.1.0 → wayscloud_cli-0.2.0}/wayscloud_cli.egg-info/entry_points.txt +0 -0
  34. {wayscloud_cli-0.1.0 → wayscloud_cli-0.2.0}/wayscloud_cli.egg-info/top_level.txt +0 -0
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.4
2
+ Name: wayscloud-cli
3
+ Version: 0.2.0
4
+ Summary: Command-line interface for WAYSCloud — manage VPS, DNS, databases, storage, apps, IoT, and more
5
+ License: MIT
6
+ Project-URL: Homepage, https://wayscloud.services
7
+ Project-URL: Documentation, https://docs.wayscloud.services/integrations/cli
8
+ Project-URL: Repository, https://github.com/wayscloudas/wayscloud-provision-api
9
+ Project-URL: Issues, https://github.com/wayscloudas/wayscloud-provision-api/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Operating System :: OS Independent
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: wayscloud>=0.1.0
23
+ Requires-Dist: typer>=0.9
24
+ Requires-Dist: rich>=13.0
25
+ Requires-Dist: websockets>=12.0
26
+
27
+ # WAYSCloud CLI
28
+
29
+ Command-line interface for [WAYSCloud](https://wayscloud.services). Built on the [Python SDK](https://pypi.org/project/wayscloud/).
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ pip install wayscloud-cli
35
+ ```
36
+
37
+ ## Authentication
38
+
39
+ ```bash
40
+ cloud login --token wayscloud_pat_...
41
+ cloud whoami
42
+ cloud logout
43
+ ```
44
+
45
+ Priority: `--token` flag > `WAYSCLOUD_TOKEN` env var > `~/.wayscloud/credentials` file.
46
+
47
+ ## Commands
48
+
49
+ ### VPS
50
+
51
+ ```bash
52
+ cloud vps list
53
+ cloud vps create --hostname web01 --plan vps-medium --region no --os ubuntu-24.04
54
+ cloud vps info <id>
55
+ cloud vps delete <id> --confirm
56
+ cloud vps start <id>
57
+ cloud vps stop <id>
58
+ cloud vps plans
59
+ cloud vps os-templates
60
+ ```
61
+
62
+ ### DNS
63
+
64
+ ```bash
65
+ cloud dns zones
66
+ cloud dns zones-create example.com
67
+ cloud dns records example.com
68
+ cloud dns records-create example.com --type A --name www --value 192.0.2.1
69
+ cloud dns records-delete example.com <record-id> --confirm
70
+ ```
71
+
72
+ ### Database
73
+
74
+ ```bash
75
+ cloud db list
76
+ cloud db create mydb --type postgresql --tier standard
77
+ cloud db info postgresql mydb
78
+ cloud db delete postgresql mydb --confirm
79
+ ```
80
+
81
+ ### Redis
82
+
83
+ ```bash
84
+ cloud redis list
85
+ cloud redis create myredis --plan redis-starter --region no
86
+ cloud redis info <id>
87
+ cloud redis delete <id> --confirm
88
+ cloud redis plans
89
+ ```
90
+
91
+ ### Storage
92
+
93
+ ```bash
94
+ cloud storage buckets
95
+ cloud storage buckets-create my-bucket
96
+ cloud storage buckets-delete my-bucket --confirm
97
+ cloud storage credentials
98
+ ```
99
+
100
+ ### Apps
101
+
102
+ ```bash
103
+ cloud app list
104
+ cloud app create my-app --plan app-basic --region eu
105
+ cloud app deploy <id> --image ghcr.io/org/app:latest
106
+ cloud app start <id>
107
+ cloud app stop <id>
108
+ cloud app delete <id> --confirm
109
+ ```
110
+
111
+ ### IoT
112
+
113
+ ```bash
114
+ cloud iot devices
115
+ cloud iot devices-create --device-id sensor-01 --name "Temperature Sensor"
116
+ cloud iot devices-info <device-id>
117
+ cloud iot devices-delete <device-id> --confirm
118
+ cloud iot groups
119
+ cloud iot groups-create --name "Floor 2"
120
+ ```
121
+
122
+ ### Shell
123
+
124
+ ```bash
125
+ cloud shell connect
126
+ ```
127
+
128
+ ## Output formats
129
+
130
+ ```bash
131
+ # Default: formatted tables
132
+ cloud vps list
133
+
134
+ # JSON (for scripting)
135
+ cloud vps list --json
136
+ ```
137
+
138
+ ## Requirements
139
+
140
+ - Python 3.9+
141
+ - wayscloud (SDK)
142
+
143
+ ## Documentation
144
+
145
+ Full reference: [docs.wayscloud.services/cli](https://docs.wayscloud.services/cli)
146
+
147
+ ## License
148
+
149
+ MIT
@@ -0,0 +1,123 @@
1
+ # WAYSCloud CLI
2
+
3
+ Command-line interface for [WAYSCloud](https://wayscloud.services). Built on the [Python SDK](https://pypi.org/project/wayscloud/).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install wayscloud-cli
9
+ ```
10
+
11
+ ## Authentication
12
+
13
+ ```bash
14
+ cloud login --token wayscloud_pat_...
15
+ cloud whoami
16
+ cloud logout
17
+ ```
18
+
19
+ Priority: `--token` flag > `WAYSCLOUD_TOKEN` env var > `~/.wayscloud/credentials` file.
20
+
21
+ ## Commands
22
+
23
+ ### VPS
24
+
25
+ ```bash
26
+ cloud vps list
27
+ cloud vps create --hostname web01 --plan vps-medium --region no --os ubuntu-24.04
28
+ cloud vps info <id>
29
+ cloud vps delete <id> --confirm
30
+ cloud vps start <id>
31
+ cloud vps stop <id>
32
+ cloud vps plans
33
+ cloud vps os-templates
34
+ ```
35
+
36
+ ### DNS
37
+
38
+ ```bash
39
+ cloud dns zones
40
+ cloud dns zones-create example.com
41
+ cloud dns records example.com
42
+ cloud dns records-create example.com --type A --name www --value 192.0.2.1
43
+ cloud dns records-delete example.com <record-id> --confirm
44
+ ```
45
+
46
+ ### Database
47
+
48
+ ```bash
49
+ cloud db list
50
+ cloud db create mydb --type postgresql --tier standard
51
+ cloud db info postgresql mydb
52
+ cloud db delete postgresql mydb --confirm
53
+ ```
54
+
55
+ ### Redis
56
+
57
+ ```bash
58
+ cloud redis list
59
+ cloud redis create myredis --plan redis-starter --region no
60
+ cloud redis info <id>
61
+ cloud redis delete <id> --confirm
62
+ cloud redis plans
63
+ ```
64
+
65
+ ### Storage
66
+
67
+ ```bash
68
+ cloud storage buckets
69
+ cloud storage buckets-create my-bucket
70
+ cloud storage buckets-delete my-bucket --confirm
71
+ cloud storage credentials
72
+ ```
73
+
74
+ ### Apps
75
+
76
+ ```bash
77
+ cloud app list
78
+ cloud app create my-app --plan app-basic --region eu
79
+ cloud app deploy <id> --image ghcr.io/org/app:latest
80
+ cloud app start <id>
81
+ cloud app stop <id>
82
+ cloud app delete <id> --confirm
83
+ ```
84
+
85
+ ### IoT
86
+
87
+ ```bash
88
+ cloud iot devices
89
+ cloud iot devices-create --device-id sensor-01 --name "Temperature Sensor"
90
+ cloud iot devices-info <device-id>
91
+ cloud iot devices-delete <device-id> --confirm
92
+ cloud iot groups
93
+ cloud iot groups-create --name "Floor 2"
94
+ ```
95
+
96
+ ### Shell
97
+
98
+ ```bash
99
+ cloud shell connect
100
+ ```
101
+
102
+ ## Output formats
103
+
104
+ ```bash
105
+ # Default: formatted tables
106
+ cloud vps list
107
+
108
+ # JSON (for scripting)
109
+ cloud vps list --json
110
+ ```
111
+
112
+ ## Requirements
113
+
114
+ - Python 3.9+
115
+ - wayscloud (SDK)
116
+
117
+ ## Documentation
118
+
119
+ Full reference: [docs.wayscloud.services/cli](https://docs.wayscloud.services/cli)
120
+
121
+ ## License
122
+
123
+ MIT
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "wayscloud-cli"
7
+ version = "0.2.0"
8
+ description = "Command-line interface for WAYSCloud — manage VPS, DNS, databases, storage, apps, IoT, and more"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = {text = "MIT"}
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Intended Audience :: Developers",
15
+ "Intended Audience :: System Administrators",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Operating System :: OS Independent",
23
+ ]
24
+ dependencies = [
25
+ "wayscloud>=0.1.0",
26
+ "typer>=0.9",
27
+ "rich>=13.0",
28
+ "websockets>=12.0",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://wayscloud.services"
33
+ Documentation = "https://docs.wayscloud.services/integrations/cli"
34
+ Repository = "https://github.com/wayscloudas/wayscloud-provision-api"
35
+ Issues = "https://github.com/wayscloudas/wayscloud-provision-api/issues"
36
+
37
+ [tool.setuptools.packages.find]
38
+ include = ["wayscloud_cli*"]
39
+ exclude = ["tests*"]
40
+
41
+ [project.scripts]
42
+ cloud = "wayscloud_cli.__main__:main"
@@ -1,10 +1,14 @@
1
1
  [metadata]
2
2
  name = wayscloud-cli
3
- version = 0.1.0
3
+ version = 0.2.0
4
4
 
5
5
  [options]
6
6
  packages = find:
7
7
 
8
+ [options.entry_points]
9
+ console_scripts =
10
+ cloud = wayscloud_cli.__main__:main
11
+
8
12
  [egg_info]
9
13
  tag_build =
10
14
  tag_date = 0
@@ -0,0 +1,146 @@
1
+ """CLI smoke tests — verify app structure, command registration, flags, and token resolution."""
2
+
3
+ import json
4
+ import os
5
+ from unittest.mock import patch
6
+
7
+ from typer.testing import CliRunner
8
+
9
+ from wayscloud_cli.__main__ import app
10
+ from wayscloud_cli import __version__
11
+ from wayscloud_cli.config import resolve_token
12
+ from wayscloud_cli.output import set_json_mode, is_json_mode
13
+
14
+ runner = CliRunner()
15
+
16
+
17
+ # ── App structure ───────────────────────────────────────────────
18
+
19
+ def test_app_has_help():
20
+ result = runner.invoke(app, ["--help"])
21
+ assert result.exit_code == 0
22
+ assert "WAYSCloud CLI" in result.output
23
+
24
+
25
+ def test_version_flag():
26
+ result = runner.invoke(app, ["--version"])
27
+ assert result.exit_code == 0
28
+ assert __version__ in result.output
29
+
30
+
31
+ # ── Command groups registered ───────────────────────────────────
32
+
33
+ EXPECTED_GROUPS = ["auth", "vps", "dns", "storage", "db", "redis", "app", "iot", "shell"]
34
+
35
+
36
+ def test_all_command_groups_registered():
37
+ result = runner.invoke(app, ["--help"])
38
+ for group in EXPECTED_GROUPS:
39
+ assert group in result.output, f"Command group '{group}' not in help output"
40
+
41
+
42
+ def test_each_group_has_help():
43
+ for group in EXPECTED_GROUPS:
44
+ result = runner.invoke(app, [group, "--help"])
45
+ assert result.exit_code == 0, f"{group} --help failed with exit {result.exit_code}"
46
+
47
+
48
+ # ── Top-level shortcuts ─────────────────────────────────────────
49
+
50
+ def test_login_shortcut_exists():
51
+ result = runner.invoke(app, ["login", "--help"])
52
+ assert result.exit_code == 0
53
+ assert "token" in result.output.lower()
54
+
55
+
56
+ def test_whoami_shortcut_exists():
57
+ result = runner.invoke(app, ["whoami", "--help"])
58
+ assert result.exit_code == 0
59
+
60
+
61
+ def test_logout_shortcut_exists():
62
+ result = runner.invoke(app, ["logout", "--help"])
63
+ assert result.exit_code == 0
64
+
65
+
66
+ # ── Token resolution ────────────────────────────────────────────
67
+
68
+ def test_explicit_token_wins(monkeypatch):
69
+ monkeypatch.setenv("WAYSCLOUD_TOKEN", "env_token")
70
+ assert resolve_token("explicit_token") == "explicit_token"
71
+
72
+
73
+ def test_env_var_used_when_no_explicit(monkeypatch):
74
+ monkeypatch.setenv("WAYSCLOUD_TOKEN", "env_token")
75
+ assert resolve_token(None) == "env_token"
76
+
77
+
78
+ def test_returns_none_when_nothing(monkeypatch, tmp_path):
79
+ monkeypatch.delenv("WAYSCLOUD_TOKEN", raising=False)
80
+ # Point credentials file to nonexistent path
81
+ monkeypatch.setattr("wayscloud_cli.config.CREDENTIALS_FILE", tmp_path / "nope")
82
+ assert resolve_token(None) is None
83
+
84
+
85
+ def test_reads_credentials_file(monkeypatch, tmp_path):
86
+ monkeypatch.delenv("WAYSCLOUD_TOKEN", raising=False)
87
+ cred_file = tmp_path / "credentials"
88
+ cred_file.write_text(json.dumps({"version": 1, "token": "file_token"}))
89
+ monkeypatch.setattr("wayscloud_cli.config.CREDENTIALS_FILE", cred_file)
90
+ assert resolve_token(None) == "file_token"
91
+
92
+
93
+ # ── Output modes ────────────────────────────────────────────────
94
+
95
+ def test_json_mode_toggle():
96
+ set_json_mode(False)
97
+ assert not is_json_mode()
98
+ set_json_mode(True)
99
+ assert is_json_mode()
100
+ set_json_mode(False)
101
+
102
+
103
+ def test_json_flag_accepted():
104
+ result = runner.invoke(app, ["--json", "--help"])
105
+ assert result.exit_code == 0
106
+
107
+
108
+ # ── VPS commands exist with correct args ────────────────────────
109
+
110
+ def test_vps_list_help():
111
+ result = runner.invoke(app, ["vps", "list", "--help"])
112
+ assert result.exit_code == 0
113
+ assert "--token" in result.output
114
+
115
+
116
+ def test_vps_create_requires_flags():
117
+ result = runner.invoke(app, ["vps", "create", "--help"])
118
+ assert result.exit_code == 0
119
+ for flag in ["--hostname", "--plan", "--region", "--os"]:
120
+ assert flag in result.output, f"Missing {flag} in vps create"
121
+
122
+
123
+ # ── DNS commands ────────────────────────────────────────────────
124
+
125
+ def test_dns_records_create_has_type_flag():
126
+ result = runner.invoke(app, ["dns", "records-create", "--help"])
127
+ assert result.exit_code == 0
128
+ assert "--type" in result.output
129
+ assert "--value" in result.output
130
+
131
+
132
+ # ── DB commands ─────────────────────────────────────────────────
133
+
134
+ def test_db_create_has_type_flag():
135
+ result = runner.invoke(app, ["db", "create", "--help"])
136
+ assert result.exit_code == 0
137
+ assert "--type" in result.output
138
+
139
+
140
+ # ── IoT commands ────────────────────────────────────────────────
141
+
142
+ def test_iot_devices_create_has_required_flags():
143
+ result = runner.invoke(app, ["iot", "devices-create", "--help"])
144
+ assert result.exit_code == 0
145
+ assert "--device-id" in result.output
146
+ assert "--name" in result.output
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
@@ -3,15 +3,23 @@ WAYSCloud CLI entry point.
3
3
 
4
4
  Binary name: cloud (C21)
5
5
  Package name: wayscloud-cli (C21)
6
+
7
+ Principle: CLI = provisioning + control, NOT observability.
6
8
  """
7
9
 
8
10
  import typer
9
11
 
10
12
  from . import __version__
11
13
  from .output import set_json_mode, set_no_color
12
- from .commands.login import app as login_app
14
+ from .commands.login import app as login_app, login, logout, whoami
13
15
  from .commands.vps import app as vps_app
14
16
  from .commands.shell import app as shell_app
17
+ from .commands.dns import app as dns_app
18
+ from .commands.storage import app as storage_app
19
+ from .commands.db import app as db_app
20
+ from .commands.redis import app as redis_app
21
+ from .commands.app import app as app_platform_app
22
+ from .commands.iot import app as iot_app
15
23
 
16
24
  app = typer.Typer(
17
25
  name="cloud",
@@ -43,14 +51,20 @@ def global_options(
43
51
 
44
52
 
45
53
  # Register command groups
46
- app.add_typer(login_app, name="auth", help="Login, logout, whoami")
54
+ app.add_typer(login_app, name="auth", help="Authentication")
47
55
  app.add_typer(vps_app, name="vps", help="Virtual Private Servers")
48
- app.add_typer(shell_app, name="shell", help="Interactive CloudShell")
56
+ app.add_typer(dns_app, name="dns", help="DNS zones and records")
57
+ app.add_typer(storage_app, name="storage", help="Object storage (S3)")
58
+ app.add_typer(db_app, name="db", help="Managed databases")
59
+ app.add_typer(redis_app, name="redis", help="Managed Redis")
60
+ app.add_typer(app_platform_app, name="app", help="App platform")
61
+ app.add_typer(iot_app, name="iot", help="IoT platform")
62
+ app.add_typer(shell_app, name="shell", help="CloudShell")
49
63
 
50
64
  # Top-level shortcuts for login/logout/whoami
51
- app.command("login")(login_app.registered_commands[0].callback)
52
- app.command("logout")(login_app.registered_commands[1].callback)
53
- app.command("whoami")(login_app.registered_commands[2].callback)
65
+ app.command("login")(login)
66
+ app.command("logout")(logout)
67
+ app.command("whoami")(whoami)
54
68
 
55
69
 
56
70
  def main():
File without changes
@@ -0,0 +1,136 @@
1
+ """
2
+ cloud app commands — App Platform provisioning and control.
3
+ """
4
+
5
+ import typer
6
+ from typing import Optional
7
+
8
+ from ..sdk import get_client, sdk_call
9
+ from ..output import print_table, print_object, print_json, is_json_mode
10
+
11
+ app = typer.Typer(help="App platform")
12
+
13
+
14
+ @app.command("list")
15
+ def list_apps(
16
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
17
+ ):
18
+ """List your apps."""
19
+ c = get_client(token)
20
+ data = sdk_call(c.apps.list)
21
+ apps_list = data if isinstance(data, list) else data.get("apps", []) if isinstance(data, dict) else []
22
+
23
+ if is_json_mode():
24
+ print_json(apps_list)
25
+ return
26
+
27
+ if not apps_list:
28
+ print("No apps found.")
29
+ return
30
+
31
+ rows = [{"id": str(a.get("id", ""))[:14], "name": a.get("name", ""),
32
+ "status": a.get("status", ""), "plan": a.get("plan", a.get("plan_code", "")),
33
+ "url": a.get("default_url", ""), "created_at": a.get("created_at", "")}
34
+ for a in apps_list]
35
+
36
+ print_table(rows, [
37
+ ("id", "ID", 14), ("name", "Name", 20), ("status", "Status", 10),
38
+ ("plan", "Plan", 16), ("url", "URL", 30), ("created_at", "Created", 20),
39
+ ])
40
+
41
+
42
+ @app.command("create")
43
+ def create_app(
44
+ name: str = typer.Argument(..., help="App name"),
45
+ plan: str = typer.Option("app-basic", "--plan", help="Plan code"),
46
+ region: str = typer.Option("no", "--region", help="Region"),
47
+ port: int = typer.Option(8080, "--port", help="Application port"),
48
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
49
+ ):
50
+ """Create a new app."""
51
+ c = get_client(token)
52
+ data = sdk_call(c.apps.create, name, plan=plan, region=region, port=port)
53
+ if is_json_mode():
54
+ print_json(data)
55
+ else:
56
+ print(f"App created: {data.get('name', name)}")
57
+ print_object(data, [("id", "ID"), ("name", "Name"), ("status", "Status"),
58
+ ("plan", "Plan"), ("default_url", "URL")])
59
+
60
+
61
+ @app.command("info")
62
+ def info_app(
63
+ app_id: str = typer.Argument(..., help="App ID"),
64
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
65
+ ):
66
+ """Show app details."""
67
+ c = get_client(token)
68
+ data = sdk_call(c.apps.get, app_id)
69
+ print_object(data, [
70
+ ("id", "ID"), ("name", "Name"), ("status", "Status"), ("plan", "Plan"),
71
+ ("region", "Region"), ("port", "Port"), ("default_url", "URL"),
72
+ ("image_uri", "Image"), ("created_at", "Created"),
73
+ ])
74
+
75
+
76
+ @app.command("delete")
77
+ def delete_app(
78
+ app_id: str = typer.Argument(..., help="App ID"),
79
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
80
+ confirm: bool = typer.Option(False, "--confirm", help="Skip confirmation"),
81
+ ):
82
+ """Delete an app (permanent)."""
83
+ if not confirm:
84
+ print(f"This will permanently delete app {app_id} and all its data.")
85
+ print("Use --confirm to proceed.")
86
+ raise typer.Exit(code=1)
87
+
88
+ c = get_client(token)
89
+ data = sdk_call(c.apps.delete, app_id)
90
+ if is_json_mode():
91
+ print_json(data)
92
+ else:
93
+ print(f"App {app_id}: deleted")
94
+
95
+
96
+ @app.command("deploy")
97
+ def deploy_app(
98
+ app_id: str = typer.Argument(..., help="App ID"),
99
+ image: str = typer.Option(..., "--image", help="Container image URI"),
100
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
101
+ ):
102
+ """Deploy an app from a container image."""
103
+ c = get_client(token)
104
+ data = sdk_call(c.apps.deploy, app_id, image)
105
+ if is_json_mode():
106
+ print_json(data)
107
+ else:
108
+ print(f"App {app_id}: deploying image {image}")
109
+
110
+
111
+ @app.command("start")
112
+ def start_app(
113
+ app_id: str = typer.Argument(..., help="App ID"),
114
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
115
+ ):
116
+ """Start an app."""
117
+ c = get_client(token)
118
+ data = sdk_call(c.apps.start, app_id)
119
+ if is_json_mode():
120
+ print_json(data)
121
+ else:
122
+ print(f"App {app_id}: {data.get('message', 'starting')}")
123
+
124
+
125
+ @app.command("stop")
126
+ def stop_app(
127
+ app_id: str = typer.Argument(..., help="App ID"),
128
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
129
+ ):
130
+ """Stop an app."""
131
+ c = get_client(token)
132
+ data = sdk_call(c.apps.stop, app_id)
133
+ if is_json_mode():
134
+ print_json(data)
135
+ else:
136
+ print(f"App {app_id}: {data.get('message', 'stopping')}")