runtime-sdk 0.4.23__tar.gz → 0.4.25__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.23 → runtime_sdk-0.4.25}/PKG-INFO +1 -1
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/pyproject.toml +1 -1
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/scripts/runtime_sdk/cli.py +134 -162
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/scripts/runtime_sdk.egg-info/PKG-INFO +1 -1
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/README.md +0 -0
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/scripts/runtime_sdk/__init__.py +0 -0
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/scripts/runtime_sdk/client.py +0 -0
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/scripts/runtime_sdk/config.py +0 -0
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/scripts/runtime_sdk.egg-info/SOURCES.txt +0 -0
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/scripts/runtime_sdk.egg-info/dependency_links.txt +0 -0
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/scripts/runtime_sdk.egg-info/entry_points.txt +0 -0
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/scripts/runtime_sdk.egg-info/requires.txt +0 -0
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/scripts/runtime_sdk.egg-info/top_level.txt +0 -0
- {runtime_sdk-0.4.23 → runtime_sdk-0.4.25}/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.25
|
|
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
|
|
@@ -325,6 +325,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
325
325
|
)
|
|
326
326
|
completion_cmd.add_argument("shell", choices=["bash", "zsh", "fish"], help="Target shell")
|
|
327
327
|
|
|
328
|
+
# Hidden helper used by shell completion to list slugs, one per line.
|
|
329
|
+
subparsers.add_parser("_slugs", help=argparse.SUPPRESS)
|
|
330
|
+
|
|
328
331
|
parser.format_help = lambda: _format_top_help() # type: ignore[assignment]
|
|
329
332
|
return parser
|
|
330
333
|
|
|
@@ -571,6 +574,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
571
574
|
return handle_help(args.topic)
|
|
572
575
|
if args.command == "completion":
|
|
573
576
|
return handle_completion(args.shell)
|
|
577
|
+
if args.command == "_slugs":
|
|
578
|
+
return handle_slugs(config)
|
|
574
579
|
if args.command == "_proxy_agent":
|
|
575
580
|
return run_proxy_agent(
|
|
576
581
|
args.base_url,
|
|
@@ -884,22 +889,6 @@ def _prompt_typed_confirm(message: str, expected: str) -> bool:
|
|
|
884
889
|
return typed.strip().lower() == expected.strip().lower()
|
|
885
890
|
|
|
886
891
|
|
|
887
|
-
def _display_width(value: Any, minimum: int, maximum: int) -> int:
|
|
888
|
-
text = str(value or "")
|
|
889
|
-
return max(minimum, min(len(text), maximum))
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
def _fit_cell(value: Any, width: int) -> str:
|
|
893
|
-
text = str(value or "")
|
|
894
|
-
if width <= 0:
|
|
895
|
-
return ""
|
|
896
|
-
if len(text) > width:
|
|
897
|
-
if width == 1:
|
|
898
|
-
return "…"
|
|
899
|
-
text = text[: width - 1] + "…"
|
|
900
|
-
return text.ljust(width)
|
|
901
|
-
|
|
902
|
-
|
|
903
892
|
def _computer_power_state(c: dict[str, Any]) -> str:
|
|
904
893
|
power = c.get("power_state")
|
|
905
894
|
if power is not None:
|
|
@@ -1118,157 +1107,97 @@ def _computer_sort_key(c: dict[str, Any]) -> tuple[int, str, str]:
|
|
|
1118
1107
|
|
|
1119
1108
|
|
|
1120
1109
|
|
|
1121
|
-
def
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
url_budget = max(16, term_width - reserved)
|
|
1134
|
-
url_width = max(
|
|
1135
|
-
len("url"),
|
|
1136
|
-
min(max(len(_computer_url_label(c)) for c in computers), url_budget),
|
|
1137
|
-
)
|
|
1138
|
-
return {
|
|
1139
|
-
"slug": slug_width,
|
|
1140
|
-
"status": status_width,
|
|
1141
|
-
"public_url": url_width,
|
|
1142
|
-
"created_at": created_width,
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
def _format_computer_row(c: dict[str, Any], widths: dict[str, int]) -> str:
|
|
1147
|
-
return " │ ".join(
|
|
1148
|
-
[
|
|
1149
|
-
_fit_cell(_computer_name(c), widths["slug"]),
|
|
1150
|
-
_fit_cell(_computer_status_label(c), widths["status"]),
|
|
1151
|
-
_fit_cell(_computer_url_label(c), widths["public_url"]),
|
|
1152
|
-
_fit_cell(_computer_created_label(c), widths["created_at"]),
|
|
1153
|
-
]
|
|
1154
|
-
)
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
def _computer_table_prompt(prompt: str, computers: list[dict[str, Any]]) -> tuple[str, dict[str, int]]:
|
|
1158
|
-
widths = _computer_table_layout(computers)
|
|
1159
|
-
header = " │ ".join(
|
|
1160
|
-
[
|
|
1161
|
-
_fit_cell("name", widths["slug"]),
|
|
1162
|
-
_fit_cell("state", widths["status"]),
|
|
1163
|
-
_fit_cell("url", widths["public_url"]),
|
|
1164
|
-
_fit_cell("age", widths["created_at"]),
|
|
1165
|
-
]
|
|
1166
|
-
)
|
|
1167
|
-
divider = "─" * len(header)
|
|
1168
|
-
return f"{prompt}\n{header}\n{divider}", widths
|
|
1110
|
+
def _picker_status_color(status: str) -> str:
|
|
1111
|
+
if "cold" in status:
|
|
1112
|
+
return "fg:#60a5fa" # cyan-blue
|
|
1113
|
+
if "warm" in status or "running" in status:
|
|
1114
|
+
return "fg:#22c55e" # green
|
|
1115
|
+
if "archiving" in status or "restoring" in status:
|
|
1116
|
+
return "fg:#eab308" # yellow
|
|
1117
|
+
if "creating" in status:
|
|
1118
|
+
return "fg:#a855f7" # magenta
|
|
1119
|
+
if "orphaned" in status:
|
|
1120
|
+
return "fg:#ef4444" # red
|
|
1121
|
+
return "fg:#e5e7eb"
|
|
1169
1122
|
|
|
1170
1123
|
|
|
1124
|
+
def _picker_widths(computers: list[dict[str, Any]]) -> dict[str, int]:
|
|
1125
|
+
"""Compute aligned column widths for the interactive picker rows.
|
|
1171
1126
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1127
|
+
We budget total width to leave room for questionary's pointer + indicator.
|
|
1128
|
+
"""
|
|
1129
|
+
term_width = shutil.get_terminal_size((100, 20)).columns
|
|
1130
|
+
name_w = max(len("name"), max((len(_computer_name(c)) for c in computers), default=0))
|
|
1131
|
+
name_w = min(name_w, 24)
|
|
1132
|
+
status_w = max(len("state"), max((len(_computer_status_label(c)) for c in computers), default=0))
|
|
1133
|
+
status_w = min(status_w, 14)
|
|
1134
|
+
age_w = max(len("age"), max((len(_computer_created_label(c)) for c in computers), default=0))
|
|
1135
|
+
age_w = min(age_w, 6)
|
|
1136
|
+
# pointer + indicator + 2 gaps * 3 + status dot (2 chars) + padding
|
|
1137
|
+
reserved = 6 + name_w + status_w + age_w + 2 * 3 + 2
|
|
1138
|
+
url_w = max(len("url"), min(max((len(_computer_url_label(c)) for c in computers), default=16), term_width - reserved))
|
|
1139
|
+
url_w = max(10, url_w)
|
|
1140
|
+
return {"name": name_w, "status": status_w, "url": url_w, "age": age_w}
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
def _computer_choice_title(c: dict[str, Any], widths: dict[str, int]) -> list[tuple[str, str]]:
|
|
1144
|
+
"""Return a styled list-of-tuples title so the picker rows look tabular.
|
|
1145
|
+
|
|
1146
|
+
Columns: name · state (colored dot + label) · url · age.
|
|
1147
|
+
"""
|
|
1148
|
+
name = _computer_name(c)
|
|
1174
1149
|
status = _computer_status_label(c)
|
|
1175
|
-
url = _computer_url_label(c)
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
body = Text()
|
|
1195
|
-
body.append(f"total {len(computers)}", style="bold white")
|
|
1196
|
-
if counts:
|
|
1197
|
-
body.append(" ")
|
|
1198
|
-
first = True
|
|
1199
|
-
status_styles = {
|
|
1200
|
-
"warm": "bold green",
|
|
1201
|
-
"cold": "bold cyan",
|
|
1202
|
-
"running": "bold green",
|
|
1203
|
-
"archiving": "bold yellow",
|
|
1204
|
-
"restoring": "bold yellow",
|
|
1205
|
-
"creating": "bold magenta",
|
|
1206
|
-
"orphaned": "bold red",
|
|
1207
|
-
}
|
|
1208
|
-
for status in sorted(counts):
|
|
1209
|
-
if not first:
|
|
1210
|
-
body.append(" • ", style="dim")
|
|
1211
|
-
first = False
|
|
1212
|
-
status_style = "bold white"
|
|
1213
|
-
for token, style in status_styles.items():
|
|
1214
|
-
if token in status:
|
|
1215
|
-
status_style = style
|
|
1216
|
-
break
|
|
1217
|
-
body.append(status, style=status_style)
|
|
1218
|
-
body.append(f" {counts[status]}", style="white")
|
|
1219
|
-
|
|
1220
|
-
_UI.console().print(
|
|
1221
|
-
Panel(body, title="runtime", border_style="blue", expand=False, padding=(0, 1))
|
|
1222
|
-
)
|
|
1223
|
-
|
|
1150
|
+
url = _computer_url_label(c) or "—"
|
|
1151
|
+
age = _computer_created_label(c) or ""
|
|
1152
|
+
color = _picker_status_color(status)
|
|
1153
|
+
dot = _computer_status_dot(status)
|
|
1154
|
+
# Keep each column within its budget; truncate with ellipsis on overflow.
|
|
1155
|
+
def fit(text: str, width: int, align: str = "<") -> str:
|
|
1156
|
+
text = str(text or "")
|
|
1157
|
+
if len(text) > width:
|
|
1158
|
+
return text[: max(1, width - 1)] + "…"
|
|
1159
|
+
return format(text, f"{align}{width}")
|
|
1160
|
+
return [
|
|
1161
|
+
("", fit(name, widths["name"]) + " "),
|
|
1162
|
+
(color, f"{dot} "),
|
|
1163
|
+
(color, fit(status, widths["status"])),
|
|
1164
|
+
("", " "),
|
|
1165
|
+
("fg:#cbd5e1", fit(url, widths["url"])),
|
|
1166
|
+
("", " "),
|
|
1167
|
+
("fg:#6b7280", fit(age, widths["age"], ">")),
|
|
1168
|
+
]
|
|
1224
1169
|
|
|
1225
1170
|
|
|
1226
|
-
def _render_computer_table(computers: list[dict[str, Any]], *, title: str | None = None) -> None:
|
|
1227
|
-
mods = _UI.rich()
|
|
1228
|
-
Table, box = mods["Table"], mods["box"]
|
|
1229
|
-
table = Table(
|
|
1230
|
-
title=title,
|
|
1231
|
-
box=box.MINIMAL_DOUBLE_HEAD,
|
|
1232
|
-
expand=True,
|
|
1233
|
-
show_lines=False,
|
|
1234
|
-
header_style="bold #7dd3fc",
|
|
1235
|
-
title_style="bold white",
|
|
1236
|
-
pad_edge=False,
|
|
1237
|
-
collapse_padding=True,
|
|
1238
|
-
row_styles=["", "dim"],
|
|
1239
|
-
)
|
|
1240
|
-
table.add_column("Name", style="bold white", no_wrap=True, ratio=2)
|
|
1241
|
-
table.add_column("State", no_wrap=True, ratio=2)
|
|
1242
|
-
table.add_column("URL", style="white", overflow="fold", ratio=4)
|
|
1243
|
-
table.add_column("Age", style="dim", no_wrap=True, justify="right", ratio=1)
|
|
1244
|
-
|
|
1245
|
-
for computer in sorted(computers, key=_computer_sort_key):
|
|
1246
|
-
status = _computer_status_label(computer)
|
|
1247
|
-
status_style = _computer_status_style(status)
|
|
1248
|
-
table.add_row(
|
|
1249
|
-
_computer_name(computer),
|
|
1250
|
-
f"[{status_style}]{_computer_status_dot(status)} {status}[/{status_style}]",
|
|
1251
|
-
_computer_url_label(computer),
|
|
1252
|
-
_computer_created_label(computer),
|
|
1253
|
-
)
|
|
1254
1171
|
|
|
1255
|
-
_UI.console().print(table)
|
|
1256
1172
|
|
|
1257
1173
|
|
|
1258
1174
|
def _select_prompt(message: str, choices: list[Any], *, instruction: str | None = None) -> Any:
|
|
1259
1175
|
# questionary rejects use_jk_keys together with use_search_filter because
|
|
1260
1176
|
# j/k would be swallowed by the search prefix. We pick search over vim keys.
|
|
1261
|
-
|
|
1177
|
+
question = _UI.q().select(
|
|
1262
1178
|
message,
|
|
1263
1179
|
choices=choices,
|
|
1264
1180
|
qmark="●",
|
|
1265
1181
|
pointer="❯",
|
|
1266
|
-
instruction=instruction or "↑↓
|
|
1182
|
+
instruction=instruction or "↑↓ · type to search · enter select · esc back",
|
|
1267
1183
|
use_indicator=True,
|
|
1268
1184
|
use_jk_keys=False,
|
|
1269
1185
|
use_search_filter=True,
|
|
1270
1186
|
style=_UI.style(),
|
|
1271
1187
|
)
|
|
1188
|
+
# Bind ESC explicitly — questionary's select prompt does not bind it by
|
|
1189
|
+
# default. With eager=True the binding fires before prompt_toolkit's
|
|
1190
|
+
# meta-key timeout. .unsafe_ask() returns the `result` we pass to exit().
|
|
1191
|
+
with contextlib.suppress(Exception):
|
|
1192
|
+
from prompt_toolkit.keys import Keys
|
|
1193
|
+
|
|
1194
|
+
bindings = question.application.key_bindings
|
|
1195
|
+
|
|
1196
|
+
@bindings.add(Keys.Escape, eager=True)
|
|
1197
|
+
def _escape(event: Any) -> None:
|
|
1198
|
+
event.app.exit(result=None)
|
|
1199
|
+
|
|
1200
|
+
return question
|
|
1272
1201
|
|
|
1273
1202
|
|
|
1274
1203
|
def _pick_computer(
|
|
@@ -1278,8 +1207,8 @@ def _pick_computer(
|
|
|
1278
1207
|
) -> dict[str, Any] | None:
|
|
1279
1208
|
"""Fetch computers and let the user arrow-key through them.
|
|
1280
1209
|
|
|
1281
|
-
Returns the picked computer dict, or None
|
|
1282
|
-
|
|
1210
|
+
Returns the picked computer dict, or None on ESC / "← cancel".
|
|
1211
|
+
Ctrl-C propagates as KeyboardInterrupt.
|
|
1283
1212
|
"""
|
|
1284
1213
|
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1285
1214
|
payload = client.list_computers()
|
|
@@ -1294,8 +1223,7 @@ def _pick_computer(
|
|
|
1294
1223
|
return None
|
|
1295
1224
|
|
|
1296
1225
|
questionary = _UI.q()
|
|
1297
|
-
widths =
|
|
1298
|
-
_render_computer_table(computers)
|
|
1226
|
+
widths = _picker_widths(computers)
|
|
1299
1227
|
choices = [
|
|
1300
1228
|
questionary.Choice(title=_computer_choice_title(c, widths), value=c) for c in computers
|
|
1301
1229
|
]
|
|
@@ -2972,7 +2900,11 @@ def _send_terminal_resize(ws: Any) -> None:
|
|
|
2972
2900
|
|
|
2973
2901
|
|
|
2974
2902
|
def _interactive_list(config: RuntimeConfig) -> int:
|
|
2975
|
-
"""
|
|
2903
|
+
"""Interactive top-level menu: picker is the display, no separate table.
|
|
2904
|
+
|
|
2905
|
+
ESC / Ctrl+C / "✕ quit" exit. "+ new computer" creates one. "↻ refresh"
|
|
2906
|
+
re-fetches. Picking a VM drops into its action submenu.
|
|
2907
|
+
"""
|
|
2976
2908
|
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
2977
2909
|
questionary = _UI.q()
|
|
2978
2910
|
|
|
@@ -2987,9 +2919,19 @@ def _interactive_list(config: RuntimeConfig) -> int:
|
|
|
2987
2919
|
return handle_create(config, None, None, None, None)
|
|
2988
2920
|
|
|
2989
2921
|
computers = sorted(computers, key=_computer_sort_key)
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2922
|
+
widths = _picker_widths(computers)
|
|
2923
|
+
|
|
2924
|
+
# Title summarises counts so the picker alone carries enough context.
|
|
2925
|
+
total = len(computers)
|
|
2926
|
+
warm = sum(1 for c in computers if "warm" in _computer_status_label(c) or "running" in _computer_status_label(c))
|
|
2927
|
+
cold = sum(1 for c in computers if "cold" in _computer_status_label(c))
|
|
2928
|
+
bits = [f"{total} total"]
|
|
2929
|
+
if warm:
|
|
2930
|
+
bits.append(f"{warm} warm")
|
|
2931
|
+
if cold:
|
|
2932
|
+
bits.append(f"{cold} cold")
|
|
2933
|
+
summary = " · ".join(bits)
|
|
2934
|
+
|
|
2993
2935
|
choices: list[Any] = [
|
|
2994
2936
|
questionary.Choice(title=_computer_choice_title(c, widths), value=c)
|
|
2995
2937
|
for c in computers
|
|
@@ -2999,9 +2941,9 @@ def _interactive_list(config: RuntimeConfig) -> int:
|
|
|
2999
2941
|
choices.append(questionary.Choice(title="↻ refresh", value="__refresh__"))
|
|
3000
2942
|
choices.append(questionary.Choice(title="✕ quit", value="__quit__"))
|
|
3001
2943
|
|
|
3002
|
-
picked = _select_prompt("Pick a computer", choices).unsafe_ask()
|
|
2944
|
+
picked = _select_prompt(f"Pick a computer ({summary})", choices).unsafe_ask()
|
|
3003
2945
|
|
|
3004
|
-
if picked == "__quit__":
|
|
2946
|
+
if picked is None or picked == "__quit__":
|
|
3005
2947
|
return 0
|
|
3006
2948
|
if picked == "__refresh__":
|
|
3007
2949
|
continue
|
|
@@ -3058,7 +3000,7 @@ def _vm_detail_menu(client: RuntimeClient, vm: dict[str, Any]) -> str:
|
|
|
3058
3000
|
choices,
|
|
3059
3001
|
).unsafe_ask()
|
|
3060
3002
|
|
|
3061
|
-
if action == "__back__":
|
|
3003
|
+
if action is None or action == "__back__":
|
|
3062
3004
|
return "__back__"
|
|
3063
3005
|
if action == "__quit__":
|
|
3064
3006
|
return "__quit__"
|
|
@@ -3270,9 +3212,9 @@ _runtime_complete() {
|
|
|
3270
3212
|
fi
|
|
3271
3213
|
sub="${COMP_WORDS[1]}"
|
|
3272
3214
|
case "$sub" in
|
|
3273
|
-
info|url|start|enter|run|publish|delete|rm|
|
|
3215
|
+
info|url|start|enter|run|publish|delete|rm|startup|service)
|
|
3274
3216
|
local ids
|
|
3275
|
-
ids=$(runtime
|
|
3217
|
+
ids=$(runtime _slugs 2>/dev/null)
|
|
3276
3218
|
COMPREPLY=( $(compgen -W "$ids" -- "$cur") )
|
|
3277
3219
|
;;
|
|
3278
3220
|
completion)
|
|
@@ -3281,6 +3223,10 @@ _runtime_complete() {
|
|
|
3281
3223
|
share)
|
|
3282
3224
|
if [[ ${COMP_CWORD} -eq 2 ]]; then
|
|
3283
3225
|
COMPREPLY=( $(compgen -W "public private" -- "$cur") )
|
|
3226
|
+
else
|
|
3227
|
+
local ids
|
|
3228
|
+
ids=$(runtime _slugs 2>/dev/null)
|
|
3229
|
+
COMPREPLY=( $(compgen -W "$ids" -- "$cur") )
|
|
3284
3230
|
fi
|
|
3285
3231
|
;;
|
|
3286
3232
|
esac
|
|
@@ -3331,11 +3277,17 @@ _runtime() {
|
|
|
3331
3277
|
case ${words[1]} in
|
|
3332
3278
|
info|url|start|enter|run|publish|delete|rm|startup|service)
|
|
3333
3279
|
local -a ids
|
|
3334
|
-
ids=(${(f)"$(runtime
|
|
3280
|
+
ids=(${(f)"$(runtime _slugs 2>/dev/null)"})
|
|
3335
3281
|
_describe -t slugs 'computer' ids
|
|
3336
3282
|
;;
|
|
3337
3283
|
share)
|
|
3338
|
-
|
|
3284
|
+
if (( CURRENT == 2 )); then
|
|
3285
|
+
_values 'visibility' public private
|
|
3286
|
+
else
|
|
3287
|
+
local -a ids
|
|
3288
|
+
ids=(${(f)"$(runtime _slugs 2>/dev/null)"})
|
|
3289
|
+
_describe -t slugs 'computer' ids
|
|
3290
|
+
fi
|
|
3339
3291
|
;;
|
|
3340
3292
|
completion)
|
|
3341
3293
|
_values 'shell' bash zsh fish
|
|
@@ -3350,7 +3302,7 @@ _runtime "$@"
|
|
|
3350
3302
|
|
|
3351
3303
|
_COMPLETION_FISH = r"""# fish completion for runtime
|
|
3352
3304
|
function __runtime_slugs
|
|
3353
|
-
runtime
|
|
3305
|
+
runtime _slugs 2>/dev/null
|
|
3354
3306
|
end
|
|
3355
3307
|
|
|
3356
3308
|
complete -c runtime -f
|
|
@@ -3387,5 +3339,25 @@ def handle_completion(shell: str) -> int:
|
|
|
3387
3339
|
return 0
|
|
3388
3340
|
|
|
3389
3341
|
|
|
3342
|
+
def handle_slugs(config: RuntimeConfig) -> int:
|
|
3343
|
+
"""Print one slug per line. Used by shell completion scripts. Silent on errors."""
|
|
3344
|
+
if not config.api_key:
|
|
3345
|
+
return 0
|
|
3346
|
+
try:
|
|
3347
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
3348
|
+
payload = client.list_computers()
|
|
3349
|
+
except Exception:
|
|
3350
|
+
return 0
|
|
3351
|
+
computers = payload.get("computers") or []
|
|
3352
|
+
if not isinstance(computers, list):
|
|
3353
|
+
return 0
|
|
3354
|
+
for c in computers:
|
|
3355
|
+
if isinstance(c, dict):
|
|
3356
|
+
slug = str(c.get("slug") or "").strip()
|
|
3357
|
+
if slug:
|
|
3358
|
+
sys.stdout.write(slug + "\n")
|
|
3359
|
+
return 0
|
|
3360
|
+
|
|
3361
|
+
|
|
3390
3362
|
if __name__ == "__main__":
|
|
3391
3363
|
raise SystemExit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runtime-sdk
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.25
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|