wayscloud-cli 0.2.3__tar.gz → 0.3.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 (29) hide show
  1. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/PKG-INFO +1 -3
  2. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/pyproject.toml +1 -1
  3. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/setup.cfg +1 -1
  4. wayscloud_cli-0.3.0/wayscloud_cli/__init__.py +1 -0
  5. wayscloud_cli-0.3.0/wayscloud_cli/commands/app.py +295 -0
  6. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/commands/db.py +1 -11
  7. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/commands/iot.py +1 -12
  8. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/commands/redis.py +0 -9
  9. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/commands/storage.py +0 -11
  10. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli.egg-info/PKG-INFO +1 -3
  11. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli.egg-info/SOURCES.txt +0 -1
  12. wayscloud_cli-0.2.3/LICENSE +0 -21
  13. wayscloud_cli-0.2.3/wayscloud_cli/__init__.py +0 -1
  14. wayscloud_cli-0.2.3/wayscloud_cli/commands/app.py +0 -136
  15. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/README.md +0 -0
  16. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/tests/test_smoke.py +0 -0
  17. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/__main__.py +0 -0
  18. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/commands/__init__.py +0 -0
  19. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/commands/dns.py +0 -0
  20. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/commands/login.py +0 -0
  21. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/commands/shell.py +0 -0
  22. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/commands/vps.py +0 -0
  23. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/config.py +0 -0
  24. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/output.py +0 -0
  25. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli/sdk.py +0 -0
  26. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli.egg-info/dependency_links.txt +0 -0
  27. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli.egg-info/entry_points.txt +0 -0
  28. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli.egg-info/requires.txt +0 -0
  29. {wayscloud_cli-0.2.3 → wayscloud_cli-0.3.0}/wayscloud_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wayscloud-cli
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: Command-line interface for WAYSCloud — manage VPS, DNS, databases, storage, apps, IoT, and more
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://wayscloud.services
@@ -19,12 +19,10 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Operating System :: OS Independent
20
20
  Requires-Python: >=3.9
21
21
  Description-Content-Type: text/markdown
22
- License-File: LICENSE
23
22
  Requires-Dist: wayscloud>=0.1.0
24
23
  Requires-Dist: typer>=0.9
25
24
  Requires-Dist: rich>=13.0
26
25
  Requires-Dist: websockets>=12.0
27
- Dynamic: license-file
28
26
 
29
27
  # WAYSCloud CLI
30
28
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wayscloud-cli"
7
- version = "0.2.3"
7
+ version = "0.3.0"
8
8
  description = "Command-line interface for WAYSCloud — manage VPS, DNS, databases, storage, apps, IoT, and more"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = wayscloud-cli
3
- version = 0.2.3
3
+ version = 0.3.0
4
4
 
5
5
  [options]
