runtime-sdk 0.4.10__tar.gz → 0.4.11__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.10 → runtime_sdk-0.4.11}/PKG-INFO +30 -12
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/README.md +29 -11
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/pyproject.toml +1 -1
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/scripts/runtime_sdk/cli.py +186 -8
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/scripts/runtime_sdk/client.py +25 -0
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/scripts/runtime_sdk.egg-info/PKG-INFO +30 -12
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/scripts/runtime_sdk/__init__.py +0 -0
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/scripts/runtime_sdk/config.py +0 -0
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/scripts/runtime_sdk.egg-info/SOURCES.txt +0 -0
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/scripts/runtime_sdk.egg-info/dependency_links.txt +0 -0
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/scripts/runtime_sdk.egg-info/entry_points.txt +0 -0
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/scripts/runtime_sdk.egg-info/requires.txt +0 -0
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/scripts/runtime_sdk.egg-info/top_level.txt +0 -0
- {runtime_sdk-0.4.10 → runtime_sdk-0.4.11}/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.11
|
|
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
|
|
@@ -54,16 +54,27 @@ runtime logout
|
|
|
54
54
|
runtime login --api-key rt_live_...
|
|
55
55
|
|
|
56
56
|
# Computers
|
|
57
|
-
runtime create #
|
|
57
|
+
runtime create # creates a computer with the starter app already published
|
|
58
58
|
runtime create myapp --command "python3 app.py" --cwd /home/ubuntu --port 3000
|
|
59
59
|
runtime enter <name-or-id> # accepts slug/name like test, or the computer id
|
|
60
60
|
runtime list
|
|
61
61
|
runtime info <id>
|
|
62
62
|
runtime start <id>
|
|
63
|
-
runtime run <id> "echo hello"
|
|
63
|
+
runtime run <id> "echo hello" # one-shot foreground command only
|
|
64
64
|
runtime run <id> "apt install -y nodejs" --uid 0
|
|
65
|
-
runtime
|
|
65
|
+
runtime startup show <id>
|
|
66
|
+
runtime startup set <id> --command "python3 app.py" --cwd /home/ubuntu --port 3000
|
|
67
|
+
runtime startup clear <id> # low-level durable service config
|
|
68
|
+
runtime service show <id> # user-facing alias for the durable published app
|
|
69
|
+
runtime service clear <id>
|
|
70
|
+
runtime publish <id> 3000 # promote the running app on port 3000 to the public durable app
|
|
66
71
|
runtime delete <id>
|
|
72
|
+
|
|
73
|
+
# Inside a running computer, the helper installed by Runtime can manage the
|
|
74
|
+
# durable app without leaving the sandbox:
|
|
75
|
+
# runtime-env publish 3000
|
|
76
|
+
# runtime-env service show
|
|
77
|
+
# runtime-env service clear
|
|
67
78
|
```
|
|
68
79
|
|
|
69
80
|
## Python
|
|
@@ -73,12 +84,12 @@ from runtime_sdk import RuntimeClient
|
|
|
73
84
|
|
|
74
85
|
client = RuntimeClient(base_url="https://api.runruntime.dev", api_key="rt_live_...")
|
|
75
86
|
|
|
76
|
-
# Create a computer
|
|
87
|
+
# Create a computer. New computers start with the starter app already published.
|
|
77
88
|
computer = client.create_computer()
|
|
78
89
|
print(computer["public_url"]) # https://goldbird.runruntime.dev
|
|
79
90
|
|
|
80
|
-
# Or create one with
|
|
81
|
-
# That
|
|
91
|
+
# Or create one with an explicit durable app command.
|
|
92
|
+
# That saved service is replayed after cold restore / start.
|
|
82
93
|
app = client.create_computer(
|
|
83
94
|
slug="myapp",
|
|
84
95
|
command="python3 app.py",
|
|
@@ -93,7 +104,8 @@ print(result["stdout"])
|
|
|
93
104
|
# Wake a cold computer explicitly
|
|
94
105
|
client.start_computer(app["id"])
|
|
95
106
|
|
|
96
|
-
#
|
|
107
|
+
# Promote the running app on a local port to the durable public app.
|
|
108
|
+
# Runtime inspects the listening process and saves its command + cwd when possible.
|
|
97
109
|
client.publish_port(computer["id"], 3000)
|
|
98
110
|
|
|
99
111
|
# List, info, delete
|
|
@@ -120,10 +132,16 @@ make smoke
|
|
|
120
132
|
Use `make deploy` instead of `make sync` when migrations, env
|
|
121
133
|
files, Caddy, or systemd units changed.
|
|
122
134
|
|
|
123
|
-
Cold restore and
|
|
124
|
-
computers
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
Cold restore and explicit `runtime start` replay the saved published app command.
|
|
136
|
+
New computers seed that durable app from the starter workspace. Later,
|
|
137
|
+
`runtime publish <id> <port>` can promote a running listener into the saved
|
|
138
|
+
public app definition by inspecting the live process. Inside a running computer,
|
|
139
|
+
`runtime-env publish <port>` does the same thing using a computer-scoped token
|
|
140
|
+
installed by Runtime. `runtime service show|clear` and `runtime-env service
|
|
141
|
+
show|clear` expose that same durable app state directly. The low-level
|
|
142
|
+
`runtime startup ...` commands still map to the same durable state. A one-off
|
|
143
|
+
`runtime run` stays one-shot: the filesystem is restored after going cold, but
|
|
144
|
+
that ad-hoc process is not.
|
|
127
145
|
|
|
128
146
|
Run the SDK unit tests through the backend project environment:
|
|
129
147
|
|
|
@@ -35,16 +35,27 @@ runtime logout
|
|
|
35
35
|
runtime login --api-key rt_live_...
|
|
36
36
|
|
|
37
37
|
# Computers
|
|
38
|
-
runtime create #
|
|
38
|
+
runtime create # creates a computer with the starter app already published
|
|
39
39
|
runtime create myapp --command "python3 app.py" --cwd /home/ubuntu --port 3000
|
|
40
40
|
runtime enter <name-or-id> # accepts slug/name like test, or the computer id
|
|
41
41
|
runtime list
|
|
42
42
|
runtime info <id>
|
|
43
43
|
runtime start <id>
|
|
44
|
-
runtime run <id> "echo hello"
|
|
44
|
+
runtime run <id> "echo hello" # one-shot foreground command only
|
|
45
45
|
runtime run <id> "apt install -y nodejs" --uid 0
|
|
46
|
-
runtime
|
|
46
|
+
runtime startup show <id>
|
|
47
|
+
runtime startup set <id> --command "python3 app.py" --cwd /home/ubuntu --port 3000
|
|
48
|
+
runtime startup clear <id> # low-level durable service config
|
|
49
|
+
runtime service show <id> # user-facing alias for the durable published app
|
|
50
|
+
runtime service clear <id>
|
|
51
|
+
runtime publish <id> 3000 # promote the running app on port 3000 to the public durable app
|
|
47
52
|
runtime delete <id>
|
|
53
|
+
|
|
54
|
+
# Inside a running computer, the helper installed by Runtime can manage the
|
|
55
|
+
# durable app without leaving the sandbox:
|
|
56
|
+
# runtime-env publish 3000
|
|
57
|
+
# runtime-env service show
|
|
58
|
+
# runtime-env service clear
|
|
48
59
|
```
|
|
49
60
|
|
|
50
61
|
## Python
|
|
@@ -54,12 +65,12 @@ from runtime_sdk import RuntimeClient
|
|
|
54
65
|
|
|
55
66
|
client = RuntimeClient(base_url="https://api.runruntime.dev", api_key="rt_live_...")
|
|
56
67
|
|
|
57
|
-
# Create a computer
|
|
68
|
+
# Create a computer. New computers start with the starter app already published.
|
|
58
69
|
computer = client.create_computer()
|
|
59
70
|
print(computer["public_url"]) # https://goldbird.runruntime.dev
|
|
60
71
|
|
|
61
|
-
# Or create one with
|
|
62
|
-
# That
|
|
72
|
+
# Or create one with an explicit durable app command.
|
|
73
|
+
# That saved service is replayed after cold restore / start.
|
|
63
74
|
app = client.create_computer(
|
|
64
75
|
slug="myapp",
|
|
65
76
|
command="python3 app.py",
|
|
@@ -74,7 +85,8 @@ print(result["stdout"])
|
|
|
74
85
|
# Wake a cold computer explicitly
|
|
75
86
|
client.start_computer(app["id"])
|
|
76
87
|
|
|
77
|
-
#
|
|
88
|
+
# Promote the running app on a local port to the durable public app.
|
|
89
|
+
# Runtime inspects the listening process and saves its command + cwd when possible.
|
|
78
90
|
client.publish_port(computer["id"], 3000)
|
|
79
91
|
|
|
80
92
|
# List, info, delete
|
|
@@ -101,10 +113,16 @@ make smoke
|
|
|
101
113
|
Use `make deploy` instead of `make sync` when migrations, env
|
|
102
114
|
files, Caddy, or systemd units changed.
|
|
103
115
|
|
|
104
|
-
Cold restore and
|
|
105
|
-
computers
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
Cold restore and explicit `runtime start` replay the saved published app command.
|
|
117
|
+
New computers seed that durable app from the starter workspace. Later,
|
|
118
|
+
`runtime publish <id> <port>` can promote a running listener into the saved
|
|
119
|
+
public app definition by inspecting the live process. Inside a running computer,
|
|
120
|
+
`runtime-env publish <port>` does the same thing using a computer-scoped token
|
|
121
|
+
installed by Runtime. `runtime service show|clear` and `runtime-env service
|
|
122
|
+
show|clear` expose that same durable app state directly. The low-level
|
|
123
|
+
`runtime startup ...` commands still map to the same durable state. A one-off
|
|
124
|
+
`runtime run` stays one-shot: the filesystem is restored after going cold, but
|
|
125
|
+
that ad-hoc process is not.
|
|
108
126
|
|
|
109
127
|
Run the SDK unit tests through the backend project environment:
|
|
110
128
|
|
|
@@ -165,16 +165,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
165
165
|
subparsers.add_parser("whoami")
|
|
166
166
|
subparsers.add_parser("logout")
|
|
167
167
|
|
|
168
|
-
create_cmd = subparsers.add_parser("create", help="Create a new computer")
|
|
168
|
+
create_cmd = subparsers.add_parser("create", help="Create a new computer with the starter app published by default")
|
|
169
169
|
create_cmd.add_argument(
|
|
170
170
|
"name",
|
|
171
171
|
nargs="?",
|
|
172
172
|
default=None,
|
|
173
173
|
help="Subdomain name (e.g. redsox → redsox.runruntime.dev). Random if skipped.",
|
|
174
174
|
)
|
|
175
|
-
create_cmd.add_argument("--command", dest="startup_command", help="
|
|
176
|
-
create_cmd.add_argument("--cwd", help="Working directory for the startup command")
|
|
177
|
-
create_cmd.add_argument("--port", type=int, help="App port to publish
|
|
175
|
+
create_cmd.add_argument("--command", dest="startup_command", help="Durable startup command to save on create")
|
|
176
|
+
create_cmd.add_argument("--cwd", help="Working directory for the durable startup command")
|
|
177
|
+
create_cmd.add_argument("--port", type=int, help="App port to publish for durable startup")
|
|
178
178
|
subparsers.add_parser("list", aliases=["ls"], help="List computers")
|
|
179
179
|
|
|
180
180
|
enter_cmd = subparsers.add_parser("enter", help="Enter a computer's console")
|
|
@@ -192,7 +192,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
192
192
|
delete_cmd = subparsers.add_parser("delete", aliases=["rm"], help="Delete a computer")
|
|
193
193
|
delete_cmd.add_argument("id", nargs="?", default=None, help="Computer ID (hostname)")
|
|
194
194
|
|
|
195
|
-
run_cmd = subparsers.add_parser("run", help="Run a command in a computer")
|
|
195
|
+
run_cmd = subparsers.add_parser("run", help="Run a one-shot command in a computer")
|
|
196
196
|
run_cmd.add_argument("id", nargs="?", default=None, help="Computer ID (hostname)")
|
|
197
197
|
run_cmd.add_argument(
|
|
198
198
|
"run_command",
|
|
@@ -207,11 +207,30 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
207
207
|
run_cmd.add_argument("--shell", help="Shell to use")
|
|
208
208
|
|
|
209
209
|
publish_cmd = subparsers.add_parser(
|
|
210
|
-
"publish", help="
|
|
210
|
+
"publish", help="Promote the running app on a local port into the durable public app"
|
|
211
211
|
)
|
|
212
212
|
publish_cmd.add_argument("id", nargs="?", default=None, help="Computer ID (hostname)")
|
|
213
213
|
publish_cmd.add_argument("port", nargs="?", type=int, default=None, help="Local app port")
|
|
214
214
|
|
|
215
|
+
startup_cmd = subparsers.add_parser("startup", help="Manage durable startup config")
|
|
216
|
+
startup_subparsers = startup_cmd.add_subparsers(dest="startup_action", required=True)
|
|
217
|
+
startup_show = startup_subparsers.add_parser("show", help="Show durable startup config")
|
|
218
|
+
startup_show.add_argument("id", nargs="?", default=None, help="Computer ID (hostname)")
|
|
219
|
+
startup_set = startup_subparsers.add_parser("set", help="Set durable startup config")
|
|
220
|
+
startup_set.add_argument("id", nargs="?", default=None, help="Computer ID (hostname)")
|
|
221
|
+
startup_set.add_argument("--command", dest="startup_value_command", required=True, help="Durable startup command")
|
|
222
|
+
startup_set.add_argument("--cwd", help="Working directory for the durable startup command")
|
|
223
|
+
startup_set.add_argument("--port", required=True, type=int, help="App port to publish for durable startup")
|
|
224
|
+
startup_clear = startup_subparsers.add_parser("clear", help="Clear durable startup config")
|
|
225
|
+
startup_clear.add_argument("id", nargs="?", default=None, help="Computer ID (hostname)")
|
|
226
|
+
|
|
227
|
+
service_cmd = subparsers.add_parser("service", help="Manage the durable published app service")
|
|
228
|
+
service_subparsers = service_cmd.add_subparsers(dest="service_action", required=True)
|
|
229
|
+
service_show = service_subparsers.add_parser("show", help="Show the durable published app service")
|
|
230
|
+
service_show.add_argument("id", nargs="?", default=None, help="Computer ID (hostname)")
|
|
231
|
+
service_clear = service_subparsers.add_parser("clear", help="Clear the durable published app service")
|
|
232
|
+
service_clear.add_argument("id", nargs="?", default=None, help="Computer ID (hostname)")
|
|
233
|
+
|
|
215
234
|
proxy_cmd = subparsers.add_parser("proxy", help="Manage background local port proxies")
|
|
216
235
|
proxy_subparsers = proxy_cmd.add_subparsers(dest="proxy_command", required=True)
|
|
217
236
|
proxy_start = proxy_subparsers.add_parser("start", help="Start one or more background local port proxies")
|
|
@@ -287,6 +306,20 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
287
306
|
)
|
|
288
307
|
if args.command == "publish":
|
|
289
308
|
return handle_publish(config, args.id, args.port)
|
|
309
|
+
if args.command == "startup":
|
|
310
|
+
if args.startup_action == "show":
|
|
311
|
+
return handle_startup_show(config, args.id)
|
|
312
|
+
if args.startup_action == "set":
|
|
313
|
+
return handle_startup_set(config, args.id, args.startup_value_command, args.cwd, args.port)
|
|
314
|
+
if args.startup_action == "clear":
|
|
315
|
+
return handle_startup_clear(config, args.id)
|
|
316
|
+
return report_error("unknown startup command")
|
|
317
|
+
if args.command == "service":
|
|
318
|
+
if args.service_action == "show":
|
|
319
|
+
return handle_service_show(config, args.id)
|
|
320
|
+
if args.service_action == "clear":
|
|
321
|
+
return handle_service_clear(config, args.id)
|
|
322
|
+
return report_error("unknown service command")
|
|
290
323
|
if args.command == "proxy":
|
|
291
324
|
if args.proxy_command == "start":
|
|
292
325
|
return handle_proxy_start(config, args.id, args.ports)
|
|
@@ -664,7 +697,7 @@ def _resolve_computer_ref(client: RuntimeClient, ref: str) -> str:
|
|
|
664
697
|
return resolved or ref
|
|
665
698
|
if len(matches) > 1:
|
|
666
699
|
raise RuntimeAPIError(f"multiple computers matched {ref!r}; use the exact computer id")
|
|
667
|
-
|
|
700
|
+
raise RuntimeAPIError(f"computer {ref!r} not found")
|
|
668
701
|
|
|
669
702
|
|
|
670
703
|
|
|
@@ -999,6 +1032,27 @@ def _render_network_panel(payload: dict[str, Any], *, title: str = "url") -> Non
|
|
|
999
1032
|
_UI.console().print(Panel(body, title=title, border_style="cyan", expand=False, padding=(0, 1)))
|
|
1000
1033
|
|
|
1001
1034
|
|
|
1035
|
+
def _render_startup_panel(payload: dict[str, Any], *, title: str = "startup") -> None:
|
|
1036
|
+
mods = _UI.rich()
|
|
1037
|
+
Panel, Text = mods["Panel"], mods["Text"]
|
|
1038
|
+
body = Text()
|
|
1039
|
+
configured = bool(payload.get("configured"))
|
|
1040
|
+
rows: list[tuple[str, str, str | None]] = [("configured", "yes" if configured else "no", "bold white" if configured else "dim")]
|
|
1041
|
+
if configured:
|
|
1042
|
+
rows.extend(
|
|
1043
|
+
[
|
|
1044
|
+
("command", str(payload.get("command") or "—"), "white"),
|
|
1045
|
+
("cwd", str(payload.get("cwd") or "/home/ubuntu"), "dim"),
|
|
1046
|
+
("port", str(payload.get("port") or "—"), "bold white"),
|
|
1047
|
+
]
|
|
1048
|
+
)
|
|
1049
|
+
for label, value, style in rows:
|
|
1050
|
+
body.append(f"{label:<10}", style="dim")
|
|
1051
|
+
body.append(value, style=style or "white")
|
|
1052
|
+
body.append("\n")
|
|
1053
|
+
_UI.console().print(Panel(body, title=title, border_style="cyan", expand=False, padding=(0, 1)))
|
|
1054
|
+
|
|
1055
|
+
|
|
1002
1056
|
def _render_whoami_panel(payload: dict[str, Any]) -> None:
|
|
1003
1057
|
mods = _UI.rich()
|
|
1004
1058
|
Panel, Text = mods["Panel"], mods["Text"]
|
|
@@ -1260,6 +1314,111 @@ def handle_url(config: RuntimeConfig, computer_id: str | None) -> int:
|
|
|
1260
1314
|
return report_success(result, lambda p: _render_network_panel(p, title="url"))
|
|
1261
1315
|
|
|
1262
1316
|
|
|
1317
|
+
def handle_startup_show(config: RuntimeConfig, computer_id: str | None) -> int:
|
|
1318
|
+
if (err := _require_api_key(config)) is not None:
|
|
1319
|
+
return err
|
|
1320
|
+
|
|
1321
|
+
if computer_id is None:
|
|
1322
|
+
if not _interactive():
|
|
1323
|
+
return report_error("computer id is required")
|
|
1324
|
+
picked = _pick_computer(config, "Pick a computer")
|
|
1325
|
+
if picked is None:
|
|
1326
|
+
return 0
|
|
1327
|
+
computer_id = picked.get("id") or picked.get("slug")
|
|
1328
|
+
if not computer_id:
|
|
1329
|
+
return report_error("computer is missing an id")
|
|
1330
|
+
|
|
1331
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1332
|
+
computer_id = _resolve_computer_ref(client, computer_id)
|
|
1333
|
+
result = client.get_startup_config(computer_id)
|
|
1334
|
+
return report_success(result, lambda p: _render_startup_panel(p, title="startup"))
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
def handle_startup_set(
|
|
1338
|
+
config: RuntimeConfig,
|
|
1339
|
+
computer_id: str | None,
|
|
1340
|
+
command: str | None,
|
|
1341
|
+
cwd: str | None,
|
|
1342
|
+
port: int | None,
|
|
1343
|
+
) -> int:
|
|
1344
|
+
if (err := _require_api_key(config)) is not None:
|
|
1345
|
+
return err
|
|
1346
|
+
if not computer_id:
|
|
1347
|
+
return report_error("computer id is required")
|
|
1348
|
+
if not command:
|
|
1349
|
+
return report_error("startup command is required")
|
|
1350
|
+
if port is None:
|
|
1351
|
+
return report_error("port is required")
|
|
1352
|
+
if port <= 0 or port > 65535:
|
|
1353
|
+
return report_error("port must be between 1 and 65535")
|
|
1354
|
+
|
|
1355
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1356
|
+
computer_id = _resolve_computer_ref(client, computer_id)
|
|
1357
|
+
result = _with_spinner(
|
|
1358
|
+
f"saving startup config for {computer_id}…",
|
|
1359
|
+
lambda: client.set_startup_config(computer_id, command=command, cwd=cwd, port=port),
|
|
1360
|
+
)
|
|
1361
|
+
return report_success(result, lambda p: _render_startup_panel(p, title="startup saved"))
|
|
1362
|
+
|
|
1363
|
+
|
|
1364
|
+
def handle_startup_clear(config: RuntimeConfig, computer_id: str | None) -> int:
|
|
1365
|
+
if (err := _require_api_key(config)) is not None:
|
|
1366
|
+
return err
|
|
1367
|
+
if not computer_id:
|
|
1368
|
+
return report_error("computer id is required")
|
|
1369
|
+
|
|
1370
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1371
|
+
computer_id = _resolve_computer_ref(client, computer_id)
|
|
1372
|
+
result = _with_spinner(
|
|
1373
|
+
f"clearing startup config for {computer_id}…",
|
|
1374
|
+
lambda: client.clear_startup_config(computer_id),
|
|
1375
|
+
)
|
|
1376
|
+
|
|
1377
|
+
def render(_: dict[str, Any]) -> None:
|
|
1378
|
+
_UI.console().print(f"[green]✓[/green] cleared durable startup config for [bold]{computer_id}[/bold]")
|
|
1379
|
+
|
|
1380
|
+
return report_success(result or {"configured": False}, render)
|
|
1381
|
+
|
|
1382
|
+
|
|
1383
|
+
def handle_service_show(config: RuntimeConfig, computer_id: str | None) -> int:
|
|
1384
|
+
if (err := _require_api_key(config)) is not None:
|
|
1385
|
+
return err
|
|
1386
|
+
|
|
1387
|
+
if computer_id is None:
|
|
1388
|
+
if not _interactive():
|
|
1389
|
+
return report_error("computer id is required")
|
|
1390
|
+
picked = _pick_computer(config, "Pick a computer")
|
|
1391
|
+
if picked is None:
|
|
1392
|
+
return 0
|
|
1393
|
+
computer_id = picked.get("id") or picked.get("slug")
|
|
1394
|
+
if not computer_id:
|
|
1395
|
+
return report_error("computer is missing an id")
|
|
1396
|
+
|
|
1397
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1398
|
+
computer_id = _resolve_computer_ref(client, computer_id)
|
|
1399
|
+
result = client.get_service_config(computer_id)
|
|
1400
|
+
return report_success(result, lambda p: _render_startup_panel(p, title="service"))
|
|
1401
|
+
|
|
1402
|
+
|
|
1403
|
+
def handle_service_clear(config: RuntimeConfig, computer_id: str | None) -> int:
|
|
1404
|
+
if (err := _require_api_key(config)) is not None:
|
|
1405
|
+
return err
|
|
1406
|
+
if not computer_id:
|
|
1407
|
+
return report_error("computer id is required")
|
|
1408
|
+
|
|
1409
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1410
|
+
computer_id = _resolve_computer_ref(client, computer_id)
|
|
1411
|
+
result = _with_spinner(
|
|
1412
|
+
f"clearing service for {computer_id}…",
|
|
1413
|
+
lambda: client.clear_service_config(computer_id),
|
|
1414
|
+
)
|
|
1415
|
+
|
|
1416
|
+
def render(_: dict[str, Any]) -> None:
|
|
1417
|
+
_UI.console().print(f"[green]✓[/green] cleared durable service for [bold]{computer_id}[/bold]")
|
|
1418
|
+
|
|
1419
|
+
return report_success(result or {"configured": False}, render)
|
|
1420
|
+
|
|
1421
|
+
|
|
1263
1422
|
def handle_start(config: RuntimeConfig, computer_id: str | None) -> int:
|
|
1264
1423
|
if (err := _require_api_key(config)) is not None:
|
|
1265
1424
|
return err
|
|
@@ -1433,7 +1592,7 @@ def handle_publish(config: RuntimeConfig, computer_id: str | None, port: int | N
|
|
|
1433
1592
|
|
|
1434
1593
|
def render(payload: dict[str, Any]) -> None:
|
|
1435
1594
|
_UI.console().print(
|
|
1436
|
-
f"[green]✓[/green] public url for [bold]{computer_id}[/bold]
|
|
1595
|
+
f"[green]✓[/green] public url for [bold]{computer_id}[/bold] now points at durable app port [bold]{payload.get('published_port') or port}[/bold]"
|
|
1437
1596
|
)
|
|
1438
1597
|
url = payload.get("public_url")
|
|
1439
1598
|
if url:
|
|
@@ -1526,6 +1685,17 @@ def handle_proxy_stop(config: RuntimeConfig, local_ports: list[int] | None, *, s
|
|
|
1526
1685
|
return report_success({"proxies": stopped}, render if _interactive() else None)
|
|
1527
1686
|
|
|
1528
1687
|
|
|
1688
|
+
def _proxy_preflight(base_url: str, api_key: str, computer_id: str, remote_port: int) -> None:
|
|
1689
|
+
client = RuntimeClient(base_url=base_url, api_key=api_key)
|
|
1690
|
+
ticket_payload = client.create_proxy_ticket(computer_id, remote_port)
|
|
1691
|
+
ws_url = _resolve_ws_url(base_url, ticket_payload.get("ws_url"))
|
|
1692
|
+
if not ws_url:
|
|
1693
|
+
raise RuntimeAPIError("proxy endpoint did not return a websocket url")
|
|
1694
|
+
with websocket_connect(ws_url, open_timeout=DEFAULT_WARMUP_TIMEOUT, close_timeout=1, max_size=None):
|
|
1695
|
+
return
|
|
1696
|
+
|
|
1697
|
+
|
|
1698
|
+
|
|
1529
1699
|
def run_proxy_agent(base_url: str, api_key: str, computer_id: str, display_name: str, local_port: int, remote_port: int) -> int:
|
|
1530
1700
|
if not api_key:
|
|
1531
1701
|
print("proxy agent missing api key", file=sys.stderr)
|
|
@@ -1541,6 +1711,14 @@ def run_proxy_agent(base_url: str, api_key: str, computer_id: str, display_name:
|
|
|
1541
1711
|
print(f"proxy listen failed on localhost:{local_port}: {exc}", file=sys.stderr)
|
|
1542
1712
|
return 1
|
|
1543
1713
|
|
|
1714
|
+
try:
|
|
1715
|
+
_proxy_preflight(base_url, api_key, computer_id, remote_port)
|
|
1716
|
+
except Exception as exc:
|
|
1717
|
+
with contextlib.suppress(OSError):
|
|
1718
|
+
listener.close()
|
|
1719
|
+
print(f"proxy preflight failed for localhost:{local_port} -> {computer_id}:{remote_port}: {exc}", file=sys.stderr)
|
|
1720
|
+
return 1
|
|
1721
|
+
|
|
1544
1722
|
proxy_dir = _proxy_dir()
|
|
1545
1723
|
proxy_dir.mkdir(parents=True, exist_ok=True)
|
|
1546
1724
|
state = {
|
|
@@ -82,6 +82,31 @@ class RuntimeClient:
|
|
|
82
82
|
def get_computer_network(self, computer_id: str) -> dict[str, Any]:
|
|
83
83
|
return self._request("GET", f"/api/computers/{computer_id}/network", auth_required=True)
|
|
84
84
|
|
|
85
|
+
def get_startup_config(self, computer_id: str) -> dict[str, Any]:
|
|
86
|
+
return self._request("GET", f"/api/computers/{computer_id}/startup", auth_required=True)
|
|
87
|
+
|
|
88
|
+
def get_service_config(self, computer_id: str) -> dict[str, Any]:
|
|
89
|
+
return self.get_startup_config(computer_id)
|
|
90
|
+
|
|
91
|
+
def set_startup_config(
|
|
92
|
+
self,
|
|
93
|
+
computer_id: str,
|
|
94
|
+
*,
|
|
95
|
+
command: str,
|
|
96
|
+
cwd: str | None = None,
|
|
97
|
+
port: int,
|
|
98
|
+
) -> dict[str, Any]:
|
|
99
|
+
body: dict[str, Any] = {"command": command, "port": port}
|
|
100
|
+
if cwd is not None:
|
|
101
|
+
body["cwd"] = cwd
|
|
102
|
+
return self._request("PUT", f"/api/computers/{computer_id}/startup", json=body, auth_required=True)
|
|
103
|
+
|
|
104
|
+
def clear_startup_config(self, computer_id: str) -> dict[str, Any]:
|
|
105
|
+
return self._request("DELETE", f"/api/computers/{computer_id}/startup", auth_required=True)
|
|
106
|
+
|
|
107
|
+
def clear_service_config(self, computer_id: str) -> dict[str, Any]:
|
|
108
|
+
return self.clear_startup_config(computer_id)
|
|
109
|
+
|
|
85
110
|
def start_computer(self, computer_id: str) -> dict[str, Any]:
|
|
86
111
|
return self._request(
|
|
87
112
|
"POST",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runtime-sdk
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.11
|
|
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
|
|
@@ -54,16 +54,27 @@ runtime logout
|
|
|
54
54
|
runtime login --api-key rt_live_...
|
|
55
55
|
|
|
56
56
|
# Computers
|
|
57
|
-
runtime create #
|
|
57
|
+
runtime create # creates a computer with the starter app already published
|
|
58
58
|
runtime create myapp --command "python3 app.py" --cwd /home/ubuntu --port 3000
|
|
59
59
|
runtime enter <name-or-id> # accepts slug/name like test, or the computer id
|
|
60
60
|
runtime list
|
|
61
61
|
runtime info <id>
|
|
62
62
|
runtime start <id>
|
|
63
|
-
runtime run <id> "echo hello"
|
|
63
|
+
runtime run <id> "echo hello" # one-shot foreground command only
|
|
64
64
|
runtime run <id> "apt install -y nodejs" --uid 0
|
|
65
|
-
runtime
|
|
65
|
+
runtime startup show <id>
|
|
66
|
+
runtime startup set <id> --command "python3 app.py" --cwd /home/ubuntu --port 3000
|
|
67
|
+
runtime startup clear <id> # low-level durable service config
|
|
68
|
+
runtime service show <id> # user-facing alias for the durable published app
|
|
69
|
+
runtime service clear <id>
|
|
70
|
+
runtime publish <id> 3000 # promote the running app on port 3000 to the public durable app
|
|
66
71
|
runtime delete <id>
|
|
72
|
+
|
|
73
|
+
# Inside a running computer, the helper installed by Runtime can manage the
|
|
74
|
+
# durable app without leaving the sandbox:
|
|
75
|
+
# runtime-env publish 3000
|
|
76
|
+
# runtime-env service show
|
|
77
|
+
# runtime-env service clear
|
|
67
78
|
```
|
|
68
79
|
|
|
69
80
|
## Python
|
|
@@ -73,12 +84,12 @@ from runtime_sdk import RuntimeClient
|
|
|
73
84
|
|
|
74
85
|
client = RuntimeClient(base_url="https://api.runruntime.dev", api_key="rt_live_...")
|
|
75
86
|
|
|
76
|
-
# Create a computer
|
|
87
|
+
# Create a computer. New computers start with the starter app already published.
|
|
77
88
|
computer = client.create_computer()
|
|
78
89
|
print(computer["public_url"]) # https://goldbird.runruntime.dev
|
|
79
90
|
|
|
80
|
-
# Or create one with
|
|
81
|
-
# That
|
|
91
|
+
# Or create one with an explicit durable app command.
|
|
92
|
+
# That saved service is replayed after cold restore / start.
|
|
82
93
|
app = client.create_computer(
|
|
83
94
|
slug="myapp",
|
|
84
95
|
command="python3 app.py",
|
|
@@ -93,7 +104,8 @@ print(result["stdout"])
|
|
|
93
104
|
# Wake a cold computer explicitly
|
|
94
105
|
client.start_computer(app["id"])
|
|
95
106
|
|
|
96
|
-
#
|
|
107
|
+
# Promote the running app on a local port to the durable public app.
|
|
108
|
+
# Runtime inspects the listening process and saves its command + cwd when possible.
|
|
97
109
|
client.publish_port(computer["id"], 3000)
|
|
98
110
|
|
|
99
111
|
# List, info, delete
|
|
@@ -120,10 +132,16 @@ make smoke
|
|
|
120
132
|
Use `make deploy` instead of `make sync` when migrations, env
|
|
121
133
|
files, Caddy, or systemd units changed.
|
|
122
134
|
|
|
123
|
-
Cold restore and
|
|
124
|
-
computers
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
Cold restore and explicit `runtime start` replay the saved published app command.
|
|
136
|
+
New computers seed that durable app from the starter workspace. Later,
|
|
137
|
+
`runtime publish <id> <port>` can promote a running listener into the saved
|
|
138
|
+
public app definition by inspecting the live process. Inside a running computer,
|
|
139
|
+
`runtime-env publish <port>` does the same thing using a computer-scoped token
|
|
140
|
+
installed by Runtime. `runtime service show|clear` and `runtime-env service
|
|
141
|
+
show|clear` expose that same durable app state directly. The low-level
|
|
142
|
+
`runtime startup ...` commands still map to the same durable state. A one-off
|
|
143
|
+
`runtime run` stays one-shot: the filesystem is restored after going cold, but
|
|
144
|
+
that ad-hoc process is not.
|
|
127
145
|
|
|
128
146
|
Run the SDK unit tests through the backend project environment:
|
|
129
147
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|