runtime-sdk 0.4.49__py3-none-win_amd64.whl

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.
@@ -0,0 +1,330 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import re
5
+ import shutil
6
+ import struct
7
+ import sys
8
+ import threading
9
+ import time
10
+ from typing import Any
11
+ from urllib.parse import urljoin, urlparse
12
+
13
+ from websockets.exceptions import ConnectionClosed, InvalidHandshake
14
+ from websockets.sync.client import connect as websocket_connect
15
+
16
+ from runtime_sdk import console, output as cli_output
17
+ from runtime_sdk.auth import authenticated_client
18
+ from runtime_sdk.client import DEFAULT_WARMUP_TIMEOUT, RuntimeAPIError
19
+ from runtime_sdk.config import RuntimeConfig
20
+
21
+
22
+ def resolve_ws_url(base_url: str, ws_url: Any) -> str:
23
+ text = str(ws_url or "").strip()
24
+ if not text:
25
+ return ""
26
+ if text.startswith("ws://") or text.startswith("wss://"):
27
+ return text
28
+
29
+ parsed = urlparse(base_url)
30
+ scheme = "wss" if parsed.scheme == "https" else "ws"
31
+ root = f"{scheme}://{parsed.netloc}"
32
+ if text.startswith("/"):
33
+ return root + text
34
+ return urljoin(root + "/", text)
35
+
36
+
37
+ def strip_controls(text: str) -> str:
38
+ text = re.sub(r"\x1b\].*?(?:\x07|\x1b\\)", "", text, flags=re.S)
39
+ text = re.sub(r"\x1bP.*?\x1b\\", "", text, flags=re.S)
40
+ text = re.sub(r"\x1b\[[0-?]*[ -/]*[@-~]", "", text)
41
+ text = re.sub(r"\x1b[@-Z\\-_]", "", text)
42
+ text = "".join(ch for ch in text if ch in "\n\r\t" or ord(ch) >= 32)
43
+ return text.replace("\r\n", "\n").replace("\r", "\n")
44
+
45
+
46
+ def enter_computer(
47
+ config: RuntimeConfig, computer_id: str, *, command: str | None = None
48
+ ) -> int:
49
+ client = authenticated_client(config)
50
+ ticket_payload = client.create_terminal_ticket(computer_id)
51
+ ws_url = resolve_ws_url(config.base_url, ticket_payload.get("ws_url"))
52
+ if not ws_url:
53
+ return cli_output.report_error(
54
+ "terminal endpoint did not return a websocket url"
55
+ )
56
+
57
+ print("Connecting to console...")
58
+
59
+ try:
60
+ return _run_terminal_session(ws_url, bootstrap_command=command)
61
+ except RuntimeAPIError as exc:
62
+ return cli_output.report_error(str(exc), status_code=exc.status_code)
63
+ except (InvalidHandshake, TimeoutError, OSError) as exc:
64
+ return cli_output.report_error(f"terminal failed: {exc}")
65
+
66
+
67
+ TERMINAL_FRAME_DATA = 0x01
68
+
69
+
70
+ TERMINAL_FRAME_WINDOW_SIZE = 0x02
71
+
72
+
73
+ TERMINAL_FRAME_SHUTDOWN = 0x03
74
+
75
+
76
+ TERMINAL_FRAME_HEARTBEAT = 0x04
77
+
78
+
79
+ TERMINAL_FRAME_SESSION_CLOSE = 0x05
80
+
81
+
82
+ TERMINAL_FRAME_HEADER_SIZE = 5
83
+
84
+
85
+ TERMINAL_HEARTBEAT_INTERVAL = 15.0
86
+
87
+
88
+ def _encode_terminal_frame(frame_type: int, payload: bytes = b"") -> bytes:
89
+ return bytes([frame_type]) + struct.pack(">I", len(payload)) + payload
90
+
91
+
92
+ def _parse_terminal_frame(raw: bytes) -> tuple[int, bytes]:
93
+ if len(raw) < TERMINAL_FRAME_HEADER_SIZE:
94
+ raise RuntimeAPIError("terminal received a truncated frame")
95
+ payload_len = struct.unpack(">I", raw[1:TERMINAL_FRAME_HEADER_SIZE])[0]
96
+ if len(raw) != TERMINAL_FRAME_HEADER_SIZE + payload_len:
97
+ raise RuntimeAPIError("terminal received a malformed frame")
98
+ return raw[0], raw[TERMINAL_FRAME_HEADER_SIZE:]
99
+
100
+
101
+ def _run_terminal_session(ws_url: str, *, bootstrap_command: str | None = None) -> int:
102
+ if not sys.stdin.isatty() or not sys.stdout.isatty():
103
+ return cli_output.report_error("enter requires an interactive terminal")
104
+
105
+ fd = sys.stdin.fileno()
106
+ stop = threading.Event()
107
+ result: dict[str, Any] = {"code": 0, "error": None}
108
+ bootstrap_echo = (
109
+ f"exec {bootstrap_command.strip()}"
110
+ if bootstrap_command and bootstrap_command.strip()
111
+ else ""
112
+ )
113
+ output_state: dict[str, Any] = {
114
+ "buffer": "",
115
+ "suppressing_preamble": True,
116
+ "bootstrap_echo": bootstrap_echo,
117
+ }
118
+
119
+ with websocket_connect(
120
+ ws_url, open_timeout=DEFAULT_WARMUP_TIMEOUT, close_timeout=1
121
+ ) as ws:
122
+ _send_terminal_resize(ws)
123
+ _send_terminal_bootstrap(ws, bootstrap_command)
124
+
125
+ def recv_loop() -> None:
126
+ try:
127
+ while not stop.is_set():
128
+ raw = ws.recv()
129
+ if raw is None:
130
+ result["code"] = 0
131
+ stop.set()
132
+ return
133
+ if not isinstance(raw, bytes):
134
+ raise RuntimeAPIError("terminal received a non-binary frame")
135
+ frame_type, payload = _parse_terminal_frame(raw)
136
+ if frame_type == TERMINAL_FRAME_HEARTBEAT:
137
+ continue
138
+ if frame_type == TERMINAL_FRAME_DATA:
139
+ data = _filter_terminal_output(
140
+ payload.decode("utf-8", errors="ignore"), output_state
141
+ )
142
+ if data:
143
+ sys.stdout.write(data)
144
+ sys.stdout.flush()
145
+ continue
146
+ if frame_type in (
147
+ TERMINAL_FRAME_SHUTDOWN,
148
+ TERMINAL_FRAME_SESSION_CLOSE,
149
+ ):
150
+ stop.set()
151
+ return
152
+ except ConnectionClosed:
153
+ if (
154
+ not stop.is_set()
155
+ and not result.get("error")
156
+ and int(result.get("code") or 0) == 0
157
+ ):
158
+ result["code"] = 1
159
+ result["error"] = "terminal connection closed unexpectedly"
160
+ stop.set()
161
+ except (
162
+ Exception
163
+ ) as exc: # pragma: no cover - defensive for real TTY sessions
164
+ result["code"] = 1
165
+ result["error"] = str(exc)
166
+ stop.set()
167
+
168
+ recv_thread = threading.Thread(target=recv_loop, daemon=True)
169
+ recv_thread.start()
170
+
171
+ last_size = shutil.get_terminal_size((100, 24))
172
+ last_heartbeat = time.monotonic()
173
+
174
+ def mark_connection_closed() -> None:
175
+ if (
176
+ not stop.is_set()
177
+ and not result.get("error")
178
+ and int(result.get("code") or 0) == 0
179
+ ):
180
+ result["code"] = 1
181
+ result["error"] = "terminal connection closed unexpectedly"
182
+ stop.set()
183
+
184
+ try:
185
+ with console.raw_terminal_input(fd) as read_input:
186
+ while not stop.is_set():
187
+ current_size = shutil.get_terminal_size((100, 24))
188
+ if current_size != last_size:
189
+ _send_terminal_resize(ws)
190
+ last_size = current_size
191
+
192
+ now = time.monotonic()
193
+ if now - last_heartbeat >= TERMINAL_HEARTBEAT_INTERVAL:
194
+ try:
195
+ _send_terminal_heartbeat(ws)
196
+ except ConnectionClosed:
197
+ mark_connection_closed()
198
+ break
199
+ last_heartbeat = now
200
+
201
+ data = read_input(0.1)
202
+ if data is None:
203
+ continue
204
+ if not data:
205
+ stop.set()
206
+ break
207
+ try:
208
+ ws.send(_encode_terminal_frame(TERMINAL_FRAME_DATA, data))
209
+ except ConnectionClosed:
210
+ mark_connection_closed()
211
+ break
212
+ finally:
213
+ stop.set()
214
+ with contextlib.suppress(Exception):
215
+ ws.send(_encode_terminal_frame(TERMINAL_FRAME_SHUTDOWN))
216
+ with contextlib.suppress(Exception):
217
+ ws.close()
218
+ recv_thread.join(timeout=1)
219
+
220
+ sys.stdout.write("\n")
221
+ sys.stdout.flush()
222
+ tail = _flush_terminal_output(output_state)
223
+ if tail:
224
+ sys.stdout.write(tail)
225
+ sys.stdout.flush()
226
+ if result.get("error"):
227
+ print(str(result["error"]), file=sys.stderr)
228
+ return int(result.get("code") or 0)
229
+
230
+
231
+ def _filter_terminal_output(chunk: str, state: dict[str, Any]) -> str:
232
+ if not chunk:
233
+ return ""
234
+ if not state.get("suppressing_preamble", False):
235
+ return _strip_terminal_noise(chunk, str(state.get("bootstrap_echo") or ""))
236
+
237
+ buffer = str(state.get("buffer") or "") + chunk
238
+ marker = "\x1b[1;34mruntime@"
239
+ idx = buffer.find(marker)
240
+ if idx != -1:
241
+ state["suppressing_preamble"] = False
242
+ state["buffer"] = ""
243
+ return _strip_terminal_noise(
244
+ buffer[idx:], str(state.get("bootstrap_echo") or "")
245
+ )
246
+
247
+ cleaned = _strip_terminal_noise(buffer, str(state.get("bootstrap_echo") or ""))
248
+ if cleaned and _should_release_terminal_output(cleaned):
249
+ state["suppressing_preamble"] = False
250
+ state["buffer"] = ""
251
+ return cleaned
252
+
253
+ if len(buffer) > 8192:
254
+ state["suppressing_preamble"] = False
255
+ state["buffer"] = ""
256
+ return _strip_terminal_noise(buffer, str(state.get("bootstrap_echo") or ""))
257
+
258
+ state["buffer"] = buffer
259
+ return ""
260
+
261
+
262
+ def _should_release_terminal_output(text: str) -> bool:
263
+ if not text:
264
+ return False
265
+ if "\n" in text or "\r" in text:
266
+ return True
267
+ if len(text) >= 256:
268
+ return True
269
+
270
+ stripped = text.rstrip()
271
+ return text.endswith(("$ ", "# ", "% ", "> ")) or stripped.endswith(
272
+ ("$", "#", "%", ">")
273
+ )
274
+
275
+
276
+ def _flush_terminal_output(state: dict[str, Any]) -> str:
277
+ buffer = str(state.get("buffer") or "")
278
+ state["buffer"] = ""
279
+ return _strip_terminal_noise(buffer, str(state.get("bootstrap_echo") or ""))
280
+
281
+
282
+ ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]")
283
+
284
+
285
+ def _strip_terminal_noise(text: str, bootstrap_echo: str = "") -> str:
286
+ if not text:
287
+ return ""
288
+
289
+ lines = text.splitlines(keepends=True)
290
+ filtered: list[str] = []
291
+ for line in lines:
292
+ stripped = line.rstrip("\r\n")
293
+ if stripped.startswith(
294
+ "Running bootstrap command: export PS1='\\[\\033[1;34m\\]runtime@"
295
+ ):
296
+ continue
297
+ if stripped == "Connected! Press Ctrl+] to exit.":
298
+ continue
299
+ if _is_terminal_bootstrap_echo(stripped, bootstrap_echo):
300
+ continue
301
+ filtered.append(line)
302
+ return "".join(filtered)
303
+
304
+
305
+ def _is_terminal_bootstrap_echo(line: str, bootstrap_echo: str) -> bool:
306
+ bootstrap_echo = bootstrap_echo.strip()
307
+ if not bootstrap_echo:
308
+ return False
309
+ clean = ANSI_ESCAPE_RE.sub("", line).strip()
310
+ return clean == bootstrap_echo or clean.endswith(bootstrap_echo)
311
+
312
+
313
+ def _send_terminal_bootstrap(ws: Any, command: str | None) -> None:
314
+ command = (command or "").strip()
315
+ if not command:
316
+ return
317
+ ws.send(_encode_terminal_frame(TERMINAL_FRAME_DATA, f"exec {command}\n".encode()))
318
+
319
+
320
+ def _send_terminal_resize(ws: Any) -> None:
321
+ size = shutil.get_terminal_size((100, 24))
322
+ ws.send(
323
+ _encode_terminal_frame(
324
+ TERMINAL_FRAME_WINDOW_SIZE, struct.pack(">II", size.columns, size.lines)
325
+ )
326
+ )
327
+
328
+
329
+ def _send_terminal_heartbeat(ws: Any) -> None:
330
+ ws.send(_encode_terminal_frame(TERMINAL_FRAME_HEARTBEAT))
@@ -0,0 +1,273 @@
1
+ Metadata-Version: 2.4
2
+ Name: runtime-sdk
3
+ Version: 0.4.49
4
+ Summary: Runtime Python SDK and CLI
5
+ Project-URL: Repository, https://github.com/The-Money-Company-Limited/runtimevm
6
+ Project-URL: Issues, https://github.com/The-Money-Company-Limited/runtimevm/issues
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Environment :: Console
13
+ Classifier: Operating System :: MacOS :: MacOS X
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Operating System :: POSIX
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: httpx>=0.28.1
20
+ Requires-Dist: websockets>=15.0
21
+
22
+ # runtime-sdk
23
+
24
+ Python SDK and CLI for Runtime.
25
+
26
+ ## Install
27
+
28
+ Supported host OS for scriptable CLI commands: **Windows, macOS, and Linux**.
29
+
30
+ The interactive dashboard ships in native wheels for Windows ARM64/x64,
31
+ macOS 13+ ARM64/x64, and 64-bit Linux with glibc 2.17+. Other supported
32
+ environments receive the universal wheel with the complete scriptable CLI but
33
+ without the OpenTUI dashboard.
34
+
35
+ ```bash
36
+ uv tool install runtime-sdk
37
+ ```
38
+
39
+ To upgrade an existing install:
40
+
41
+ ```bash
42
+ uv tool upgrade runtime-sdk
43
+ ```
44
+
45
+ ## Configure
46
+
47
+ The CLI talks to `https://api.runruntime.dev` by default.
48
+
49
+ For local or self-hosted Runtime, override it with:
50
+
51
+ ```bash
52
+ export RUNTIME_BASE_URL=http://127.0.0.1:8080
53
+ ```
54
+
55
+ Or pass `--base-url` per command.
56
+
57
+ ## Usage
58
+
59
+ ```bash
60
+ # Auth
61
+ runtime # open the interactive dashboard; sign in first if needed
62
+ runtime login # WorkOS device login; opens the verification page
63
+ runtime login --no-open # print the URL and code for a browserless host
64
+ runtime whoami
65
+ runtime logout
66
+ runtime login --api-key sk_... # explicit headless credential mode
67
+ # Or set RUNTIME_API_KEY for one process without saving it.
68
+ runtime api-keys list
69
+ runtime api-keys create "my laptop"
70
+ runtime api-keys revoke key_01...
71
+ runtime integrate github
72
+ runtime github list
73
+ runtime github disconnect acme
74
+
75
+ # Computers
76
+ runtime switch --repo owner/repo feature/login # open or create the branch workspace computer
77
+ runtime switch -c feature/login --repo owner/repo # create a new branch workspace from the repo default branch
78
+ runtime checkout --repo owner/repo feature/login # alias for switch
79
+ runtime create # creates a computer with the starter app already published
80
+ runtime create myapp --command "python3 app.py" --cwd /home/runtime --port 3000
81
+ runtime create locked --network allowlist --allow-host api.example.com
82
+ runtime enter <name-or-id> # open an interactive shell; accepts slug/name or computer id
83
+ runtime enter <name-or-id> -- claude # open a real TTY and run an interactive command
84
+ runtime enter <name-or-id> -- python # use enter for REPLs, agents, prompts, and full-screen apps
85
+ runtime ssh <name-or-id> # open SSH through Runtime's authenticated tunnel
86
+ runtime ssh <name-or-id> -- uptime # run a command with the local ssh client
87
+ runtime list # open the computer dashboard in a terminal
88
+ runtime list --json # one JSON response for scripts and agents
89
+ runtime list --json --watch 2 # newline-delimited JSON updates
90
+ runtime info <id>
91
+ runtime share public <id>
92
+ runtime share private <id>
93
+ runtime start <id>
94
+ runtime run <id> "echo hello" # one-shot foreground command only
95
+ runtime run <id> "apt install -y nodejs" --uid 0
96
+ runtime exec <id> -- bash -lc 'for i in 1 2 3; do echo $i; sleep 1; done'
97
+ printf 'hello' | runtime exec <id> --stdin -- cat
98
+ # Use runtime exec for automation and exact exit codes; use runtime enter -- <cmd> for interactive TTY commands.
99
+ runtime files ls <id> /home/runtime
100
+ runtime files read <id> /home/runtime/app.py --output app.py
101
+ runtime files write <id> /home/runtime/app.py --input app.py --mode 0644
102
+ runtime files mkdir <id> /home/runtime/data --parents
103
+ runtime files upload <id> ./local-app /home/runtime/app
104
+ runtime files download <id> /home/runtime/app ./downloaded-app
105
+ runtime files rm <id> /home/runtime/data --recursive
106
+ runtime startup show <id>
107
+ runtime startup set <id> --command "python3 app.py" --cwd /home/runtime --port 3000
108
+ runtime startup clear <id> # low-level durable service config
109
+ runtime service show <id> # user-facing alias for the durable published app
110
+ runtime service clear <id>
111
+ runtime publish <id> 3000 -- python3 app.py
112
+ runtime delete <id>
113
+
114
+ # Network policy
115
+ runtime network default show
116
+ runtime network default allowlist api.example.com '*.github.com'
117
+ runtime network show <id>
118
+ runtime network denials <id>
119
+ runtime network set <id> none
120
+ runtime network set <id> open
121
+
122
+ # Inside a running computer, the helper installed by Runtime can manage the
123
+ # durable app without leaving the sandbox:
124
+ # runtime-env publish 3000 -- python3 app.py
125
+ # runtime-env service show
126
+ # runtime-env service clear
127
+ ```
128
+
129
+ Private public URLs authenticate the browser before forwarding traffic. Apps
130
+ behind a private URL receive trusted `X-Runtime-Account-ID`,
131
+ `X-Runtime-User-ID`, and `X-Runtime-User-Email` headers. Runtime strips
132
+ client-supplied versions of those headers from both public and private traffic;
133
+ public URLs receive no Runtime identity headers.
134
+
135
+ ## Python
136
+
137
+ ```python
138
+ from runtime_sdk import RuntimeClient
139
+
140
+ client = RuntimeClient(base_url="https://api.runruntime.dev", credential="sk_...")
141
+
142
+ # Create a computer. New computers start with the starter app already published.
143
+ computer = client.create_computer()
144
+ print(computer["public_url"]) # https://goldbird.runruntime.dev
145
+
146
+ # Or create one with an explicit durable app command.
147
+ # That saved service is replayed after cold restore / start.
148
+ app = client.create_computer(
149
+ slug="myapp",
150
+ command="python3 app.py",
151
+ cwd="/home/runtime",
152
+ port=3000,
153
+ )
154
+
155
+ # Account defaults are copied only to computers created later.
156
+ client.set_default_network("allowlist", ["api.example.com"])
157
+ restricted = client.create_computer()
158
+ client.set_computer_network(restricted["id"], "none")
159
+
160
+ # Run a command
161
+ result = client.run_command(computer["id"], "echo hello")
162
+ print(result["stdout"])
163
+
164
+ client.write_file(computer["id"], "/home/runtime/hello.txt", b"hello\n")
165
+ print(client.read_file(computer["id"], "/home/runtime/hello.txt"))
166
+ print(client.list_files(computer["id"], "/home/runtime"))
167
+
168
+ # Wake a cold computer explicitly
169
+ client.start_computer(app["id"])
170
+
171
+ # Promote the running app on a local port to the durable public app.
172
+ # Runtime inspects the listening process and saves its command + cwd when possible.
173
+ client.publish_port(computer["id"], 3000)
174
+
175
+ # List, info, delete
176
+ computers = client.list_computers()
177
+ info = client.get_computer(computer["id"])
178
+ client.delete_computer(computer["id"])
179
+ ```
180
+
181
+ ## Development
182
+
183
+ For fast local backend iteration:
184
+
185
+ ```bash
186
+ make local-backend
187
+ ```
188
+
189
+ For deploying and testing against the Hetzner production server:
190
+
191
+ ```bash
192
+ make sync SERVER_IP=x.x.x.x SSH_USER=root
193
+ make smoke
194
+ ```
195
+
196
+ Use `make deploy` instead of `make sync` when migrations, env
197
+ files, Caddy, or systemd units changed.
198
+
199
+ Cold restore and explicit `runtime start` replay the saved published app command.
200
+ New computers seed that durable app from the starter workspace. Later,
201
+ `runtime publish <id> <port> -- <command...>` replaces the durable app command
202
+ and starts it. Inside a running computer,
203
+ `runtime-env publish <port> -- <command...>` does the same thing using a
204
+ computer-scoped token installed by Runtime. `runtime service show|clear` and
205
+ `runtime-env service show|clear`
206
+ expose that same durable app state directly. The low-level `runtime startup ...`
207
+ commands still map to the same durable state. A one-off `runtime run` stays
208
+ one-shot: the filesystem is restored after going cold, but that ad-hoc process
209
+ is not.
210
+
211
+ ## GitHub branch workspaces
212
+
213
+ `runtime switch` gives a repo branch its own Runtime computer so you can jump
214
+ between parallel tasks without local worktrees.
215
+
216
+ ```bash
217
+ runtime integrate github
218
+ runtime github list
219
+ runtime switch --repo owner/repo feature/login
220
+ runtime switch -c feature/search --repo owner/repo --from main
221
+ ```
222
+
223
+ Rules:
224
+ - GitHub must already be connected with `runtime integrate github`.
225
+ - Run `runtime integrate github` again when you want to add another personal account or org.
226
+ - New GitHub connects require user authorization during installation and
227
+ `GITHUB_APP_CLIENT_ID` / `GITHUB_APP_CLIENT_SECRET` on the Runtime API.
228
+ In GitHub App settings, enable user authorization during installation and set
229
+ the callback URL to `https://api.runruntime.dev/github/callback`.
230
+ - `runtime switch` opens an existing branch workspace, or creates the computer if
231
+ the branch exists but the workspace does not yet.
232
+ - `runtime switch -c` creates a new branch first, then opens that branch's
233
+ workspace.
234
+ - Each `repo + branch` maps to one dedicated computer.
235
+ - The repo is cloned into `/home/runtime/<repo>` and new shells in that computer
236
+ start there automatically.
237
+ - `runtime checkout ...` is an alias for `runtime switch ...`.
238
+
239
+ Run the SDK unit tests through the backend project environment:
240
+
241
+ ```bash
242
+ uv run python -m unittest discover -s scripts/tests -p 'test_*.py'
243
+ ```
244
+
245
+ ## Release
246
+
247
+ Set and commit the release version first:
248
+
249
+ ```bash
250
+ cd backend
251
+ uv version <next-version>
252
+ ```
253
+
254
+ Merge the release version to `main`, then run the Runtime SDK workflow with the
255
+ exact committed version. The workflow tests the Python CLI and OpenTUI, builds
256
+ all platform wheels, runs the installed Windows wheel against production, and
257
+ only then publishes those same artifacts to PyPI.
258
+
259
+ ```bash
260
+ VERSION="$(uv version --short)"
261
+ gh workflow run runtime-sdk.yml --ref main -f version="${VERSION}" -f publish=true
262
+ ```
263
+
264
+ Preview the runner commands without building:
265
+
266
+ ```bash
267
+ ./scripts/release_runtime_sdk.sh --dry-run
268
+ ```
269
+
270
+ `RUNTIME_WINDOWS_SMOKE_API_KEY` is the production smoke credential;
271
+ `UV_PUBLISH_TOKEN` is used only by the publish job after every wheel and the
272
+ Windows production smoke pass. The build script never publishes, changes the
273
+ source version, or loads a local `.env` file.
@@ -0,0 +1,26 @@
1
+ runtime_sdk/__init__.py,sha256=_fcdLGAGl00mqptSUWHoPwjgEL8kASrWVMy_tZqUUf0,501
2
+ runtime_sdk/account.py,sha256=2GEG5pZiF1Dh3ZhkpxaAKE0B03RGubVMnIzLwVgbel4,8468
3
+ runtime_sdk/arguments.py,sha256=-zfsxKFoCr-2epM8cSk9g8NltBaRZ-ih3vDNeChWPk0,264
4
+ runtime_sdk/auth.py,sha256=GXuTMaT3an7dv2Fgl8arIi1zSgZ2CDLAskML3X8GxSI,5914
5
+ runtime_sdk/cli.py,sha256=xUKaQBwIizcXt_0l6XiSaDW__aPhT2MwE2x48mTNJ8o,50587
6
+ runtime_sdk/client.py,sha256=Kthr1FjPFMMh2oO_ZzlxAC7OrUmwm8wI2mgo09v1YVY,23250
7
+ runtime_sdk/completion.py,sha256=yaGk0YVYK8i4RLdrqWuJUVndONbp5G173gyPGhEyAlo,13617
8
+ runtime_sdk/computers.py,sha256=ZKRb8jAtsP3mYpeiCWAddLworKqFrS32SykizSdCR7s,20084
9
+ runtime_sdk/config.py,sha256=CdjCDs-5qa12DWrHgqSf0YQOZ5_o3cjYvEzosaEXT-4,6158
10
+ runtime_sdk/console.py,sha256=KvvgmNJzEixNa9SHp9JF2WGRQdrO9bqrtLeU1zoCGus,4035
11
+ runtime_sdk/files.py,sha256=LyZohpK6fjXhSv5X4M2kuGG0y75791ZvPAD_QEjJsRw,8936
12
+ runtime_sdk/github.py,sha256=cPxLNJScawmptCeraMeDbZviDJ1gXo9M9tGSdnIMXYM,8189
13
+ runtime_sdk/goals.py,sha256=FWM9OhIFiFTEujUUSDzkgymgEcAV1LkaM9QNpEO5p4Y,11491
14
+ runtime_sdk/output.py,sha256=NNzG_xpwe91j2bNnIKDj-Z9Xk5Tg8DFHaZD-VXnhVes,2046
15
+ runtime_sdk/providers.py,sha256=TQan5VB63pIxEuhpMxS8d5VoxG_dqlO93aVxu61_LXE,14478
16
+ runtime_sdk/proxy.py,sha256=KJVmoVGJ7btZYmsjnT0JUAd9cjNK98ASvMQED7VG5xU,19744
17
+ runtime_sdk/runtime-tui.exe,sha256=o-3ZwrRicYA8WHSWO2N-7dgY1BwJgRkGXcSNssty_6Y,107655168
18
+ runtime_sdk/secrets.py,sha256=aAXC-4ywbv8kbn9Zl55CicpTRD0wcnFc4lyk8JGAB8s,5900
19
+ runtime_sdk/services.py,sha256=gmGNLa_u9WAoMmAjQWrPaeVvN-zVqT-oxNDp2ftjptQ,6411
20
+ runtime_sdk/ssh.py,sha256=e9fRSkqeVE6DYux_W1yks3YVGX_7Ba7S3rJcfYs4JZ0,6492
21
+ runtime_sdk/terminal.py,sha256=PzPXfTPA5TZb4VFpNxWlt-2Y56XT38wOzFwBEIvGuk8,11030
22
+ runtime_sdk-0.4.49.dist-info/METADATA,sha256=sPhn3uDPsdpA9Xsz58RwRs6P5FXwxzxGkxHpYpx-KZM,10182
23
+ runtime_sdk-0.4.49.dist-info/WHEEL,sha256=hVx9elvUDfBjRmbl8JwIcXXikst35RZTZK9nspfI_28,98
24
+ runtime_sdk-0.4.49.dist-info/entry_points.txt,sha256=Hk_zo0mQTeqWOYb1oTxGnGN-b61_6uKYAt3THED8xeE,49
25
+ runtime_sdk-0.4.49.dist-info/top_level.txt,sha256=RmTYN3o3Mn1j6OozW1LbYaYvy3FjQU3E118nfNv7Ye0,12
26
+ runtime_sdk-0.4.49.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win_amd64
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ runtime = runtime_sdk.cli:main
@@ -0,0 +1 @@
1
+ runtime_sdk