runtime-sdk 0.4.11__tar.gz → 0.4.12__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.4.11 → runtime_sdk-0.4.12}/PKG-INFO +4 -1
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/README.md +3 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/pyproject.toml +1 -1
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/scripts/runtime_sdk/cli.py +156 -3
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/scripts/runtime_sdk/client.py +9 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/scripts/runtime_sdk.egg-info/PKG-INFO +4 -1
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/scripts/runtime_sdk/__init__.py +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/scripts/runtime_sdk/config.py +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/scripts/runtime_sdk.egg-info/SOURCES.txt +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/scripts/runtime_sdk.egg-info/dependency_links.txt +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/scripts/runtime_sdk.egg-info/entry_points.txt +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/scripts/runtime_sdk.egg-info/requires.txt +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/scripts/runtime_sdk.egg-info/top_level.txt +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.12}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runtime-sdk
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.12
|
|
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
|
|
@@ -52,6 +52,9 @@ runtime verify 123456
|
|
|
52
52
|
runtime whoami
|
|
53
53
|
runtime logout
|
|
54
54
|
runtime login --api-key rt_live_...
|
|
55
|
+
runtime api-keys list
|
|
56
|
+
runtime api-keys create "my laptop"
|
|
57
|
+
runtime api-keys revoke 123
|
|
55
58
|
|
|
56
59
|
# Computers
|
|
57
60
|
runtime create # creates a computer with the starter app already published
|
|
@@ -33,6 +33,9 @@ runtime verify 123456
|
|
|
33
33
|
runtime whoami
|
|
34
34
|
runtime logout
|
|
35
35
|
runtime login --api-key rt_live_...
|
|
36
|
+
runtime api-keys list
|
|
37
|
+
runtime api-keys create "my laptop"
|
|
38
|
+
runtime api-keys revoke 123
|
|
36
39
|
|
|
37
40
|
# Computers
|
|
38
41
|
runtime create # creates a computer with the starter app already published
|
|
@@ -165,6 +165,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
165
165
|
subparsers.add_parser("whoami")
|
|
166
166
|
subparsers.add_parser("logout")
|
|
167
167
|
|
|
168
|
+
api_keys_cmd = subparsers.add_parser("api-keys", help="Manage Runtime API keys")
|
|
169
|
+
api_keys_subparsers = api_keys_cmd.add_subparsers(dest="api_keys_action", required=True)
|
|
170
|
+
api_keys_subparsers.add_parser("list", aliases=["ls"], help="List API keys")
|
|
171
|
+
api_keys_create = api_keys_subparsers.add_parser("create", help="Create a new API key")
|
|
172
|
+
api_keys_create.add_argument("label", nargs="?", default=None, help="Human-friendly label for the new API key")
|
|
173
|
+
api_keys_revoke = api_keys_subparsers.add_parser("revoke", help="Revoke an API key")
|
|
174
|
+
api_keys_revoke.add_argument("id", nargs="?", default=None, help="API key id")
|
|
175
|
+
|
|
168
176
|
create_cmd = subparsers.add_parser("create", help="Create a new computer with the starter app published by default")
|
|
169
177
|
create_cmd.add_argument(
|
|
170
178
|
"name",
|
|
@@ -280,6 +288,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
280
288
|
return handle_whoami(config)
|
|
281
289
|
if args.command == "logout":
|
|
282
290
|
return handle_logout(config)
|
|
291
|
+
if args.command == "api-keys":
|
|
292
|
+
if args.api_keys_action in ("list", "ls"):
|
|
293
|
+
return handle_api_keys_list(config)
|
|
294
|
+
if args.api_keys_action == "create":
|
|
295
|
+
return handle_api_keys_create(config, args.label)
|
|
296
|
+
if args.api_keys_action == "revoke":
|
|
297
|
+
return handle_api_keys_revoke(config, args.id)
|
|
298
|
+
return report_error("unknown api-keys command")
|
|
283
299
|
if args.command == "create":
|
|
284
300
|
return handle_create(
|
|
285
301
|
config,
|
|
@@ -543,7 +559,7 @@ def _spawn_proxy_agent(base_url: str, api_key: str, computer_id: str, display_na
|
|
|
543
559
|
finally:
|
|
544
560
|
log_file.close()
|
|
545
561
|
|
|
546
|
-
deadline = time.time() + 4.0
|
|
562
|
+
deadline = time.time() + max(4.0, DEFAULT_WARMUP_TIMEOUT + 2.0)
|
|
547
563
|
state_path = _proxy_state_path(local_port)
|
|
548
564
|
while time.time() < deadline:
|
|
549
565
|
if state_path.exists():
|
|
@@ -715,8 +731,8 @@ def _computer_url_label(c: dict[str, Any]) -> str:
|
|
|
715
731
|
|
|
716
732
|
|
|
717
733
|
|
|
718
|
-
def
|
|
719
|
-
raw = str(
|
|
734
|
+
def _relative_time_label(value: Any) -> str:
|
|
735
|
+
raw = str(value or "").strip()
|
|
720
736
|
if not raw:
|
|
721
737
|
return ""
|
|
722
738
|
try:
|
|
@@ -738,6 +754,11 @@ def _computer_created_label(c: dict[str, Any]) -> str:
|
|
|
738
754
|
|
|
739
755
|
|
|
740
756
|
|
|
757
|
+
def _computer_created_label(c: dict[str, Any]) -> str:
|
|
758
|
+
return _relative_time_label(c.get("created_at"))
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
|
|
741
762
|
def _computer_status_style(status: str) -> str:
|
|
742
763
|
if "cold" in status:
|
|
743
764
|
return "bold cyan"
|
|
@@ -1072,6 +1093,84 @@ def _render_whoami_panel(payload: dict[str, Any]) -> None:
|
|
|
1072
1093
|
_UI.console().print(Panel(body, title="whoami", border_style="cyan", expand=False))
|
|
1073
1094
|
|
|
1074
1095
|
|
|
1096
|
+
|
|
1097
|
+
def _api_key_status_label(key: dict[str, Any]) -> str:
|
|
1098
|
+
if key.get("revoked_at"):
|
|
1099
|
+
return "revoked"
|
|
1100
|
+
if key.get("last_used_at"):
|
|
1101
|
+
return "used"
|
|
1102
|
+
return "unused"
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
def _api_key_status_style(status: str) -> str:
|
|
1107
|
+
if status == "revoked":
|
|
1108
|
+
return "bold red"
|
|
1109
|
+
if status == "used":
|
|
1110
|
+
return "bold green"
|
|
1111
|
+
return "yellow"
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
def _render_api_keys_table(payload: dict[str, Any]) -> None:
|
|
1116
|
+
mods = _UI.rich()
|
|
1117
|
+
Table = mods["Table"]
|
|
1118
|
+
api_keys = payload.get("api_keys") or []
|
|
1119
|
+
if not isinstance(api_keys, list) or not api_keys:
|
|
1120
|
+
_UI.console().print("[yellow]no api keys found[/yellow]")
|
|
1121
|
+
return
|
|
1122
|
+
|
|
1123
|
+
table = Table(title="API keys", expand=True, box=mods["box"].SIMPLE_HEAVY)
|
|
1124
|
+
table.add_column("ID", style="dim", no_wrap=True)
|
|
1125
|
+
table.add_column("Label", style="bold white")
|
|
1126
|
+
table.add_column("Prefix", style="white", no_wrap=True)
|
|
1127
|
+
table.add_column("Created", style="dim", no_wrap=True, justify="right")
|
|
1128
|
+
table.add_column("Last used", style="dim", no_wrap=True, justify="right")
|
|
1129
|
+
table.add_column("Status", no_wrap=True)
|
|
1130
|
+
|
|
1131
|
+
for entry in api_keys:
|
|
1132
|
+
if not isinstance(entry, dict):
|
|
1133
|
+
continue
|
|
1134
|
+
status = _api_key_status_label(entry)
|
|
1135
|
+
style = _api_key_status_style(status)
|
|
1136
|
+
table.add_row(
|
|
1137
|
+
str(entry.get("id") or "—"),
|
|
1138
|
+
str(entry.get("label") or "—"),
|
|
1139
|
+
str(entry.get("key_prefix") or "—"),
|
|
1140
|
+
_relative_time_label(entry.get("created_at")) or "—",
|
|
1141
|
+
_relative_time_label(entry.get("last_used_at")) or "—",
|
|
1142
|
+
f"[{style}]{status}[/{style}]",
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1145
|
+
_UI.console().print(table)
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
def _render_api_key_created(payload: dict[str, Any]) -> None:
|
|
1150
|
+
mods = _UI.rich()
|
|
1151
|
+
Panel, Text = mods["Panel"], mods["Text"]
|
|
1152
|
+
key = payload.get("key") or {}
|
|
1153
|
+
secret = str(payload.get("api_key") or "")
|
|
1154
|
+
body = Text()
|
|
1155
|
+
rows = [
|
|
1156
|
+
("id", key.get("id")),
|
|
1157
|
+
("label", key.get("label")),
|
|
1158
|
+
("prefix", key.get("key_prefix")),
|
|
1159
|
+
]
|
|
1160
|
+
for label, value in rows:
|
|
1161
|
+
if value is None:
|
|
1162
|
+
continue
|
|
1163
|
+
body.append(f"{label:<12}", style="dim")
|
|
1164
|
+
body.append(f"{value}\n")
|
|
1165
|
+
if secret:
|
|
1166
|
+
body.append("save now ", style="bold yellow")
|
|
1167
|
+
body.append("this secret is only shown once\n", style="yellow")
|
|
1168
|
+
body.append("api key ", style="dim")
|
|
1169
|
+
body.append(secret, style="bold white")
|
|
1170
|
+
body.append("\n")
|
|
1171
|
+
_UI.console().print(Panel(body, title="api key created", border_style="cyan", expand=False))
|
|
1172
|
+
|
|
1173
|
+
|
|
1075
1174
|
# --------------------------------------------------------------------------- #
|
|
1076
1175
|
# Command handlers #
|
|
1077
1176
|
# --------------------------------------------------------------------------- #
|
|
@@ -1220,6 +1319,56 @@ def handle_logout(config: RuntimeConfig) -> int:
|
|
|
1220
1319
|
return report_success({"message": message}, render)
|
|
1221
1320
|
|
|
1222
1321
|
|
|
1322
|
+
|
|
1323
|
+
def handle_api_keys_list(config: RuntimeConfig) -> int:
|
|
1324
|
+
if (err := _require_api_key(config)) is not None:
|
|
1325
|
+
return err
|
|
1326
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1327
|
+
return report_success(client.list_api_keys(), _render_api_keys_table)
|
|
1328
|
+
|
|
1329
|
+
|
|
1330
|
+
|
|
1331
|
+
def handle_api_keys_create(config: RuntimeConfig, label: str | None) -> int:
|
|
1332
|
+
if (err := _require_api_key(config)) is not None:
|
|
1333
|
+
return err
|
|
1334
|
+
if label is None:
|
|
1335
|
+
if not _interactive():
|
|
1336
|
+
return report_error("label is required")
|
|
1337
|
+
label = _prompt_text("API key label")
|
|
1338
|
+
if not label:
|
|
1339
|
+
return report_error("label is required")
|
|
1340
|
+
|
|
1341
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1342
|
+
result = client.create_api_key(label)
|
|
1343
|
+
return report_success(result, _render_api_key_created)
|
|
1344
|
+
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
def handle_api_keys_revoke(config: RuntimeConfig, key_id: str | None) -> int:
|
|
1348
|
+
if (err := _require_api_key(config)) is not None:
|
|
1349
|
+
return err
|
|
1350
|
+
if key_id is None:
|
|
1351
|
+
if not _interactive():
|
|
1352
|
+
return report_error("api key id is required")
|
|
1353
|
+
key_id = _prompt_text("API key id")
|
|
1354
|
+
if not key_id:
|
|
1355
|
+
return report_error("api key id is required")
|
|
1356
|
+
|
|
1357
|
+
try:
|
|
1358
|
+
resolved_key_id = int(str(key_id).strip())
|
|
1359
|
+
except ValueError:
|
|
1360
|
+
return report_error("api key id must be a number")
|
|
1361
|
+
|
|
1362
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1363
|
+
client.revoke_api_key(resolved_key_id)
|
|
1364
|
+
|
|
1365
|
+
def render(_: dict[str, Any]) -> None:
|
|
1366
|
+
_UI.console().print(f"[green]✓[/green] api key {resolved_key_id} revoked")
|
|
1367
|
+
|
|
1368
|
+
return report_success({"id": resolved_key_id, "message": f"api key {resolved_key_id} revoked"}, render)
|
|
1369
|
+
|
|
1370
|
+
|
|
1371
|
+
|
|
1223
1372
|
def handle_create(
|
|
1224
1373
|
config: RuntimeConfig,
|
|
1225
1374
|
name: str | None,
|
|
@@ -1633,6 +1782,10 @@ def handle_proxy_start(config: RuntimeConfig, computer_id: str | None, specs: li
|
|
|
1633
1782
|
if started_ports:
|
|
1634
1783
|
_stop_proxy_ports(started_ports, stop_all=False)
|
|
1635
1784
|
return report_error(str(exc), status_code=exc.status_code)
|
|
1785
|
+
except Exception as exc:
|
|
1786
|
+
if started_ports:
|
|
1787
|
+
_stop_proxy_ports(started_ports, stop_all=False)
|
|
1788
|
+
return report_error(str(exc))
|
|
1636
1789
|
started_ports.append(local_port)
|
|
1637
1790
|
started.append({"computer_id": resolved_id, "computer": computer_id, "local_port": local_port, "remote_port": remote_port})
|
|
1638
1791
|
|
|
@@ -43,6 +43,15 @@ class RuntimeClient:
|
|
|
43
43
|
def logout(self) -> dict[str, Any]:
|
|
44
44
|
return self._request("POST", "/api/auth/logout", auth_required=True)
|
|
45
45
|
|
|
46
|
+
def list_api_keys(self) -> dict[str, Any]:
|
|
47
|
+
return self._request("GET", "/api/api-keys", auth_required=True)
|
|
48
|
+
|
|
49
|
+
def create_api_key(self, label: str) -> dict[str, Any]:
|
|
50
|
+
return self._request("POST", "/api/api-keys", json={"label": label}, auth_required=True)
|
|
51
|
+
|
|
52
|
+
def revoke_api_key(self, key_id: int) -> dict[str, Any]:
|
|
53
|
+
return self._request("POST", f"/api/api-keys/{key_id}/revoke", auth_required=True)
|
|
54
|
+
|
|
46
55
|
# --- Computers ---
|
|
47
56
|
|
|
48
57
|
def create_computer(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runtime-sdk
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.12
|
|
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
|
|
@@ -52,6 +52,9 @@ runtime verify 123456
|
|
|
52
52
|
runtime whoami
|
|
53
53
|
runtime logout
|
|
54
54
|
runtime login --api-key rt_live_...
|
|
55
|
+
runtime api-keys list
|
|
56
|
+
runtime api-keys create "my laptop"
|
|
57
|
+
runtime api-keys revoke 123
|
|
55
58
|
|
|
56
59
|
# Computers
|
|
57
60
|
runtime create # creates a computer with the starter app already published
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|