runtime-sdk 0.2.0__tar.gz → 0.2.1__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.
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/PKG-INFO +3 -3
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/README.md +2 -2
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/pyproject.toml +1 -1
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/scripts/runtime_sdk/cli.py +191 -49
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/scripts/runtime_sdk/client.py +28 -3
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/scripts/runtime_sdk.egg-info/PKG-INFO +3 -3
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/scripts/runtime_sdk/__init__.py +0 -0
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/scripts/runtime_sdk/config.py +0 -0
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/scripts/runtime_sdk.egg-info/SOURCES.txt +0 -0
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/scripts/runtime_sdk.egg-info/dependency_links.txt +0 -0
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/scripts/runtime_sdk.egg-info/entry_points.txt +0 -0
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/scripts/runtime_sdk.egg-info/requires.txt +0 -0
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/scripts/runtime_sdk.egg-info/top_level.txt +0 -0
- {runtime_sdk-0.2.0 → runtime_sdk-0.2.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runtime-sdk
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Runtime Python SDK and CLI
|
|
5
5
|
Project-URL: Repository, https://github.com/The-Money-Company-Limited/runtimevm
|
|
6
6
|
Project-URL: Issues, https://github.com/The-Money-Company-Limited/runtimevm/issues
|
|
@@ -46,11 +46,11 @@ Or pass `--base-url` per command.
|
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
48
|
# Auth
|
|
49
|
-
runtime
|
|
49
|
+
runtime login you@example.com
|
|
50
50
|
runtime verify 123456
|
|
51
51
|
runtime whoami
|
|
52
52
|
runtime logout
|
|
53
|
-
runtime login
|
|
53
|
+
runtime login --api-key rt_live_...
|
|
54
54
|
|
|
55
55
|
# Computers
|
|
56
56
|
runtime create
|
|
@@ -28,11 +28,11 @@ Or pass `--base-url` per command.
|
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
# Auth
|
|
31
|
-
runtime
|
|
31
|
+
runtime login you@example.com
|
|
32
32
|
runtime verify 123456
|
|
33
33
|
runtime whoami
|
|
34
34
|
runtime logout
|
|
35
|
-
runtime login
|
|
35
|
+
runtime login --api-key rt_live_...
|
|
36
36
|
|
|
37
37
|
# Computers
|
|
38
38
|
runtime create
|
|
@@ -50,6 +50,7 @@ class _UI:
|
|
|
50
50
|
_console: Any = None
|
|
51
51
|
_err_console: Any = None
|
|
52
52
|
_questionary: Any = None
|
|
53
|
+
_questionary_style: Any = None
|
|
53
54
|
_rich_mods: dict[str, Any] | None = None
|
|
54
55
|
|
|
55
56
|
@classmethod
|
|
@@ -76,6 +77,25 @@ class _UI:
|
|
|
76
77
|
cls._questionary = questionary
|
|
77
78
|
return cls._questionary
|
|
78
79
|
|
|
80
|
+
@classmethod
|
|
81
|
+
def style(cls) -> Any:
|
|
82
|
+
if cls._questionary_style is None:
|
|
83
|
+
cls._questionary_style = cls.q().Style(
|
|
84
|
+
[
|
|
85
|
+
("qmark", "fg:#22c55e bold"),
|
|
86
|
+
("question", "bold fg:#e5e7eb"),
|
|
87
|
+
("answer", "fg:#22c55e bold"),
|
|
88
|
+
("pointer", "fg:#60a5fa bold"),
|
|
89
|
+
("highlighted", "fg:#ffffff bg:#1f2937 bold"),
|
|
90
|
+
("selected", "fg:#22c55e bold"),
|
|
91
|
+
("separator", "fg:#4b5563"),
|
|
92
|
+
("instruction", "fg:#94a3b8 italic"),
|
|
93
|
+
("text", "fg:#d1d5db"),
|
|
94
|
+
("disabled", "fg:#6b7280 italic"),
|
|
95
|
+
]
|
|
96
|
+
)
|
|
97
|
+
return cls._questionary_style
|
|
98
|
+
|
|
79
99
|
@classmethod
|
|
80
100
|
def rich(cls) -> dict[str, Any]:
|
|
81
101
|
if cls._rich_mods is None:
|
|
@@ -98,16 +118,29 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
98
118
|
# Subcommand is optional: bare `runtime` defaults to `create`.
|
|
99
119
|
subparsers = parser.add_subparsers(dest="command", required=False)
|
|
100
120
|
|
|
101
|
-
signup = subparsers.add_parser("signup")
|
|
121
|
+
signup = subparsers.add_parser("signup", help="Send an email verification code")
|
|
102
122
|
signup.add_argument("email", nargs="?", default=None)
|
|
103
123
|
signup.add_argument("--name", default=None)
|
|
104
124
|
|
|
105
|
-
verify = subparsers.add_parser("verify")
|
|
125
|
+
verify = subparsers.add_parser("verify", help="Verify an emailed code and save a fresh API key")
|
|
106
126
|
verify.add_argument("code", nargs="?", default=None)
|
|
107
127
|
verify.add_argument("--flow-id", type=int)
|
|
108
128
|
|
|
109
|
-
login = subparsers.add_parser("login")
|
|
110
|
-
login.add_argument(
|
|
129
|
+
login = subparsers.add_parser("login", help="Start email login or import an API key")
|
|
130
|
+
login.add_argument(
|
|
131
|
+
"identifier",
|
|
132
|
+
nargs="?",
|
|
133
|
+
default=None,
|
|
134
|
+
help="Email address to send a code to, or an API key for backwards compatibility",
|
|
135
|
+
)
|
|
136
|
+
login.add_argument("--name", default=None, help="Optional display name for first-time email signup")
|
|
137
|
+
login.add_argument(
|
|
138
|
+
"--api-key",
|
|
139
|
+
nargs="?",
|
|
140
|
+
const="",
|
|
141
|
+
default=None,
|
|
142
|
+
help="Import an API key explicitly (prompts if omitted in interactive mode)",
|
|
143
|
+
)
|
|
111
144
|
|
|
112
145
|
subparsers.add_parser("whoami")
|
|
113
146
|
subparsers.add_parser("logout")
|
|
@@ -119,6 +152,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
119
152
|
default=None,
|
|
120
153
|
help="Subdomain name (e.g. redsox → redsox.runruntime.dev). Random if skipped.",
|
|
121
154
|
)
|
|
155
|
+
create_cmd.add_argument("--command", dest="startup_command", help="App startup command to run after create")
|
|
156
|
+
create_cmd.add_argument("--cwd", help="Working directory for the startup command")
|
|
157
|
+
create_cmd.add_argument("--port", type=int, help="App port to publish after startup")
|
|
122
158
|
subparsers.add_parser("list", aliases=["ls"], help="List computers")
|
|
123
159
|
|
|
124
160
|
info_cmd = subparsers.add_parser("info", help="Get computer details")
|
|
@@ -174,13 +210,19 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
174
210
|
if args.command == "verify":
|
|
175
211
|
return handle_verify(config, args.code, args.flow_id)
|
|
176
212
|
if args.command == "login":
|
|
177
|
-
return handle_login(config, args.api_key)
|
|
213
|
+
return handle_login(config, args.identifier, args.name, args.api_key)
|
|
178
214
|
if args.command == "whoami":
|
|
179
215
|
return handle_whoami(config)
|
|
180
216
|
if args.command == "logout":
|
|
181
217
|
return handle_logout(config)
|
|
182
218
|
if args.command == "create":
|
|
183
|
-
return handle_create(
|
|
219
|
+
return handle_create(
|
|
220
|
+
config,
|
|
221
|
+
args.name,
|
|
222
|
+
getattr(args, "startup_command", None),
|
|
223
|
+
getattr(args, "cwd", None),
|
|
224
|
+
getattr(args, "port", None),
|
|
225
|
+
)
|
|
184
226
|
if args.command in ("list", "ls"):
|
|
185
227
|
return handle_list(config)
|
|
186
228
|
if args.command == "info":
|
|
@@ -242,7 +284,7 @@ def report_success(payload: dict[str, Any], renderer: Callable[[dict[str, Any]],
|
|
|
242
284
|
def _require_api_key(config: RuntimeConfig) -> int | None:
|
|
243
285
|
if config.api_key:
|
|
244
286
|
return None
|
|
245
|
-
return report_error("missing api key; run
|
|
287
|
+
return report_error("missing api key; run login or verify first")
|
|
246
288
|
|
|
247
289
|
|
|
248
290
|
def _prompt_text(message: str, *, default: str | None = None) -> str | None:
|
|
@@ -308,7 +350,7 @@ def _computer_table_layout(computers: list[dict[str, Any]]) -> dict[str, int]:
|
|
|
308
350
|
|
|
309
351
|
|
|
310
352
|
def _format_computer_row(c: dict[str, Any], widths: dict[str, int]) -> str:
|
|
311
|
-
return "
|
|
353
|
+
return " │ ".join(
|
|
312
354
|
[
|
|
313
355
|
_fit_cell(c.get("slug") or c.get("id") or "?", widths["slug"]),
|
|
314
356
|
_fit_cell(c.get("status") or "?", widths["status"]),
|
|
@@ -320,7 +362,7 @@ def _format_computer_row(c: dict[str, Any], widths: dict[str, int]) -> str:
|
|
|
320
362
|
|
|
321
363
|
def _computer_table_prompt(prompt: str, computers: list[dict[str, Any]]) -> tuple[str, dict[str, int]]:
|
|
322
364
|
widths = _computer_table_layout(computers)
|
|
323
|
-
header = "
|
|
365
|
+
header = " │ ".join(
|
|
324
366
|
[
|
|
325
367
|
_fit_cell("slug", widths["slug"]),
|
|
326
368
|
_fit_cell("status", widths["status"]),
|
|
@@ -328,7 +370,53 @@ def _computer_table_prompt(prompt: str, computers: list[dict[str, Any]]) -> tupl
|
|
|
328
370
|
_fit_cell("created", widths["created_at"]),
|
|
329
371
|
]
|
|
330
372
|
)
|
|
331
|
-
|
|
373
|
+
divider = "─" * len(header)
|
|
374
|
+
return f"{prompt}\n{header}\n{divider}", widths
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _render_computer_summary(computers: list[dict[str, Any]]) -> None:
|
|
378
|
+
mods = _UI.rich()
|
|
379
|
+
Panel, Text = mods["Panel"], mods["Text"]
|
|
380
|
+
counts: dict[str, int] = {}
|
|
381
|
+
for computer in computers:
|
|
382
|
+
status = str(computer.get("status") or "unknown")
|
|
383
|
+
counts[status] = counts.get(status, 0) + 1
|
|
384
|
+
|
|
385
|
+
body = Text()
|
|
386
|
+
body.append(f"total {len(computers)}", style="bold white")
|
|
387
|
+
if counts:
|
|
388
|
+
body.append(" ")
|
|
389
|
+
first = True
|
|
390
|
+
status_styles = {
|
|
391
|
+
"running": "bold green",
|
|
392
|
+
"cold": "bold cyan",
|
|
393
|
+
"archiving": "bold yellow",
|
|
394
|
+
"restoring": "bold yellow",
|
|
395
|
+
"creating": "bold magenta",
|
|
396
|
+
"orphaned": "bold red",
|
|
397
|
+
}
|
|
398
|
+
for status in sorted(counts):
|
|
399
|
+
if not first:
|
|
400
|
+
body.append(" • ", style="dim")
|
|
401
|
+
first = False
|
|
402
|
+
body.append(status, style=status_styles.get(status, "bold white"))
|
|
403
|
+
body.append(f" {counts[status]}", style="white")
|
|
404
|
+
|
|
405
|
+
_UI.console().print(
|
|
406
|
+
Panel(body, title="runtime", border_style="blue", expand=False, padding=(0, 1))
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _select_prompt(message: str, choices: list[Any]) -> Any:
|
|
411
|
+
return _UI.q().select(
|
|
412
|
+
message,
|
|
413
|
+
choices=choices,
|
|
414
|
+
qmark="●",
|
|
415
|
+
pointer="❯",
|
|
416
|
+
instruction="↑/↓ move • Enter select • j/k also work",
|
|
417
|
+
use_indicator=True,
|
|
418
|
+
style=_UI.style(),
|
|
419
|
+
)
|
|
332
420
|
|
|
333
421
|
|
|
334
422
|
def _pick_computer(config: RuntimeConfig, prompt: str) -> dict[str, Any] | None:
|
|
@@ -350,11 +438,7 @@ def _pick_computer(config: RuntimeConfig, prompt: str) -> dict[str, Any] | None:
|
|
|
350
438
|
questionary.Choice(title=_format_computer_row(c, widths), value=c) for c in computers
|
|
351
439
|
]
|
|
352
440
|
choices.append(questionary.Choice(title="← cancel", value=None))
|
|
353
|
-
picked =
|
|
354
|
-
message,
|
|
355
|
-
choices=choices,
|
|
356
|
-
instruction="↑/↓ to move, Enter to select",
|
|
357
|
-
).unsafe_ask()
|
|
441
|
+
picked = _select_prompt(message, choices).unsafe_ask()
|
|
358
442
|
return picked if isinstance(picked, dict) else None
|
|
359
443
|
|
|
360
444
|
|
|
@@ -413,22 +497,21 @@ def _render_whoami_panel(payload: dict[str, Any]) -> None:
|
|
|
413
497
|
# --------------------------------------------------------------------------- #
|
|
414
498
|
|
|
415
499
|
|
|
416
|
-
def
|
|
500
|
+
def _looks_like_api_key(value: str | None) -> bool:
|
|
501
|
+
candidate = (value or "").strip()
|
|
502
|
+
return candidate.startswith("rt_live_")
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _start_verification_flow(config: RuntimeConfig, email: str | None, name: str | None) -> int:
|
|
417
506
|
if email is None:
|
|
418
507
|
if not _interactive():
|
|
419
508
|
return report_error("email is required")
|
|
420
509
|
email = _prompt_text("Email")
|
|
421
510
|
if not email:
|
|
422
511
|
return report_error("email is required")
|
|
423
|
-
if name is None:
|
|
424
|
-
if not _interactive():
|
|
425
|
-
return report_error("--name is required")
|
|
426
|
-
name = _prompt_text("Your name")
|
|
427
|
-
if not name:
|
|
428
|
-
return report_error("name is required")
|
|
429
512
|
|
|
430
513
|
client = RuntimeClient(base_url=config.base_url)
|
|
431
|
-
result = client.
|
|
514
|
+
result = client.start_verification(email, name)
|
|
432
515
|
config.pending_signup = PendingSignup(
|
|
433
516
|
flow_id=int(result["flow_id"]),
|
|
434
517
|
email=email,
|
|
@@ -447,11 +530,40 @@ def handle_signup(config: RuntimeConfig, email: str | None, name: str | None) ->
|
|
|
447
530
|
return report_success(result, render)
|
|
448
531
|
|
|
449
532
|
|
|
533
|
+
def _login_with_api_key(config: RuntimeConfig, api_key: str | None) -> int:
|
|
534
|
+
if api_key is None or api_key == "":
|
|
535
|
+
if not _interactive():
|
|
536
|
+
return report_error("api key is required")
|
|
537
|
+
api_key = _prompt_password("API key")
|
|
538
|
+
if not api_key:
|
|
539
|
+
return report_error("api key is required")
|
|
540
|
+
|
|
541
|
+
client = RuntimeClient(base_url=config.base_url, api_key=api_key)
|
|
542
|
+
whoami = client.whoami()
|
|
543
|
+
config.api_key = api_key
|
|
544
|
+
config.pending_signup = None
|
|
545
|
+
save_config(config)
|
|
546
|
+
|
|
547
|
+
def render(_: dict[str, Any]) -> None:
|
|
548
|
+
owner = whoami.get("owner") or {}
|
|
549
|
+
email = owner.get("email")
|
|
550
|
+
if email:
|
|
551
|
+
_UI.console().print(f"[green]✓[/green] logged in as [bold]{email}[/bold]")
|
|
552
|
+
return
|
|
553
|
+
_UI.console().print("[green]✓[/green] api key saved")
|
|
554
|
+
|
|
555
|
+
return report_success({"message": "logged in", **whoami}, render)
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def handle_signup(config: RuntimeConfig, email: str | None, name: str | None) -> int:
|
|
559
|
+
return _start_verification_flow(config, email, name)
|
|
560
|
+
|
|
561
|
+
|
|
450
562
|
def handle_verify(config: RuntimeConfig, code: str | None, flow_id: int | None) -> int:
|
|
451
563
|
pending = config.pending_signup
|
|
452
564
|
resolved_flow_id = flow_id or (pending.flow_id if pending else None)
|
|
453
565
|
if not resolved_flow_id:
|
|
454
|
-
return report_error("missing flow id; run
|
|
566
|
+
return report_error("missing flow id; run login first or pass --flow-id")
|
|
455
567
|
|
|
456
568
|
if code is None:
|
|
457
569
|
if not _interactive():
|
|
@@ -468,27 +580,32 @@ def handle_verify(config: RuntimeConfig, code: str | None, flow_id: int | None)
|
|
|
468
580
|
config.pending_signup = None
|
|
469
581
|
save_config(config)
|
|
470
582
|
|
|
471
|
-
def render(
|
|
583
|
+
def render(payload: dict[str, Any]) -> None:
|
|
584
|
+
owner = payload.get("owner") or {}
|
|
585
|
+
email = owner.get("email")
|
|
586
|
+
if email:
|
|
587
|
+
_UI.console().print(f"[green]✓[/green] verified, logged in as [bold]{email}[/bold]")
|
|
588
|
+
return
|
|
472
589
|
_UI.console().print("[green]✓[/green] verified, api key saved")
|
|
473
590
|
|
|
474
591
|
return report_success(result, render)
|
|
475
592
|
|
|
476
593
|
|
|
477
|
-
def handle_login(
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
594
|
+
def handle_login(
|
|
595
|
+
config: RuntimeConfig,
|
|
596
|
+
identifier: str | None,
|
|
597
|
+
name: str | None,
|
|
598
|
+
api_key: str | None,
|
|
599
|
+
) -> int:
|
|
600
|
+
if api_key is not None:
|
|
601
|
+
if identifier is not None:
|
|
602
|
+
return report_error("pass either an email/api key argument or --api-key, not both")
|
|
603
|
+
return _login_with_api_key(config, api_key)
|
|
487
604
|
|
|
488
|
-
|
|
489
|
-
|
|
605
|
+
if _looks_like_api_key(identifier):
|
|
606
|
+
return _login_with_api_key(config, identifier)
|
|
490
607
|
|
|
491
|
-
return
|
|
608
|
+
return _start_verification_flow(config, identifier, name)
|
|
492
609
|
|
|
493
610
|
|
|
494
611
|
def handle_whoami(config: RuntimeConfig) -> int:
|
|
@@ -500,26 +617,54 @@ def handle_whoami(config: RuntimeConfig) -> int:
|
|
|
500
617
|
|
|
501
618
|
|
|
502
619
|
def handle_logout(config: RuntimeConfig) -> int:
|
|
620
|
+
had_api_key = config.api_key is not None
|
|
621
|
+
revoked = False
|
|
622
|
+
if config.api_key:
|
|
623
|
+
try:
|
|
624
|
+
RuntimeClient(base_url=config.base_url, api_key=config.api_key).logout()
|
|
625
|
+
revoked = True
|
|
626
|
+
except RuntimeAPIError:
|
|
627
|
+
revoked = False
|
|
628
|
+
|
|
503
629
|
config.api_key = None
|
|
504
630
|
config.pending_signup = None
|
|
505
631
|
save_config(config)
|
|
506
632
|
|
|
633
|
+
message = "logged out"
|
|
634
|
+
if had_api_key and not revoked:
|
|
635
|
+
message = "logged out locally"
|
|
636
|
+
|
|
507
637
|
def render(_: dict[str, Any]) -> None:
|
|
508
|
-
_UI.console().print("[green]✓[/green]
|
|
638
|
+
_UI.console().print(f"[green]✓[/green] {message}")
|
|
509
639
|
|
|
510
|
-
return report_success({"message":
|
|
640
|
+
return report_success({"message": message}, render)
|
|
511
641
|
|
|
512
642
|
|
|
513
|
-
def handle_create(
|
|
643
|
+
def handle_create(
|
|
644
|
+
config: RuntimeConfig,
|
|
645
|
+
name: str | None,
|
|
646
|
+
startup_command: str | None,
|
|
647
|
+
cwd: str | None,
|
|
648
|
+
port: int | None,
|
|
649
|
+
) -> int:
|
|
514
650
|
if (err := _require_api_key(config)) is not None:
|
|
515
651
|
return err
|
|
516
652
|
|
|
517
653
|
if name is None and _interactive():
|
|
518
654
|
name = _prompt_text("Name your computer")
|
|
519
655
|
|
|
656
|
+
if startup_command is None and port is not None:
|
|
657
|
+
return report_error("startup command is required when port is provided")
|
|
658
|
+
if startup_command is not None and port is None:
|
|
659
|
+
return report_error("port is required when startup command is provided")
|
|
660
|
+
|
|
520
661
|
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
662
|
+
label = "creating computer…"
|
|
663
|
+
if startup_command is not None:
|
|
664
|
+
label = f"creating computer and starting app on port {port}…"
|
|
521
665
|
result = _with_spinner(
|
|
522
|
-
|
|
666
|
+
label,
|
|
667
|
+
lambda: client.create_computer(slug=name, command=startup_command, cwd=cwd, port=port),
|
|
523
668
|
)
|
|
524
669
|
|
|
525
670
|
def render(payload: dict[str, Any]) -> None:
|
|
@@ -701,6 +846,7 @@ def _interactive_list(config: RuntimeConfig) -> int:
|
|
|
701
846
|
_UI.console().print("[dim]no computers yet. create one with `runtime create`.[/dim]")
|
|
702
847
|
return 0
|
|
703
848
|
|
|
849
|
+
_render_computer_summary(computers)
|
|
704
850
|
message, widths = _computer_table_prompt("Pick a computer", computers)
|
|
705
851
|
choices = [
|
|
706
852
|
questionary.Choice(title=_format_computer_row(c, widths), value=c)
|
|
@@ -710,11 +856,7 @@ def _interactive_list(config: RuntimeConfig) -> int:
|
|
|
710
856
|
choices.append(questionary.Choice(title="↻ refresh", value="__refresh__"))
|
|
711
857
|
choices.append(questionary.Choice(title="✕ quit", value="__quit__"))
|
|
712
858
|
|
|
713
|
-
picked =
|
|
714
|
-
message,
|
|
715
|
-
choices=choices,
|
|
716
|
-
instruction="↑/↓ to move, Enter to select",
|
|
717
|
-
).unsafe_ask()
|
|
859
|
+
picked = _select_prompt(message, choices).unsafe_ask()
|
|
718
860
|
|
|
719
861
|
if picked == "__quit__":
|
|
720
862
|
return 0
|
|
@@ -738,9 +880,9 @@ def _vm_detail_menu(client: RuntimeClient, vm: dict[str, Any]) -> str:
|
|
|
738
880
|
while True:
|
|
739
881
|
_render_computer_panel(vm, title=f"computer: {slug}")
|
|
740
882
|
|
|
741
|
-
action =
|
|
883
|
+
action = _select_prompt(
|
|
742
884
|
f"What would you like to do with {slug}?",
|
|
743
|
-
|
|
885
|
+
[
|
|
744
886
|
questionary.Choice(title="▶ run a command", value="run"),
|
|
745
887
|
questionary.Choice(title="⤴ publish a port", value="publish"),
|
|
746
888
|
questionary.Choice(title="ℹ refresh info", value="info"),
|
|
@@ -22,8 +22,14 @@ class RuntimeClient:
|
|
|
22
22
|
def __post_init__(self) -> None:
|
|
23
23
|
self.base_url = self.base_url.rstrip("/")
|
|
24
24
|
|
|
25
|
-
def
|
|
26
|
-
|
|
25
|
+
def start_verification(self, email: str, name: str | None = None) -> dict[str, Any]:
|
|
26
|
+
body: dict[str, Any] = {"email": email}
|
|
27
|
+
if name is not None:
|
|
28
|
+
body["name"] = name
|
|
29
|
+
return self._request("POST", "/api/auth/start", json=body)
|
|
30
|
+
|
|
31
|
+
def signup(self, email: str, name: str | None = None) -> dict[str, Any]:
|
|
32
|
+
return self.start_verification(email, name)
|
|
27
33
|
|
|
28
34
|
def verify(self, flow_id: int, code: str) -> dict[str, Any]:
|
|
29
35
|
return self._request("POST", "/api/auth/verify", json={"flow_id": flow_id, "code": code})
|
|
@@ -31,12 +37,31 @@ class RuntimeClient:
|
|
|
31
37
|
def whoami(self) -> dict[str, Any]:
|
|
32
38
|
return self._request("GET", "/api/auth/whoami", auth_required=True)
|
|
33
39
|
|
|
40
|
+
def logout(self) -> dict[str, Any]:
|
|
41
|
+
return self._request("POST", "/api/auth/logout", auth_required=True)
|
|
42
|
+
|
|
34
43
|
# --- Computers ---
|
|
35
44
|
|
|
36
|
-
def create_computer(
|
|
45
|
+
def create_computer(
|
|
46
|
+
self,
|
|
47
|
+
*,
|
|
48
|
+
slug: str | None = None,
|
|
49
|
+
command: str | None = None,
|
|
50
|
+
cwd: str | None = None,
|
|
51
|
+
port: int | None = None,
|
|
52
|
+
) -> dict[str, Any]:
|
|
37
53
|
body: dict[str, Any] = {}
|
|
38
54
|
if slug:
|
|
39
55
|
body["slug"] = slug
|
|
56
|
+
if command is not None or cwd is not None or port is not None:
|
|
57
|
+
start: dict[str, Any] = {}
|
|
58
|
+
if command is not None:
|
|
59
|
+
start["command"] = command
|
|
60
|
+
if cwd is not None:
|
|
61
|
+
start["cwd"] = cwd
|
|
62
|
+
if port is not None:
|
|
63
|
+
start["port"] = port
|
|
64
|
+
body["start"] = start
|
|
40
65
|
return self._request("POST", "/api/computers", json=body or None, auth_required=True)
|
|
41
66
|
|
|
42
67
|
def list_computers(self) -> dict[str, Any]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runtime-sdk
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Runtime Python SDK and CLI
|
|
5
5
|
Project-URL: Repository, https://github.com/The-Money-Company-Limited/runtimevm
|
|
6
6
|
Project-URL: Issues, https://github.com/The-Money-Company-Limited/runtimevm/issues
|
|
@@ -46,11 +46,11 @@ Or pass `--base-url` per command.
|
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
48
|
# Auth
|
|
49
|
-
runtime
|
|
49
|
+
runtime login you@example.com
|
|
50
50
|
runtime verify 123456
|
|
51
51
|
runtime whoami
|
|
52
52
|
runtime logout
|
|
53
|
-
runtime login
|
|
53
|
+
runtime login --api-key rt_live_...
|
|
54
54
|
|
|
55
55
|
# Computers
|
|
56
56
|
runtime create
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|