runtime-sdk 0.4.13__tar.gz → 0.4.15__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.13 → runtime_sdk-0.4.15}/PKG-INFO +5 -3
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/README.md +4 -2
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/pyproject.toml +1 -1
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/scripts/runtime_sdk/cli.py +82 -8
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/scripts/runtime_sdk/client.py +30 -0
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/scripts/runtime_sdk/config.py +13 -4
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/scripts/runtime_sdk.egg-info/PKG-INFO +5 -3
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/scripts/runtime_sdk/__init__.py +0 -0
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/scripts/runtime_sdk.egg-info/SOURCES.txt +0 -0
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/scripts/runtime_sdk.egg-info/dependency_links.txt +0 -0
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/scripts/runtime_sdk.egg-info/entry_points.txt +0 -0
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/scripts/runtime_sdk.egg-info/requires.txt +0 -0
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/scripts/runtime_sdk.egg-info/top_level.txt +0 -0
- {runtime_sdk-0.4.13 → runtime_sdk-0.4.15}/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.15
|
|
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
|
|
@@ -35,10 +35,12 @@ uv tool upgrade runtime-sdk
|
|
|
35
35
|
|
|
36
36
|
## Configure
|
|
37
37
|
|
|
38
|
-
The CLI talks to `
|
|
38
|
+
The CLI talks to `https://api.runruntime.dev` by default.
|
|
39
|
+
|
|
40
|
+
For local or self-hosted Runtime, override it with:
|
|
39
41
|
|
|
40
42
|
```bash
|
|
41
|
-
export RUNTIME_BASE_URL=
|
|
43
|
+
export RUNTIME_BASE_URL=http://127.0.0.1:8080
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
Or pass `--base-url` per command.
|
|
@@ -16,10 +16,12 @@ uv tool upgrade runtime-sdk
|
|
|
16
16
|
|
|
17
17
|
## Configure
|
|
18
18
|
|
|
19
|
-
The CLI talks to `
|
|
19
|
+
The CLI talks to `https://api.runruntime.dev` by default.
|
|
20
|
+
|
|
21
|
+
For local or self-hosted Runtime, override it with:
|
|
20
22
|
|
|
21
23
|
```bash
|
|
22
|
-
export RUNTIME_BASE_URL=
|
|
24
|
+
export RUNTIME_BASE_URL=http://127.0.0.1:8080
|
|
23
25
|
```
|
|
24
26
|
|
|
25
27
|
Or pass `--base-url` per command.
|
|
@@ -15,6 +15,7 @@ import termios
|
|
|
15
15
|
import threading
|
|
16
16
|
import time
|
|
17
17
|
import tty
|
|
18
|
+
import webbrowser
|
|
18
19
|
from datetime import datetime, timezone
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
from typing import Any, Callable
|
|
@@ -132,7 +133,7 @@ class _UI:
|
|
|
132
133
|
|
|
133
134
|
def build_parser() -> argparse.ArgumentParser:
|
|
134
135
|
parser = argparse.ArgumentParser(prog="runtime")
|
|
135
|
-
parser.add_argument("--base-url", help="Runtime API base URL")
|
|
136
|
+
parser.add_argument("--base-url", help="Override the Runtime API base URL")
|
|
136
137
|
parser.add_argument("--version", action="version", version=f"runtime-sdk {__version__}")
|
|
137
138
|
|
|
138
139
|
# Subcommand is optional: bare `runtime` defaults to `create`.
|
|
@@ -173,6 +174,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
173
174
|
api_keys_revoke = api_keys_subparsers.add_parser("revoke", help="Revoke an API key")
|
|
174
175
|
api_keys_revoke.add_argument("id", nargs="?", default=None, help="API key id")
|
|
175
176
|
|
|
177
|
+
integrations_cmd = subparsers.add_parser("integrations", help="Connect account-level integrations")
|
|
178
|
+
integrations_subparsers = integrations_cmd.add_subparsers(dest="integrations_action", required=True)
|
|
179
|
+
integrations_connect = integrations_subparsers.add_parser("connect", help="Connect an integration")
|
|
180
|
+
integrations_connect.add_argument("provider", choices=["github"])
|
|
181
|
+
integrations_connect.add_argument("--slug", default="github", help="Integration slug to create (default: github)")
|
|
182
|
+
integrations_connect.add_argument("--owner", default=None, help="GitHub username or org where the app is installed")
|
|
183
|
+
integrations_connect.add_argument("--no-open", action="store_true", help="Print the install URL instead of opening a browser")
|
|
184
|
+
|
|
176
185
|
create_cmd = subparsers.add_parser("create", help="Create a new computer with the starter app published by default")
|
|
177
186
|
create_cmd.add_argument(
|
|
178
187
|
"name",
|
|
@@ -265,13 +274,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
265
274
|
|
|
266
275
|
try:
|
|
267
276
|
config = load_config()
|
|
268
|
-
base_url
|
|
269
|
-
args.base_url
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
config.base_url = base_url
|
|
277
|
+
if args.base_url:
|
|
278
|
+
config.base_url = args.base_url
|
|
279
|
+
elif os.environ.get("RUNTIME_BASE_URL"):
|
|
280
|
+
config.base_url = str(os.environ.get("RUNTIME_BASE_URL"))
|
|
281
|
+
else:
|
|
282
|
+
config.base_url = config.configured_base_url or DEFAULT_BASE_URL
|
|
275
283
|
|
|
276
284
|
# Bare `runtime` → first-run onboarding for interactive users, create otherwise.
|
|
277
285
|
if args.command is None:
|
|
@@ -298,6 +306,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
298
306
|
if args.api_keys_action == "revoke":
|
|
299
307
|
return handle_api_keys_revoke(config, args.id)
|
|
300
308
|
return report_error("unknown api-keys command")
|
|
309
|
+
if args.command == "integrations":
|
|
310
|
+
if args.integrations_action == "connect":
|
|
311
|
+
return handle_integrations_connect(config, args.provider, args.slug, args.owner, no_open=args.no_open)
|
|
312
|
+
return report_error("unknown integrations command")
|
|
301
313
|
if args.command == "create":
|
|
302
314
|
return handle_create(
|
|
303
315
|
config,
|
|
@@ -1408,6 +1420,68 @@ def handle_api_keys_revoke(config: RuntimeConfig, key_id: str | None) -> int:
|
|
|
1408
1420
|
|
|
1409
1421
|
|
|
1410
1422
|
|
|
1423
|
+
def handle_integrations_connect(
|
|
1424
|
+
config: RuntimeConfig,
|
|
1425
|
+
provider: str,
|
|
1426
|
+
slug: str | None,
|
|
1427
|
+
owner: str | None,
|
|
1428
|
+
*,
|
|
1429
|
+
no_open: bool = False,
|
|
1430
|
+
) -> int:
|
|
1431
|
+
if (err := _require_api_key(config)) is not None:
|
|
1432
|
+
return err
|
|
1433
|
+
if provider != "github":
|
|
1434
|
+
return report_error(f"unsupported integration provider: {provider}")
|
|
1435
|
+
|
|
1436
|
+
client = RuntimeClient(base_url=config.base_url, api_key=config.api_key)
|
|
1437
|
+
payload = _with_spinner("getting GitHub install link…", lambda: client.start_github_connect(slug=slug))
|
|
1438
|
+
install_url = str(payload.get("install_url") or "").strip()
|
|
1439
|
+
resolved_slug = str(payload.get("slug") or slug or "github").strip() or "github"
|
|
1440
|
+
if not install_url:
|
|
1441
|
+
return report_error("server did not return a GitHub install URL")
|
|
1442
|
+
|
|
1443
|
+
opened = False
|
|
1444
|
+
if not no_open:
|
|
1445
|
+
with contextlib.suppress(Exception):
|
|
1446
|
+
opened = bool(webbrowser.open(install_url))
|
|
1447
|
+
|
|
1448
|
+
if _interactive():
|
|
1449
|
+
action = "opened in your browser" if opened else "copy this URL into your browser"
|
|
1450
|
+
_UI.console().print(f"[cyan]→[/cyan] {action}: [link={install_url}]{install_url}[/link]")
|
|
1451
|
+
_UI.console().print("[dim]Install the RuntimeVM GitHub App, then come back here.[/dim]")
|
|
1452
|
+
input("Press Enter after the GitHub App is installed... ")
|
|
1453
|
+
if not owner:
|
|
1454
|
+
owner = _prompt_text("GitHub owner (username or org)")
|
|
1455
|
+
elif not owner:
|
|
1456
|
+
return report_error("--owner is required without a TTY")
|
|
1457
|
+
|
|
1458
|
+
resolved_owner = str(owner or "").strip()
|
|
1459
|
+
if not resolved_owner:
|
|
1460
|
+
return report_error("GitHub owner is required")
|
|
1461
|
+
|
|
1462
|
+
integration = _with_spinner(
|
|
1463
|
+
"connecting GitHub…",
|
|
1464
|
+
lambda: client.create_integration(
|
|
1465
|
+
slug=resolved_slug,
|
|
1466
|
+
provider="github",
|
|
1467
|
+
transport="scoped_credential",
|
|
1468
|
+
backend="native",
|
|
1469
|
+
credential_source="external_account",
|
|
1470
|
+
credential_ref=resolved_owner,
|
|
1471
|
+
config={},
|
|
1472
|
+
),
|
|
1473
|
+
)
|
|
1474
|
+
|
|
1475
|
+
def render(result: dict[str, Any]) -> None:
|
|
1476
|
+
connected = result.get("integration") or {}
|
|
1477
|
+
_UI.console().print(
|
|
1478
|
+
f"[green]✓[/green] connected [bold]{connected.get('slug', resolved_slug)}[/bold] → [bold]{connected.get('credential_ref', resolved_owner)}[/bold]"
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
return report_success({"integration": integration, "install_url": install_url}, render)
|
|
1482
|
+
|
|
1483
|
+
|
|
1484
|
+
|
|
1411
1485
|
def handle_onboarding(config: RuntimeConfig) -> int:
|
|
1412
1486
|
code = handle_login(config, None, None, None)
|
|
1413
1487
|
if code != 0:
|
|
@@ -52,6 +52,36 @@ class RuntimeClient:
|
|
|
52
52
|
def revoke_api_key(self, key_id: int) -> dict[str, Any]:
|
|
53
53
|
return self._request("POST", f"/api/api-keys/{key_id}/revoke", auth_required=True)
|
|
54
54
|
|
|
55
|
+
# --- Integrations ---
|
|
56
|
+
|
|
57
|
+
def start_github_connect(self, *, slug: str | None = None) -> dict[str, Any]:
|
|
58
|
+
body: dict[str, Any] = {}
|
|
59
|
+
if slug:
|
|
60
|
+
body["slug"] = slug
|
|
61
|
+
return self._request("POST", "/api/integrations/github/connect", json=body, auth_required=True)
|
|
62
|
+
|
|
63
|
+
def create_integration(
|
|
64
|
+
self,
|
|
65
|
+
*,
|
|
66
|
+
slug: str,
|
|
67
|
+
provider: str,
|
|
68
|
+
transport: str,
|
|
69
|
+
backend: str,
|
|
70
|
+
credential_source: str,
|
|
71
|
+
credential_ref: str,
|
|
72
|
+
config: dict[str, Any] | None = None,
|
|
73
|
+
) -> dict[str, Any]:
|
|
74
|
+
body: dict[str, Any] = {
|
|
75
|
+
"slug": slug,
|
|
76
|
+
"provider": provider,
|
|
77
|
+
"transport": transport,
|
|
78
|
+
"backend": backend,
|
|
79
|
+
"credential_source": credential_source,
|
|
80
|
+
"credential_ref": credential_ref,
|
|
81
|
+
"config": config or {},
|
|
82
|
+
}
|
|
83
|
+
return self._request("POST", "/api/integrations", json=body, auth_required=True)
|
|
84
|
+
|
|
55
85
|
# --- Computers ---
|
|
56
86
|
|
|
57
87
|
def create_computer(
|
|
@@ -6,7 +6,7 @@ from dataclasses import asdict, dataclass
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
DEFAULT_BASE_URL = "
|
|
9
|
+
DEFAULT_BASE_URL = "https://api.runruntime.dev"
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class RuntimeConfigError(RuntimeError):
|
|
@@ -23,6 +23,7 @@ class PendingSignup:
|
|
|
23
23
|
@dataclass(slots=True)
|
|
24
24
|
class RuntimeConfig:
|
|
25
25
|
base_url: str = DEFAULT_BASE_URL
|
|
26
|
+
configured_base_url: str | None = None
|
|
26
27
|
api_key: str | None = None
|
|
27
28
|
pending_signup: PendingSignup | None = None
|
|
28
29
|
|
|
@@ -37,7 +38,7 @@ def config_path() -> Path:
|
|
|
37
38
|
def load_config() -> RuntimeConfig:
|
|
38
39
|
path = config_path()
|
|
39
40
|
if not path.exists():
|
|
40
|
-
return RuntimeConfig(
|
|
41
|
+
return RuntimeConfig()
|
|
41
42
|
|
|
42
43
|
try:
|
|
43
44
|
data = json.loads(path.read_text())
|
|
@@ -48,9 +49,11 @@ def load_config() -> RuntimeConfig:
|
|
|
48
49
|
if not isinstance(data, dict):
|
|
49
50
|
raise RuntimeConfigError("config file must contain a JSON object")
|
|
50
51
|
pending = data.get("pending_signup")
|
|
52
|
+
configured_base_url = data.get("base_url") or None
|
|
51
53
|
try:
|
|
52
54
|
return RuntimeConfig(
|
|
53
|
-
base_url=
|
|
55
|
+
base_url=configured_base_url or DEFAULT_BASE_URL,
|
|
56
|
+
configured_base_url=configured_base_url,
|
|
54
57
|
api_key=data.get("api_key"),
|
|
55
58
|
pending_signup=PendingSignup(**pending) if pending else None,
|
|
56
59
|
)
|
|
@@ -60,7 +63,13 @@ def load_config() -> RuntimeConfig:
|
|
|
60
63
|
|
|
61
64
|
def save_config(config: RuntimeConfig) -> None:
|
|
62
65
|
path = config_path()
|
|
63
|
-
|
|
66
|
+
configured_base_url = config.configured_base_url
|
|
67
|
+
payload = {
|
|
68
|
+
"api_key": config.api_key,
|
|
69
|
+
"pending_signup": asdict(config.pending_signup) if config.pending_signup else None,
|
|
70
|
+
}
|
|
71
|
+
if configured_base_url is not None:
|
|
72
|
+
payload["base_url"] = configured_base_url
|
|
64
73
|
try:
|
|
65
74
|
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
66
75
|
path.write_text(json.dumps(payload, indent=2) + "\n")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runtime-sdk
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.15
|
|
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
|
|
@@ -35,10 +35,12 @@ uv tool upgrade runtime-sdk
|
|
|
35
35
|
|
|
36
36
|
## Configure
|
|
37
37
|
|
|
38
|
-
The CLI talks to `
|
|
38
|
+
The CLI talks to `https://api.runruntime.dev` by default.
|
|
39
|
+
|
|
40
|
+
For local or self-hosted Runtime, override it with:
|
|
39
41
|
|
|
40
42
|
```bash
|
|
41
|
-
export RUNTIME_BASE_URL=
|
|
43
|
+
export RUNTIME_BASE_URL=http://127.0.0.1:8080
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
Or pass `--base-url` per command.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|