6
6
  packages = find:
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -0,0 +1,295 @@
1
+ """
2
+ cloud app commands — App Platform provisioning and control.
3
+ """
4
+
5
+ import typer
6
+ from typing import Optional, List
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
+ # ── Env sub-group ────────────────────────────────────────────────
14
+ env_app = typer.Typer(help="Environment variables")
15
+ app.add_typer(env_app, name="env")
16
+
17
+
18
+ @env_app.command("list")
19
+ def env_list(
20
+ app_id: str = typer.Argument(..., help="App ID or name"),
21
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
22
+ ):
23
+ """List environment variable keys for an app."""
24
+ c = get_client(token)
25
+ env = sdk_call(c.apps.env_vars, app_id)
26
+
27
+ if is_json_mode():
28
+ print_json(env)
29
+ return
30
+
31
+ if not env:
32
+ print("No environment variables set.")
33
+ return
34
+
35
+ print(f"Environment variables ({len(env)}):\n")
36
+ for key in sorted(env.keys()):
37
+ print(f" {key}=***")
38
+
39
+
40
+ @env_app.command("set")
41
+ def env_set(
42
+ app_id: str = typer.Argument(..., help="App ID or name"),
43
+ pairs: List[str] = typer.Argument(..., help="KEY=value pairs"),
44
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
45
+ ):
46
+ """Set one or more environment variables."""
47
+ c = get_client(token)
48
+
49
+ for pair in pairs:
50
+ if "=" not in pair:
51
+ print(f"Error: Invalid format '{pair}'. Use KEY=value", err=True)
52
+ raise typer.Exit(code=1)
53
+ key, _, value = pair.partition("=")
54
+ sdk_call(c.apps.set_env, app_id, key.strip(), value)
55
+ print(f" Set: {key.strip()}")
56
+
57
+ print(f"\n{len(pairs)} variable(s) updated.")
58
+
59
+
60
+ @env_app.command("unset")
61
+ def env_unset(
62
+ app_id: str = typer.Argument(..., help="App ID or name"),
63
+ keys: List[str] = typer.Argument(..., help="Variable names to remove"),
64
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
65
+ ):
66
+ """Remove one or more environment variables."""
67
+ c = get_client(token)
68
+
69
+ for key in keys:
70
+ sdk_call(c.apps.unset_env, app_id, key)
71
+ print(f" Removed: {key}")
72
+
73
+ print(f"\n{len(keys)} variable(s) removed.")
74
+
75
+
76
+ # ── Core commands ────────────────────────────────────────────────
77
+
78
+ @app.command("list")
79
+ def list_apps(
80
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
81
+ ):
82
+ """List your apps."""
83
+ c = get_client(token)
84
+ data = sdk_call(c.apps.list)
85
+ apps_list = data if isinstance(data, list) else data.get("apps", []) if isinstance(data, dict) else []
86
+
87
+ if is_json_mode():
88
+ print_json(apps_list)
89
+ return
90
+
91
+ if not apps_list:
92
+ print("No apps found.")
93
+ return
94
+
95
+ rows = [{"id": str(a.get("id", ""))[:14], "name": a.get("name", ""),
96
+ "status": a.get("status", ""), "plan": a.get("plan", a.get("plan_code", "")),
97
+ "url": a.get("default_url", ""), "created_at": a.get("created_at", "")}
98
+ for a in apps_list]
99
+
100
+ print_table(rows, [
101
+ ("id", "ID", 14), ("name", "Name", 20), ("status", "Status", 10),
102
+ ("plan", "Plan", 16), ("url", "URL", 30), ("created_at", "Created", 20),
103
+ ])
104
+
105
+
106
+ @app.command("create")
107
+ def create_app(
108
+ name: str = typer.Argument(..., help="App name"),
109
+ plan: str = typer.Option("app-basic", "--plan", help="Plan code"),
110
+ region: str = typer.Option("no", "--region", help="Region"),
111
+ port: int = typer.Option(8080, "--port", help="Application port"),
112
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
113
+ ):
114
+ """Create a new app."""
115
+ c = get_client(token)
116
+ data = sdk_call(c.apps.create, name, plan=plan, region=region, port=port)
117
+ if is_json_mode():
118
+ print_json(data)
119
+ else:
120
+ print(f"App created: {data.get('name', name)}")
121
+ print_object(data, [("id", "ID"), ("name", "Name"), ("status", "Status"),
122
+ ("plan", "Plan"), ("default_url", "URL")])
123
+
124
+
125
+ @app.command("info")
126
+ def info_app(
127
+ app_id: str = typer.Argument(..., help="App ID"),
128
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
129
+ ):
130
+ """Show app details."""
131
+ c = get_client(token)
132
+ data = sdk_call(c.apps.get, app_id)
133
+ print_object(data, [
134
+ ("id", "ID"), ("name", "Name"), ("status", "Status"), ("plan", "Plan"),
135
+ ("region", "Region"), ("port", "Port"), ("default_url", "URL"),
136
+ ("image_uri", "Image"), ("created_at", "Created"),
137
+ ])
138
+
139
+
140
+ @app.command("delete")
141
+ def delete_app(
142
+ app_id: str = typer.Argument(..., help="App ID"),
143
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
144
+ confirm: bool = typer.Option(False, "--confirm", help="Skip confirmation"),
145
+ ):
146
+ """Delete an app (permanent)."""
147
+ if not confirm:
148
+ print(f"This will permanently delete app {app_id} and all its data.")
149
+ print("Use --confirm to proceed.")
150
+ raise typer.Exit(code=1)
151
+
152
+ c = get_client(token)
153
+ data = sdk_call(c.apps.delete, app_id)
154
+ if is_json_mode():
155
+ print_json(data)
156
+ else:
157
+ print(f"App {app_id}: deleted")
158
+
159
+
160
+ @app.command("deploy")
161
+ def deploy_app(
162
+ app_id: str = typer.Argument(..., help="App ID"),
163
+ image: str = typer.Option(..., "--image", help="Container image URI"),
164
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
165
+ ):
166
+ """Deploy an app from a container image."""
167
+ c = get_client(token)
168
+ data = sdk_call(c.apps.deploy, app_id, image)
169
+ if is_json_mode():
170
+ print_json(data)
171
+ else:
172
+ print(f"App {app_id}: deploying image {image}")
173
+
174
+
175
+ @app.command("start")
176
+ def start_app(
177
+ app_id: str = typer.Argument(..., help="App ID"),
178
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
179
+ ):
180
+ """Start an app."""
181
+ c = get_client(token)
182
+ data = sdk_call(c.apps.start, app_id)
183
+ if is_json_mode():
184
+ print_json(data)
185
+ else:
186
+ print(f"App {app_id}: {data.get('message', 'starting')}")
187
+
188
+
189
+ @app.command("stop")
190
+ def stop_app(
191
+ app_id: str = typer.Argument(..., help="App ID"),
192
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
193
+ ):
194
+ """Stop an app."""
195
+ c = get_client(token)
196
+ data = sdk_call(c.apps.stop, app_id)
197
+ if is_json_mode():
198
+ print_json(data)
199
+ else:
200
+ print(f"App {app_id}: {data.get('message', 'stopping')}")
201
+
202
+
203
+ @app.command("restart")
204
+ def restart_app(
205
+ app_id: str = typer.Argument(..., help="App ID"),
206
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
207
+ ):
208
+ """Restart an app."""
209
+ c = get_client(token)
210
+ data = sdk_call(c.apps.restart, app_id)
211
+ if is_json_mode():
212
+ print_json(data)
213
+ else:
214
+ print(f"App {app_id}: {data.get('message', 'restarting')}")
215
+
216
+
217
+ @app.command("logs")
218
+ def logs_app(
219
+ app_id: str = typer.Argument(..., help="App ID"),
220
+ lines: int = typer.Option(100, "--lines", "-n", help="Number of lines"),
221
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
222
+ ):
223
+ """Show app logs."""
224
+ c = get_client(token)
225
+ data = sdk_call(c.apps.logs, app_id, lines=lines)
226
+
227
+ if is_json_mode():
228
+ print_json(data)
229
+ return
230
+
231
+ log_lines = data if isinstance(data, list) else []
232
+ if not log_lines:
233
+ print("No logs available.")
234
+ return
235
+
236
+ for line in log_lines:
237
+ if isinstance(line, dict):
238
+ ts = line.get("timestamp", "")
239
+ msg = line.get("message", line.get("log", str(line)))
240
+ print(f"{ts} {msg}")
241
+ else:
242
+ print(line)
243
+
244
+
245
+ @app.command("plans")
246
+ def list_plans(
247
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
248
+ ):
249
+ """List available app plans."""
250
+ c = get_client(token)
251
+ data = sdk_call(c.apps.plans)
252
+ plans_list = data if isinstance(data, list) else []
253
+
254
+ if is_json_mode():
255
+ print_json(plans_list)
256
+ return
257
+
258
+ if not plans_list:
259
+ print("No plans available.")
260
+ return
261
+
262
+ rows = [{"code": p.get("code", p.get("plan_code", "")),
263
+ "name": p.get("name", ""),
264
+ "cpu": str(p.get("cpu", "")),
265
+ "memory": str(p.get("memory_mb", p.get("memory", ""))),
266
+ "price": str(p.get("price_monthly", p.get("price", "")))}
267
+ for p in plans_list]
268
+
269
+ print_table(rows, [
270
+ ("code", "Code", 20), ("name", "Name", 20), ("cpu", "CPU", 6),
271
+ ("memory", "Memory MB", 10), ("price", "Price/mo", 10),
272
+ ])
273
+
274
+
275
+ @app.command("regions")
276
+ def list_regions(
277
+ token: Optional[str] = typer.Option(None, "--token", help="Override token"),
278
+ ):
279
+ """List available app regions."""
280
+ c = get_client(token)
281
+ data = sdk_call(c.apps.regions)
282
+
283
+ if is_json_mode():
284
+ print_json(data)
285
+ return
286
+
287
+ regions_list = data if isinstance(data, list) else []
288
+ if not regions_list:
289
+ print("No regions available.")
290
+ return
291
+
292
+ for r in regions_list:
293
+ code = r.get("code", r.get("region", ""))
294
+ name = r.get("name", r.get("label", ""))
295
+ print(f" {code:<10} {name}")
@@ -65,20 +65,10 @@ def database_info(
65
65
  c = get_client(token)
66
66
  data = sdk_call(c.database.get, db_type, name)
67
67
 
68
- # Merge credentials if available
69
- try:
70
- creds = c.database.credentials(db_type, name)
71
- if isinstance(creds, dict):
72
- data.update(creds)
73
- except Exception as e:
74
- import sys
75
- print(f"Warning: could not fetch credentials: {e}", file=sys.stderr)
76
-
77
68
  print_object(data, [
78
69
  ("name", "Name"), ("type", "Type"), ("tier", "Tier"), ("status", "Status"),
79
70
  ("size_mb", "Size (MB)"), ("host", "Host"), ("port", "Port"),
80
- ("username", "Username"), ("password", "Password"), ("database", "Database"),
81
- ("connection_string", "Connection String"), ("created_at", "Created"),
71
+ ("created_at", "Created"),
82
72
  ])
83
73
 
84
74
 
@@ -65,21 +65,10 @@ def device_info(
65
65
  c = get_client(token)
66
66
  data = sdk_call(c.iot.get_device, device_id)
67
67
 
68
- # Merge credentials if available
69
- try:
70
- creds = c.iot.device_credentials(device_id)
71
- if isinstance(creds, dict):
72
- data.update(creds)
73
- except Exception as e:
74
- import sys
75
- print(f"Warning: could not fetch credentials: {e}", file=sys.stderr)
76
-
77
68
  print_object(data, [
78
69
  ("device_id", "Device ID"), ("name", "Name"), ("device_type", "Type"),
79
70
  ("is_active", "Active"), ("last_seen", "Last Seen"),
80
- ("username", "MQTT Username"), ("password", "MQTT Password"),
81
- ("client_id", "MQTT Client ID"), ("broker_url", "Broker URL"),
82
- ("port", "Broker Port"), ("created_at", "Created"),
71
+ ("created_at", "Created"),
83
72
  ])
84
73
 
85
74
 
@@ -63,15 +63,6 @@ def instance_info(
63
63
  c = get_client(token)
64
64
  data = sdk_call(c.redis.get, instance_id)
65
65
 
66
- # Merge credentials if available
67
- try:
68
- creds = c.redis.credentials(instance_id)
69
- if isinstance(creds, dict):
70
- data.update(creds)
71
- except Exception as e:
72
- import sys
73
- print(f"Warning: could not fetch credentials: {e}", file=sys.stderr)
74
-
75
66
  print_object(data, [
76
67
  ("id", "ID"), ("name", "Name"), ("plan", "Plan"), ("status", "Status"),
77
68
  ("endpoint", "Endpoint"), ("port", "Port"), ("region", "Region"),
@@ -74,14 +74,3 @@ def delete_bucket(
74
74
  print(f"Bucket {name}: deleted")
75
75
 
76
76
 
77
- @app.command("credentials")
78
- def show_credentials(
79
- token: Optional[str] = typer.Option(None, "--token", help="Override token"),
80
- ):
81
- """Show S3 credentials."""
82
- c = get_client(token)
83
- data = sdk_call(c.storage.credentials)
84
- print_object(data, [
85
- ("access_key", "Access Key"), ("secret_key", "Secret Key"),
86
- ("endpoint", "Endpoint"), ("region", "Region"),
87
- ])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wayscloud-cli
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: Command-line interface for WAYSCloud — manage VPS, DNS, databases, storage, apps, IoT, and more
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://wayscloud.services
@@ -19,12 +19,10 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Operating System :: OS Independent
20
20
  Requires-Python: >=3.9
21
21
  Description-Content-Type: text/markdown
22
- License-File: LICENSE
23
22
  Requires-Dist: wayscloud>=0.1.0
24
23
  Requires-Dist: typer>=0.9
25
24
  Requires-Dist: rich>=13.0
26
25
  Requires-Dist: websockets>=12.0
27
- Dynamic: license-file
28
26
 
29
27
  # WAYSCloud CLI
30
28
 
@@ -1,4 +1,3 @@
1
- LICENSE
2
1
  README.md
3
2
  pyproject.toml
4
3
  setup.cfg
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 WAYSCloud AS
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.
@@ -1 +0,0 @@
1
- __version__ = "0.2.3"
@@ -1,136 +0,0 @@
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')}")
File without changes