runtime-sdk 0.4.35__tar.gz → 0.4.36__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.35 → runtime_sdk-0.4.36}/PKG-INFO +5 -2
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/README.md +4 -1
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/pyproject.toml +1 -1
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/scripts/runtime_sdk/cli.py +84 -21
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/scripts/runtime_sdk/client.py +5 -2
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/scripts/runtime_sdk.egg-info/PKG-INFO +5 -2
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/scripts/runtime_sdk/__init__.py +0 -0
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/scripts/runtime_sdk/config.py +0 -0
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/scripts/runtime_sdk.egg-info/SOURCES.txt +0 -0
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/scripts/runtime_sdk.egg-info/dependency_links.txt +0 -0
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/scripts/runtime_sdk.egg-info/entry_points.txt +0 -0
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/scripts/runtime_sdk.egg-info/requires.txt +0 -0
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/scripts/runtime_sdk.egg-info/top_level.txt +0 -0
- {runtime_sdk-0.4.35 → runtime_sdk-0.4.36}/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.36
|
|
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
|
|
@@ -76,7 +76,9 @@ runtime switch -c feature/login --repo owner/repo # create a new branch worksp
|
|
|
76
76
|
runtime checkout --repo owner/repo feature/login # alias for switch
|
|
77
77
|
runtime create # creates a computer with the starter app already published
|
|
78
78
|
runtime create myapp --command "python3 app.py" --cwd /home/ubuntu --port 3000
|
|
79
|
-
runtime enter <name-or-id> # accepts slug/name
|
|
79
|
+
runtime enter <name-or-id> # open an interactive shell; accepts slug/name or computer id
|
|
80
|
+
runtime enter <name-or-id> -- claude # open a real TTY and run an interactive command
|
|
81
|
+
runtime enter <name-or-id> -- python # use enter for REPLs, agents, prompts, and full-screen apps
|
|
80
82
|
runtime list
|
|
81
83
|
runtime info <id>
|
|
82
84
|
runtime share public <id>
|
|
@@ -86,6 +88,7 @@ runtime run <id> "echo hello" # one-shot foreground command only
|
|
|
86
88
|
runtime run <id> "apt install -y nodejs" --uid 0
|
|
87
89
|
runtime exec <id> -- bash -lc 'for i in 1 2 3; do echo $i; sleep 1; done'
|
|
88
90
|
printf 'hello' | runtime exec <id> --stdin -- cat
|
|
91
|
+
# Use runtime exec for automation and exact exit codes; use runtime enter -- <cmd> for interactive TTY commands.
|
|
89
92
|
runtime files ls <id> /home/ubuntu
|
|
90
93
|
runtime files read <id> /home/ubuntu/app.py --output app.py
|
|
91
94
|
runtime files write <id> /home/ubuntu/app.py --input app.py --mode 0644
|
|
@@ -54,7 +54,9 @@ runtime switch -c feature/login --repo owner/repo # create a new branch worksp
|
|
|
54
54
|
runtime checkout --repo owner/repo feature/login # alias for switch
|
|
55
55
|
runtime create # creates a computer with the starter app already published
|
|
56
56
|
runtime create myapp --command "python3 app.py" --cwd /home/ubuntu --port 3000
|
|
57
|
-
runtime enter <name-or-id> # accepts slug/name
|
|
57
|
+
runtime enter <name-or-id> # open an interactive shell; accepts slug/name or computer id
|
|
58
|
+
runtime enter <name-or-id> -- claude # open a real TTY and run an interactive command
|
|
59
|
+
runtime enter <name-or-id> -- python # use enter for REPLs, agents, prompts, and full-screen apps
|
|
58
60
|
runtime list
|
|
59
61
|
runtime info <id>
|
|
60
62
|
runtime share public <id>
|
|
@@ -64,6 +66,7 @@ runtime run <id> "echo hello" # one-shot foreground command only
|
|
|
64
66
|
runtime run <id> "apt install -y nodejs" --uid 0
|
|
65
67
|
runtime exec <id> -- bash -lc 'for i in 1 2 3; do echo $i; sleep 1; done'
|
|
66
68
|
printf 'hello' | runtime exec <id> --stdin -- cat
|
|
69
|
+
# Use runtime exec for automation and exact exit codes; use runtime enter -- <cmd> for interactive TTY commands.
|
|
67
70
|
runtime files ls <id> /home/ubuntu
|
|
68
71
|
runtime files read <id> /home/ubuntu/app.py --output app.py
|
|
69
72
|
runtime files write <id> /home/ubuntu/app.py --input app.py --mode 0644
|
|
@@ -189,9 +189,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
189
189
|
secrets_set = secrets_subparsers.add_parser("set", help="Create or replace a secret set from KEY=VALUE pairs")
|
|
190
190
|
secrets_set.add_argument("slug", nargs="?", default=None, help="Secret set slug")
|
|
191
191
|
secrets_set.add_argument("pairs", nargs="*", help="KEY=VALUE pairs")
|
|
192
|
+
secrets_set.add_argument("--proxy", action="append", default=[], metavar="KEY=HOST", help="Deliver KEY through the egress proxy and inject it only for HOST")
|
|
192
193
|
secrets_import = secrets_subparsers.add_parser("import", help="Create or replace a secret set from a .env file")
|
|
193
194
|
secrets_import.add_argument("slug", nargs="?", default=None, help="Secret set slug")
|
|
194
195
|
secrets_import.add_argument("--env-file", required=True, help="Path to .env file")
|
|
196
|
+
secrets_import.add_argument("--proxy", action="append", default=[], metavar="KEY=HOST", help="Deliver KEY through the egress proxy and inject it only for HOST")
|
|
195
197
|
secrets_delete = secrets_subparsers.add_parser("delete", aliases=["rm"], help="Delete a secret set")
|
|
196
198
|
secrets_delete.add_argument("slug", nargs="?", default=None, help="Secret set slug")
|
|
197
199
|
|
|
@@ -238,8 +240,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
238
240
|
help="Re-render the table every N seconds (default 2)",
|
|
239
241
|
)
|
|
240
242
|
|
|
241
|
-
enter_cmd = subparsers.add_parser(
|
|
243
|
+
enter_cmd = subparsers.add_parser(
|
|
244
|
+
"enter",
|
|
245
|
+
help="Enter a computer's console",
|
|
246
|
+
epilog="Use `runtime exec` for exact exit codes; use `runtime enter <id> -- <cmd>` for interactive TTY commands.",
|
|
247
|
+
)
|
|
242
248
|
enter_cmd.add_argument("id", nargs="?", default=None, help="Computer ID (hostname)")
|
|
249
|
+
enter_cmd.add_argument("enter_command", nargs="*", help="Command to run in the interactive terminal")
|
|
243
250
|
|
|
244
251
|
info_cmd = subparsers.add_parser("info", help="Get computer details")
|
|
245
252
|
info_cmd.add_argument("id", nargs="?", default=None, help="Computer ID (hostname)")
|
|
@@ -408,7 +415,7 @@ _HELP_SECTIONS: tuple[tuple[str, tuple[tuple[str, str], ...]], ...] = (
|
|
|
408
415
|
("info [id]", "Show computer details"),
|
|
409
416
|
("url [id]", "Show a computer's public URL and live published port"),
|
|
410
417
|
("start [id]", "Wake a cold computer"),
|
|
411
|
-
("enter [id]", "Enter a computer's console
|
|
418
|
+
("enter [id] [-- <cmd>]", "Enter a computer's console or interactive TTY command"),
|
|
412
419
|
("run [id] <cmd>", "Run a one-shot command"),
|
|
413
420
|
("exec [id] -- <cmd>", "Stream stdout/stderr and optionally pipe stdin"),
|
|
414
421
|
("publish [id] <port> -- <cmd>", "Run and publish a durable service command"),
|
|
@@ -580,9 +587,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
580
587
|
if args.secrets_action == "show":
|
|
581
588
|
return handle_secrets_show(config, args.slug)
|
|
582
589
|
if args.secrets_action == "set":
|
|
583
|
-
return handle_secrets_set(config, args.slug, args.pairs)
|
|
590
|
+
return handle_secrets_set(config, args.slug, args.pairs, args.proxy)
|
|
584
591
|
if args.secrets_action == "import":
|
|
585
|
-
return handle_secrets_import(config, args.slug, args.env_file)
|
|
592
|
+
return handle_secrets_import(config, args.slug, args.env_file, args.proxy)
|
|
586
593
|
if args.secrets_action in ("delete", "rm"):
|
|
587
594
|
return handle_secrets_delete(config, args.slug)
|
|
588
595
|
return report_error("unknown secrets command")
|
|
@@ -621,7 +628,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
621
628
|
if args.command == "start":
|
|
622
629
|
return handle_start(config, args.id)
|
|
623
630
|
if args.command == "enter":
|
|
624
|
-
return handle_enter(config, args.id)
|
|
631
|
+
return handle_enter(config, args.id, args.enter_command)
|
|
625
632
|
if args.command in ("delete", "rm"):
|
|
626
633
|
return handle_delete(config, args.id, force=getattr(args, "force", False))
|
|
627
634
|
if args.command == "run":
|
|
@@ -1591,7 +1598,15 @@ def _render_secret_set_panel(payload: dict[str, Any]) -> None:
|
|
|
1591
1598
|
body.append(f"{'slug':<12}", style="dim")
|
|
1592
1599
|
body.append(f"{payload.get('slug') or '—'}\n")
|
|
1593
1600
|
body.append(f"{'keys':<12}", style="dim")
|
|
1594
|
-
|
|
1601
|
+
deliveries = payload.get("deliveries") or {}
|
|
1602
|
+
lines = []
|
|
1603
|
+
for key in payload.get("keys") or []:
|
|
1604
|
+
meta = deliveries.get(str(key)) if isinstance(deliveries, dict) else None
|
|
1605
|
+
if isinstance(meta, dict) and meta.get("delivery") == "proxy":
|
|
1606
|
+
lines.append(f"{key} proxy {meta.get('target_host') or '—'}")
|
|
1607
|
+
else:
|
|
1608
|
+
lines.append(f"{key} env")
|
|
1609
|
+
body.append("\n".join(lines) or "—")
|
|
1595
1610
|
_UI.console().print(Panel(body, title="secret set", border_style="cyan", expand=False))
|
|
1596
1611
|
|
|
1597
1612
|
|
|
@@ -1880,6 +1895,26 @@ def _parse_secret_pairs(pairs: list[str] | None) -> dict[str, str]:
|
|
|
1880
1895
|
|
|
1881
1896
|
|
|
1882
1897
|
|
|
1898
|
+
def _parse_secret_delivery(proxy_specs: list[str] | None, values: dict[str, str]) -> dict[str, dict[str, str]]:
|
|
1899
|
+
delivery: dict[str, dict[str, str]] = {}
|
|
1900
|
+
for spec in proxy_specs or []:
|
|
1901
|
+
if "=" not in spec:
|
|
1902
|
+
raise RuntimeConfigError(f"invalid proxy delivery: {spec}")
|
|
1903
|
+
key, host = spec.split("=", 1)
|
|
1904
|
+
key = key.strip()
|
|
1905
|
+
host = host.strip()
|
|
1906
|
+
if not key or not host or key not in values:
|
|
1907
|
+
raise RuntimeConfigError(f"invalid proxy delivery: {spec}")
|
|
1908
|
+
delivery[key] = {
|
|
1909
|
+
"delivery": "proxy",
|
|
1910
|
+
"target_host": host,
|
|
1911
|
+
"header": "Authorization",
|
|
1912
|
+
"scheme": "Bearer",
|
|
1913
|
+
}
|
|
1914
|
+
return delivery
|
|
1915
|
+
|
|
1916
|
+
|
|
1917
|
+
|
|
1883
1918
|
def _parse_env_file(path: str) -> dict[str, str]:
|
|
1884
1919
|
try:
|
|
1885
1920
|
lines = Path(path).read_text(encoding="utf-8").splitlines()
|
|
@@ -1929,7 +1964,7 @@ def handle_secrets_show(config: RuntimeConfig, slug: str | None) -> int:
|
|
|
1929
1964
|
|
|
1930
1965
|
|
|
1931
1966
|
|
|
1932
|
-
def handle_secrets_set(config: RuntimeConfig, slug: str | None, pairs: list[str] | None) -> int:
|
|
1967
|
+
def handle_secrets_set(config: RuntimeConfig, slug: str | None, pairs: list[str] | None, proxy_specs: list[str] | None) -> int:
|
|
1933
1968
|
if (err := _require_api_key(config)) is not None:
|
|
1934
1969
|
return err
|
|
1935
1970
|
resolved_slug = _coerce_secret_set_slug(slug)
|
|
@@ -1942,8 +1977,9 @@ def handle_secrets_set(config: RuntimeConfig, slug: str | None, pairs: list[str]
|
|
|
1942
1977
|
values = _parse_secret_pairs(pairs)
|
|
1943
1978
|
if not values:
|
|
1944
1979
|
return report_error("at least one KEY=VALUE pair is required")
|
|
1980
|
+
delivery = _parse_secret_delivery(proxy_specs, values)
|
|
1945
1981
|
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1946
|
-
secret_set = client.put_secret_set(resolved_slug, values)
|
|
1982
|
+
secret_set = client.put_secret_set(resolved_slug, values, delivery)
|
|
1947
1983
|
return report_success(
|
|
1948
1984
|
{"secret_set": secret_set},
|
|
1949
1985
|
lambda payload: _UI.console().print(f"[green]✓[/green] saved [bold]{payload['secret_set'].get('slug', resolved_slug)}[/bold]"),
|
|
@@ -1951,7 +1987,7 @@ def handle_secrets_set(config: RuntimeConfig, slug: str | None, pairs: list[str]
|
|
|
1951
1987
|
|
|
1952
1988
|
|
|
1953
1989
|
|
|
1954
|
-
def handle_secrets_import(config: RuntimeConfig, slug: str | None, env_file: str) -> int:
|
|
1990
|
+
def handle_secrets_import(config: RuntimeConfig, slug: str | None, env_file: str, proxy_specs: list[str] | None) -> int:
|
|
1955
1991
|
if (err := _require_api_key(config)) is not None:
|
|
1956
1992
|
return err
|
|
1957
1993
|
resolved_slug = _coerce_secret_set_slug(slug)
|
|
@@ -1964,8 +2000,9 @@ def handle_secrets_import(config: RuntimeConfig, slug: str | None, env_file: str
|
|
|
1964
2000
|
values = _parse_env_file(env_file)
|
|
1965
2001
|
if not values:
|
|
1966
2002
|
return report_error("env file did not contain any secrets")
|
|
2003
|
+
delivery = _parse_secret_delivery(proxy_specs, values)
|
|
1967
2004
|
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1968
|
-
secret_set = client.put_secret_set(resolved_slug, values)
|
|
2005
|
+
secret_set = client.put_secret_set(resolved_slug, values, delivery)
|
|
1969
2006
|
return report_success(
|
|
1970
2007
|
{"secret_set": secret_set},
|
|
1971
2008
|
lambda payload: _UI.console().print(f"[green]✓[/green] saved [bold]{payload['secret_set'].get('slug', resolved_slug)}[/bold] from [bold]{env_file}[/bold]"),
|
|
@@ -2585,12 +2622,14 @@ def handle_start(config: RuntimeConfig, computer_id: str | None) -> int:
|
|
|
2585
2622
|
return report_success(result, render)
|
|
2586
2623
|
|
|
2587
2624
|
|
|
2588
|
-
def handle_enter(config: RuntimeConfig, computer_id: str | None) -> int:
|
|
2625
|
+
def handle_enter(config: RuntimeConfig, computer_id: str | None, command_parts: list[str] | None = None) -> int:
|
|
2589
2626
|
if (err := _require_api_key(config)) is not None:
|
|
2590
2627
|
return err
|
|
2591
2628
|
if not _interactive():
|
|
2592
2629
|
return report_error("enter requires an interactive terminal")
|
|
2593
2630
|
|
|
2631
|
+
command = _command_from_remainder(command_parts)
|
|
2632
|
+
|
|
2594
2633
|
if computer_id is None:
|
|
2595
2634
|
picked = _pick_computer(
|
|
2596
2635
|
config,
|
|
@@ -2605,6 +2644,8 @@ def handle_enter(config: RuntimeConfig, computer_id: str | None) -> int:
|
|
|
2605
2644
|
|
|
2606
2645
|
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
2607
2646
|
computer_id = _resolve_computer_ref(client, str(computer_id))
|
|
2647
|
+
if command:
|
|
2648
|
+
return _enter_computer(config, str(computer_id), command=command)
|
|
2608
2649
|
return _enter_computer(config, str(computer_id))
|
|
2609
2650
|
|
|
2610
2651
|
|
|
@@ -3190,7 +3231,7 @@ def _handle_proxy_client(base_url: str, api_key: str, computer_id: str, remote_p
|
|
|
3190
3231
|
conn.close()
|
|
3191
3232
|
|
|
3192
3233
|
|
|
3193
|
-
def _enter_computer(config: RuntimeConfig, computer_id: str) -> int:
|
|
3234
|
+
def _enter_computer(config: RuntimeConfig, computer_id: str, *, command: str | None = None) -> int:
|
|
3194
3235
|
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
3195
3236
|
ticket_payload = _with_spinner(
|
|
3196
3237
|
f"preparing console for {computer_id}…",
|
|
@@ -3203,7 +3244,7 @@ def _enter_computer(config: RuntimeConfig, computer_id: str) -> int:
|
|
|
3203
3244
|
_UI.console().print("[cyan]●[/cyan] Connecting to console...")
|
|
3204
3245
|
|
|
3205
3246
|
try:
|
|
3206
|
-
return _run_terminal_session(ws_url)
|
|
3247
|
+
return _run_terminal_session(ws_url, bootstrap_command=command)
|
|
3207
3248
|
except RuntimeAPIError as exc:
|
|
3208
3249
|
return report_error(str(exc), status_code=exc.status_code)
|
|
3209
3250
|
except (InvalidHandshake, TimeoutError, OSError) as exc:
|
|
@@ -3242,7 +3283,7 @@ def _parse_terminal_frame(raw: bytes) -> tuple[int, bytes]:
|
|
|
3242
3283
|
return raw[0], raw[TERMINAL_FRAME_HEADER_SIZE:]
|
|
3243
3284
|
|
|
3244
3285
|
|
|
3245
|
-
def _run_terminal_session(ws_url: str) -> int:
|
|
3286
|
+
def _run_terminal_session(ws_url: str, *, bootstrap_command: str | None = None) -> int:
|
|
3246
3287
|
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
3247
3288
|
return report_error("enter requires an interactive terminal")
|
|
3248
3289
|
|
|
@@ -3251,10 +3292,12 @@ def _run_terminal_session(ws_url: str) -> int:
|
|
|
3251
3292
|
old_settings = termios_mod.tcgetattr(fd)
|
|
3252
3293
|
stop = threading.Event()
|
|
3253
3294
|
result: dict[str, Any] = {"code": 0, "error": None}
|
|
3254
|
-
|
|
3295
|
+
bootstrap_echo = f"exec {bootstrap_command.strip()}" if bootstrap_command and bootstrap_command.strip() else ""
|
|
3296
|
+
output_state: dict[str, Any] = {"buffer": "", "suppressing_preamble": True, "bootstrap_echo": bootstrap_echo}
|
|
3255
3297
|
|
|
3256
3298
|
with websocket_connect(ws_url, open_timeout=DEFAULT_WARMUP_TIMEOUT, close_timeout=1) as ws:
|
|
3257
3299
|
_send_terminal_resize(ws)
|
|
3300
|
+
_send_terminal_bootstrap(ws, bootstrap_command)
|
|
3258
3301
|
|
|
3259
3302
|
def recv_loop() -> None:
|
|
3260
3303
|
try:
|
|
@@ -3332,7 +3375,7 @@ def _filter_terminal_output(chunk: str, state: dict[str, Any]) -> str:
|
|
|
3332
3375
|
if not chunk:
|
|
3333
3376
|
return ""
|
|
3334
3377
|
if not state.get("suppressing_preamble", False):
|
|
3335
|
-
return _strip_terminal_noise(chunk)
|
|
3378
|
+
return _strip_terminal_noise(chunk, str(state.get("bootstrap_echo") or ""))
|
|
3336
3379
|
|
|
3337
3380
|
buffer = str(state.get("buffer") or "") + chunk
|
|
3338
3381
|
marker = "\x1b[1;34mruntime@"
|
|
@@ -3340,9 +3383,9 @@ def _filter_terminal_output(chunk: str, state: dict[str, Any]) -> str:
|
|
|
3340
3383
|
if idx != -1:
|
|
3341
3384
|
state["suppressing_preamble"] = False
|
|
3342
3385
|
state["buffer"] = ""
|
|
3343
|
-
return _strip_terminal_noise(buffer[idx:])
|
|
3386
|
+
return _strip_terminal_noise(buffer[idx:], str(state.get("bootstrap_echo") or ""))
|
|
3344
3387
|
|
|
3345
|
-
cleaned = _strip_terminal_noise(buffer)
|
|
3388
|
+
cleaned = _strip_terminal_noise(buffer, str(state.get("bootstrap_echo") or ""))
|
|
3346
3389
|
if cleaned and _should_release_terminal_output(cleaned):
|
|
3347
3390
|
state["suppressing_preamble"] = False
|
|
3348
3391
|
state["buffer"] = ""
|
|
@@ -3351,7 +3394,7 @@ def _filter_terminal_output(chunk: str, state: dict[str, Any]) -> str:
|
|
|
3351
3394
|
if len(buffer) > 8192:
|
|
3352
3395
|
state["suppressing_preamble"] = False
|
|
3353
3396
|
state["buffer"] = ""
|
|
3354
|
-
return _strip_terminal_noise(buffer)
|
|
3397
|
+
return _strip_terminal_noise(buffer, str(state.get("bootstrap_echo") or ""))
|
|
3355
3398
|
|
|
3356
3399
|
state["buffer"] = buffer
|
|
3357
3400
|
return ""
|
|
@@ -3374,10 +3417,13 @@ def _should_release_terminal_output(text: str) -> bool:
|
|
|
3374
3417
|
def _flush_terminal_output(state: dict[str, Any]) -> str:
|
|
3375
3418
|
buffer = str(state.get("buffer") or "")
|
|
3376
3419
|
state["buffer"] = ""
|
|
3377
|
-
return _strip_terminal_noise(buffer)
|
|
3420
|
+
return _strip_terminal_noise(buffer, str(state.get("bootstrap_echo") or ""))
|
|
3421
|
+
|
|
3378
3422
|
|
|
3423
|
+
ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]")
|
|
3379
3424
|
|
|
3380
|
-
|
|
3425
|
+
|
|
3426
|
+
def _strip_terminal_noise(text: str, bootstrap_echo: str = "") -> str:
|
|
3381
3427
|
if not text:
|
|
3382
3428
|
return ""
|
|
3383
3429
|
|
|
@@ -3395,10 +3441,27 @@ def _strip_terminal_noise(text: str) -> str:
|
|
|
3395
3441
|
continue
|
|
3396
3442
|
if stripped == "* Docs: https://docs.slicervm.com":
|
|
3397
3443
|
continue
|
|
3444
|
+
if _is_terminal_bootstrap_echo(stripped, bootstrap_echo):
|
|
3445
|
+
continue
|
|
3398
3446
|
filtered.append(line)
|
|
3399
3447
|
return "".join(filtered)
|
|
3400
3448
|
|
|
3401
3449
|
|
|
3450
|
+
def _is_terminal_bootstrap_echo(line: str, bootstrap_echo: str) -> bool:
|
|
3451
|
+
bootstrap_echo = bootstrap_echo.strip()
|
|
3452
|
+
if not bootstrap_echo:
|
|
3453
|
+
return False
|
|
3454
|
+
clean = ANSI_ESCAPE_RE.sub("", line).strip()
|
|
3455
|
+
return clean == bootstrap_echo or clean.endswith(bootstrap_echo)
|
|
3456
|
+
|
|
3457
|
+
|
|
3458
|
+
def _send_terminal_bootstrap(ws: Any, command: str | None) -> None:
|
|
3459
|
+
command = (command or "").strip()
|
|
3460
|
+
if not command:
|
|
3461
|
+
return
|
|
3462
|
+
ws.send(_encode_terminal_frame(TERMINAL_FRAME_DATA, f"exec {command}\n".encode()))
|
|
3463
|
+
|
|
3464
|
+
|
|
3402
3465
|
def _send_terminal_resize(ws: Any) -> None:
|
|
3403
3466
|
size = shutil.get_terminal_size((100, 24))
|
|
3404
3467
|
ws.send(_encode_terminal_frame(TERMINAL_FRAME_WINDOW_SIZE, struct.pack(">II", size.columns, size.lines)))
|
|
@@ -75,8 +75,11 @@ class RuntimeClient:
|
|
|
75
75
|
def get_secret_set(self, slug: str) -> dict[str, Any]:
|
|
76
76
|
return self._request("GET", f"/api/secrets/{slug}", auth_required=True)
|
|
77
77
|
|
|
78
|
-
def put_secret_set(self, slug: str, values: dict[str, str]) -> dict[str, Any]:
|
|
79
|
-
|
|
78
|
+
def put_secret_set(self, slug: str, values: dict[str, str], delivery: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
79
|
+
payload: dict[str, Any] = {"values": values}
|
|
80
|
+
if delivery:
|
|
81
|
+
payload["deliveries"] = delivery
|
|
82
|
+
return self._request("PUT", f"/api/secrets/{slug}", json=payload, auth_required=True)
|
|
80
83
|
|
|
81
84
|
def delete_secret_set(self, slug: str) -> dict[str, Any]:
|
|
82
85
|
return self._request("DELETE", f"/api/secrets/{slug}", auth_required=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runtime-sdk
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.36
|
|
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
|
|
@@ -76,7 +76,9 @@ runtime switch -c feature/login --repo owner/repo # create a new branch worksp
|
|
|
76
76
|
runtime checkout --repo owner/repo feature/login # alias for switch
|
|
77
77
|
runtime create # creates a computer with the starter app already published
|
|
78
78
|
runtime create myapp --command "python3 app.py" --cwd /home/ubuntu --port 3000
|
|
79
|
-
runtime enter <name-or-id> # accepts slug/name
|
|
79
|
+
runtime enter <name-or-id> # open an interactive shell; accepts slug/name or computer id
|
|
80
|
+
runtime enter <name-or-id> -- claude # open a real TTY and run an interactive command
|
|
81
|
+
runtime enter <name-or-id> -- python # use enter for REPLs, agents, prompts, and full-screen apps
|
|
80
82
|
runtime list
|
|
81
83
|
runtime info <id>
|
|
82
84
|
runtime share public <id>
|
|
@@ -86,6 +88,7 @@ runtime run <id> "echo hello" # one-shot foreground command only
|
|
|
86
88
|
runtime run <id> "apt install -y nodejs" --uid 0
|
|
87
89
|
runtime exec <id> -- bash -lc 'for i in 1 2 3; do echo $i; sleep 1; done'
|
|
88
90
|
printf 'hello' | runtime exec <id> --stdin -- cat
|
|
91
|
+
# Use runtime exec for automation and exact exit codes; use runtime enter -- <cmd> for interactive TTY commands.
|
|
89
92
|
runtime files ls <id> /home/ubuntu
|
|
90
93
|
runtime files read <id> /home/ubuntu/app.py --output app.py
|
|
91
94
|
runtime files write <id> /home/ubuntu/app.py --input app.py --mode 0644
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|