runtime-sdk 0.4.11__tar.gz → 0.4.13__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.13}/PKG-INFO +6 -1
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/README.md +5 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/pyproject.toml +1 -1
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/scripts/runtime_sdk/cli.py +217 -17
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/scripts/runtime_sdk/client.py +9 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/scripts/runtime_sdk.egg-info/PKG-INFO +6 -1
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/scripts/runtime_sdk/__init__.py +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/scripts/runtime_sdk/config.py +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/scripts/runtime_sdk.egg-info/SOURCES.txt +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/scripts/runtime_sdk.egg-info/dependency_links.txt +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/scripts/runtime_sdk.egg-info/entry_points.txt +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/scripts/runtime_sdk.egg-info/requires.txt +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/scripts/runtime_sdk.egg-info/top_level.txt +0 -0
- {runtime_sdk-0.4.11 → runtime_sdk-0.4.13}/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.13
|
|
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
|
|
@@ -47,11 +47,16 @@ Or pass `--base-url` per command.
|
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
49
|
# Auth
|
|
50
|
+
runtime # first run in a real terminal prompts for email + verification code, then creates your first computer
|
|
51
|
+
runtime login # interactive email + verification code flow
|
|
50
52
|
runtime login you@example.com
|
|
51
53
|
runtime verify 123456
|
|
52
54
|
runtime whoami
|
|
53
55
|
runtime logout
|
|
54
56
|
runtime login --api-key rt_live_...
|
|
57
|
+
runtime api-keys list
|
|
58
|
+
runtime api-keys create "my laptop"
|
|
59
|
+
runtime api-keys revoke 123
|
|
55
60
|
|
|
56
61
|
# Computers
|
|
57
62
|
runtime create # creates a computer with the starter app already published
|
|
@@ -28,11 +28,16 @@ Or pass `--base-url` per command.
|
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
# Auth
|
|
31
|
+
runtime # first run in a real terminal prompts for email + verification code, then creates your first computer
|
|
32
|
+
runtime login # interactive email + verification code flow
|
|
31
33
|
runtime login you@example.com
|
|
32
34
|
runtime verify 123456
|
|
33
35
|
runtime whoami
|
|
34
36
|
runtime logout
|
|
35
37
|
runtime login --api-key rt_live_...
|
|
38
|
+
runtime api-keys list
|
|
39
|
+
runtime api-keys create "my laptop"
|
|
40
|
+
runtime api-keys revoke 123
|
|
36
41
|
|
|
37
42
|
# Computers
|
|
38
43
|
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",
|
|
@@ -255,11 +263,6 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
255
263
|
parser = build_parser()
|
|
256
264
|
args = parser.parse_args(argv)
|
|
257
265
|
|
|
258
|
-
# Bare `runtime` → create flow.
|
|
259
|
-
if args.command is None:
|
|
260
|
-
args.command = "create"
|
|
261
|
-
args.name = None
|
|
262
|
-
|
|
263
266
|
try:
|
|
264
267
|
config = load_config()
|
|
265
268
|
base_url = (
|
|
@@ -270,6 +273,13 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
270
273
|
)
|
|
271
274
|
config.base_url = base_url
|
|
272
275
|
|
|
276
|
+
# Bare `runtime` → first-run onboarding for interactive users, create otherwise.
|
|
277
|
+
if args.command is None:
|
|
278
|
+
if _interactive() and not config.api_key:
|
|
279
|
+
return handle_onboarding(config)
|
|
280
|
+
args.command = "create"
|
|
281
|
+
args.name = None
|
|
282
|
+
|
|
273
283
|
if args.command == "signup":
|
|
274
284
|
return handle_signup(config, args.email, args.name)
|
|
275
285
|
if args.command == "verify":
|
|
@@ -280,6 +290,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
280
290
|
return handle_whoami(config)
|
|
281
291
|
if args.command == "logout":
|
|
282
292
|
return handle_logout(config)
|
|
293
|
+
if args.command == "api-keys":
|
|
294
|
+
if args.api_keys_action in ("list", "ls"):
|
|
295
|
+
return handle_api_keys_list(config)
|
|
296
|
+
if args.api_keys_action == "create":
|
|
297
|
+
return handle_api_keys_create(config, args.label)
|
|
298
|
+
if args.api_keys_action == "revoke":
|
|
299
|
+
return handle_api_keys_revoke(config, args.id)
|
|
300
|
+
return report_error("unknown api-keys command")
|
|
283
301
|
if args.command == "create":
|
|
284
302
|
return handle_create(
|
|
285
303
|
config,
|
|
@@ -543,7 +561,7 @@ def _spawn_proxy_agent(base_url: str, api_key: str, computer_id: str, display_na
|
|
|
543
561
|
finally:
|
|
544
562
|
log_file.close()
|
|
545
563
|
|
|
546
|
-
deadline = time.time() + 4.0
|
|
564
|
+
deadline = time.time() + max(4.0, DEFAULT_WARMUP_TIMEOUT + 2.0)
|
|
547
565
|
state_path = _proxy_state_path(local_port)
|
|
548
566
|
while time.time() < deadline:
|
|
549
567
|
if state_path.exists():
|
|
@@ -715,8 +733,8 @@ def _computer_url_label(c: dict[str, Any]) -> str:
|
|
|
715
733
|
|
|
716
734
|
|
|
717
735
|
|
|
718
|
-
def
|
|
719
|
-
raw = str(
|
|
736
|
+
def _relative_time_label(value: Any) -> str:
|
|
737
|
+
raw = str(value or "").strip()
|
|
720
738
|
if not raw:
|
|
721
739
|
return ""
|
|
722
740
|
try:
|
|
@@ -738,6 +756,11 @@ def _computer_created_label(c: dict[str, Any]) -> str:
|
|
|
738
756
|
|
|
739
757
|
|
|
740
758
|
|
|
759
|
+
def _computer_created_label(c: dict[str, Any]) -> str:
|
|
760
|
+
return _relative_time_label(c.get("created_at"))
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
|
|
741
764
|
def _computer_status_style(status: str) -> str:
|
|
742
765
|
if "cold" in status:
|
|
743
766
|
return "bold cyan"
|
|
@@ -1072,6 +1095,84 @@ def _render_whoami_panel(payload: dict[str, Any]) -> None:
|
|
|
1072
1095
|
_UI.console().print(Panel(body, title="whoami", border_style="cyan", expand=False))
|
|
1073
1096
|
|
|
1074
1097
|
|
|
1098
|
+
|
|
1099
|
+
def _api_key_status_label(key: dict[str, Any]) -> str:
|
|
1100
|
+
if key.get("revoked_at"):
|
|
1101
|
+
return "revoked"
|
|
1102
|
+
if key.get("last_used_at"):
|
|
1103
|
+
return "used"
|
|
1104
|
+
return "unused"
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
def _api_key_status_style(status: str) -> str:
|
|
1109
|
+
if status == "revoked":
|
|
1110
|
+
return "bold red"
|
|
1111
|
+
if status == "used":
|
|
1112
|
+
return "bold green"
|
|
1113
|
+
return "yellow"
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
|
|
1117
|
+
def _render_api_keys_table(payload: dict[str, Any]) -> None:
|
|
1118
|
+
mods = _UI.rich()
|
|
1119
|
+
Table = mods["Table"]
|
|
1120
|
+
api_keys = payload.get("api_keys") or []
|
|
1121
|
+
if not isinstance(api_keys, list) or not api_keys:
|
|
1122
|
+
_UI.console().print("[yellow]no api keys found[/yellow]")
|
|
1123
|
+
return
|
|
1124
|
+
|
|
1125
|
+
table = Table(title="API keys", expand=True, box=mods["box"].SIMPLE_HEAVY)
|
|
1126
|
+
table.add_column("ID", style="dim", no_wrap=True)
|
|
1127
|
+
table.add_column("Label", style="bold white")
|
|
1128
|
+
table.add_column("Prefix", style="white", no_wrap=True)
|
|
1129
|
+
table.add_column("Created", style="dim", no_wrap=True, justify="right")
|
|
1130
|
+
table.add_column("Last used", style="dim", no_wrap=True, justify="right")
|
|
1131
|
+
table.add_column("Status", no_wrap=True)
|
|
1132
|
+
|
|
1133
|
+
for entry in api_keys:
|
|
1134
|
+
if not isinstance(entry, dict):
|
|
1135
|
+
continue
|
|
1136
|
+
status = _api_key_status_label(entry)
|
|
1137
|
+
style = _api_key_status_style(status)
|
|
1138
|
+
table.add_row(
|
|
1139
|
+
str(entry.get("id") or "—"),
|
|
1140
|
+
str(entry.get("label") or "—"),
|
|
1141
|
+
str(entry.get("key_prefix") or "—"),
|
|
1142
|
+
_relative_time_label(entry.get("created_at")) or "—",
|
|
1143
|
+
_relative_time_label(entry.get("last_used_at")) or "—",
|
|
1144
|
+
f"[{style}]{status}[/{style}]",
|
|
1145
|
+
)
|
|
1146
|
+
|
|
1147
|
+
_UI.console().print(table)
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
def _render_api_key_created(payload: dict[str, Any]) -> None:
|
|
1152
|
+
mods = _UI.rich()
|
|
1153
|
+
Panel, Text = mods["Panel"], mods["Text"]
|
|
1154
|
+
key = payload.get("key") or {}
|
|
1155
|
+
secret = str(payload.get("api_key") or "")
|
|
1156
|
+
body = Text()
|
|
1157
|
+
rows = [
|
|
1158
|
+
("id", key.get("id")),
|
|
1159
|
+
("label", key.get("label")),
|
|
1160
|
+
("prefix", key.get("key_prefix")),
|
|
1161
|
+
]
|
|
1162
|
+
for label, value in rows:
|
|
1163
|
+
if value is None:
|
|
1164
|
+
continue
|
|
1165
|
+
body.append(f"{label:<12}", style="dim")
|
|
1166
|
+
body.append(f"{value}\n")
|
|
1167
|
+
if secret:
|
|
1168
|
+
body.append("save now ", style="bold yellow")
|
|
1169
|
+
body.append("this secret is only shown once\n", style="yellow")
|
|
1170
|
+
body.append("api key ", style="dim")
|
|
1171
|
+
body.append(secret, style="bold white")
|
|
1172
|
+
body.append("\n")
|
|
1173
|
+
_UI.console().print(Panel(body, title="api key created", border_style="cyan", expand=False))
|
|
1174
|
+
|
|
1175
|
+
|
|
1075
1176
|
# --------------------------------------------------------------------------- #
|
|
1076
1177
|
# Command handlers #
|
|
1077
1178
|
# --------------------------------------------------------------------------- #
|
|
@@ -1082,14 +1183,7 @@ def _looks_like_api_key(value: str | None) -> bool:
|
|
|
1082
1183
|
return candidate.startswith("rt_live_")
|
|
1083
1184
|
|
|
1084
1185
|
|
|
1085
|
-
def
|
|
1086
|
-
if email is None:
|
|
1087
|
-
if not _interactive():
|
|
1088
|
-
return report_error("email is required")
|
|
1089
|
-
email = _prompt_text("Email")
|
|
1090
|
-
if not email:
|
|
1091
|
-
return report_error("email is required")
|
|
1092
|
-
|
|
1186
|
+
def _begin_verification_flow(config: RuntimeConfig, email: str, name: str | None) -> dict[str, Any]:
|
|
1093
1187
|
client = RuntimeClient(base_url=config.base_url)
|
|
1094
1188
|
result = client.start_verification(email, name)
|
|
1095
1189
|
config.pending_signup = PendingSignup(
|
|
@@ -1098,6 +1192,19 @@ def _start_verification_flow(config: RuntimeConfig, email: str | None, name: str
|
|
|
1098
1192
|
expires_at=result.get("expires_at"),
|
|
1099
1193
|
)
|
|
1100
1194
|
save_config(config)
|
|
1195
|
+
return result
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
def _start_verification_flow(config: RuntimeConfig, email: str | None, name: str | None) -> int:
|
|
1200
|
+
if email is None:
|
|
1201
|
+
if not _interactive():
|
|
1202
|
+
return report_error("email is required")
|
|
1203
|
+
email = _prompt_text("Email")
|
|
1204
|
+
if not email:
|
|
1205
|
+
return report_error("email is required")
|
|
1206
|
+
|
|
1207
|
+
result = _begin_verification_flow(config, email, name)
|
|
1101
1208
|
|
|
1102
1209
|
def render(payload: dict[str, Any]) -> None:
|
|
1103
1210
|
_UI.console().print(
|
|
@@ -1110,6 +1217,33 @@ def _start_verification_flow(config: RuntimeConfig, email: str | None, name: str
|
|
|
1110
1217
|
return report_success(result, render)
|
|
1111
1218
|
|
|
1112
1219
|
|
|
1220
|
+
|
|
1221
|
+
def _interactive_email_auth_flow(config: RuntimeConfig, email: str | None, name: str | None) -> int:
|
|
1222
|
+
pending = config.pending_signup
|
|
1223
|
+
if email is None and pending is not None and pending.email:
|
|
1224
|
+
_UI.console().print(
|
|
1225
|
+
f"[green]✓[/green] verification code already sent to [bold]{pending.email}[/bold]"
|
|
1226
|
+
)
|
|
1227
|
+
code = _prompt_text("Verification code")
|
|
1228
|
+
if not code:
|
|
1229
|
+
return report_error("verification code is required")
|
|
1230
|
+
return handle_verify(config, code, pending.flow_id)
|
|
1231
|
+
|
|
1232
|
+
if email is None:
|
|
1233
|
+
email = _prompt_text("Email")
|
|
1234
|
+
if not email:
|
|
1235
|
+
return report_error("email is required")
|
|
1236
|
+
|
|
1237
|
+
_begin_verification_flow(config, email, name)
|
|
1238
|
+
_UI.console().print(
|
|
1239
|
+
f"[green]✓[/green] verification code sent to [bold]{email}[/bold]"
|
|
1240
|
+
)
|
|
1241
|
+
code = _prompt_text("Verification code")
|
|
1242
|
+
if not code:
|
|
1243
|
+
return report_error("verification code is required")
|
|
1244
|
+
return handle_verify(config, code, None)
|
|
1245
|
+
|
|
1246
|
+
|
|
1113
1247
|
def _login_with_api_key(config: RuntimeConfig, api_key: str | None) -> int:
|
|
1114
1248
|
if api_key is None or api_key == "":
|
|
1115
1249
|
if not _interactive():
|
|
@@ -1136,6 +1270,8 @@ def _login_with_api_key(config: RuntimeConfig, api_key: str | None) -> int:
|
|
|
1136
1270
|
|
|
1137
1271
|
|
|
1138
1272
|
def handle_signup(config: RuntimeConfig, email: str | None, name: str | None) -> int:
|
|
1273
|
+
if _interactive():
|
|
1274
|
+
return _interactive_email_auth_flow(config, email, name)
|
|
1139
1275
|
return _start_verification_flow(config, email, name)
|
|
1140
1276
|
|
|
1141
1277
|
|
|
@@ -1185,6 +1321,8 @@ def handle_login(
|
|
|
1185
1321
|
if _looks_like_api_key(identifier):
|
|
1186
1322
|
return _login_with_api_key(config, identifier)
|
|
1187
1323
|
|
|
1324
|
+
if _interactive():
|
|
1325
|
+
return _interactive_email_auth_flow(config, identifier, name)
|
|
1188
1326
|
return _start_verification_flow(config, identifier, name)
|
|
1189
1327
|
|
|
1190
1328
|
|
|
@@ -1220,6 +1358,64 @@ def handle_logout(config: RuntimeConfig) -> int:
|
|
|
1220
1358
|
return report_success({"message": message}, render)
|
|
1221
1359
|
|
|
1222
1360
|
|
|
1361
|
+
|
|
1362
|
+
def handle_api_keys_list(config: RuntimeConfig) -> int:
|
|
1363
|
+
if (err := _require_api_key(config)) is not None:
|
|
1364
|
+
return err
|
|
1365
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1366
|
+
return report_success(client.list_api_keys(), _render_api_keys_table)
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
|
|
1370
|
+
def handle_api_keys_create(config: RuntimeConfig, label: str | None) -> int:
|
|
1371
|
+
if (err := _require_api_key(config)) is not None:
|
|
1372
|
+
return err
|
|
1373
|
+
if label is None:
|
|
1374
|
+
if not _interactive():
|
|
1375
|
+
return report_error("label is required")
|
|
1376
|
+
label = _prompt_text("API key label")
|
|
1377
|
+
if not label:
|
|
1378
|
+
return report_error("label is required")
|
|
1379
|
+
|
|
1380
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1381
|
+
result = client.create_api_key(label)
|
|
1382
|
+
return report_success(result, _render_api_key_created)
|
|
1383
|
+
|
|
1384
|
+
|
|
1385
|
+
|
|
1386
|
+
def handle_api_keys_revoke(config: RuntimeConfig, key_id: str | None) -> int:
|
|
1387
|
+
if (err := _require_api_key(config)) is not None:
|
|
1388
|
+
return err
|
|
1389
|
+
if key_id is None:
|
|
1390
|
+
if not _interactive():
|
|
1391
|
+
return report_error("api key id is required")
|
|
1392
|
+
key_id = _prompt_text("API key id")
|
|
1393
|
+
if not key_id:
|
|
1394
|
+
return report_error("api key id is required")
|
|
1395
|
+
|
|
1396
|
+
try:
|
|
1397
|
+
resolved_key_id = int(str(key_id).strip())
|
|
1398
|
+
except ValueError:
|
|
1399
|
+
return report_error("api key id must be a number")
|
|
1400
|
+
|
|
1401
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1402
|
+
client.revoke_api_key(resolved_key_id)
|
|
1403
|
+
|
|
1404
|
+
def render(_: dict[str, Any]) -> None:
|
|
1405
|
+
_UI.console().print(f"[green]✓[/green] api key {resolved_key_id} revoked")
|
|
1406
|
+
|
|
1407
|
+
return report_success({"id": resolved_key_id, "message": f"api key {resolved_key_id} revoked"}, render)
|
|
1408
|
+
|
|
1409
|
+
|
|
1410
|
+
|
|
1411
|
+
def handle_onboarding(config: RuntimeConfig) -> int:
|
|
1412
|
+
code = handle_login(config, None, None, None)
|
|
1413
|
+
if code != 0:
|
|
1414
|
+
return code
|
|
1415
|
+
return handle_create(config, None, None, None, None)
|
|
1416
|
+
|
|
1417
|
+
|
|
1418
|
+
|
|
1223
1419
|
def handle_create(
|
|
1224
1420
|
config: RuntimeConfig,
|
|
1225
1421
|
name: str | None,
|
|
@@ -1633,6 +1829,10 @@ def handle_proxy_start(config: RuntimeConfig, computer_id: str | None, specs: li
|
|
|
1633
1829
|
if started_ports:
|
|
1634
1830
|
_stop_proxy_ports(started_ports, stop_all=False)
|
|
1635
1831
|
return report_error(str(exc), status_code=exc.status_code)
|
|
1832
|
+
except Exception as exc:
|
|
1833
|
+
if started_ports:
|
|
1834
|
+
_stop_proxy_ports(started_ports, stop_all=False)
|
|
1835
|
+
return report_error(str(exc))
|
|
1636
1836
|
started_ports.append(local_port)
|
|
1637
1837
|
started.append({"computer_id": resolved_id, "computer": computer_id, "local_port": local_port, "remote_port": remote_port})
|
|
1638
1838
|
|
|
@@ -1988,7 +2188,7 @@ def _strip_terminal_noise(text: str) -> str:
|
|
|
1988
2188
|
filtered: list[str] = []
|
|
1989
2189
|
for line in lines:
|
|
1990
2190
|
stripped = line.rstrip("\r\n")
|
|
1991
|
-
if stripped
|
|
2191
|
+
if stripped.startswith("Running bootstrap command: export PS1='\\[\\033[1;34m\\]runtime@"):
|
|
1992
2192
|
continue
|
|
1993
2193
|
if stripped == "Connected! Press Ctrl+] to exit.":
|
|
1994
2194
|
continue
|
|
@@ -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.13
|
|
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
|
|
@@ -47,11 +47,16 @@ Or pass `--base-url` per command.
|
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
49
|
# Auth
|
|
50
|
+
runtime # first run in a real terminal prompts for email + verification code, then creates your first computer
|
|
51
|
+
runtime login # interactive email + verification code flow
|
|
50
52
|
runtime login you@example.com
|
|
51
53
|
runtime verify 123456
|
|
52
54
|
runtime whoami
|
|
53
55
|
runtime logout
|
|
54
56
|
runtime login --api-key rt_live_...
|
|
57
|
+
runtime api-keys list
|
|
58
|
+
runtime api-keys create "my laptop"
|
|
59
|
+
runtime api-keys revoke 123
|
|
55
60
|
|
|
56
61
|
# Computers
|
|
57
62
|
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
|