runtime-sdk 0.4.12__tar.gz → 0.4.14__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runtime-sdk
3
- Version: 0.4.12
3
+ Version: 0.4.14
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 `http://127.0.0.1:8080` by default. Override it with:
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=https://api.runruntime.dev
43
+ export RUNTIME_BASE_URL=http://127.0.0.1:8080
42
44
  ```
43
45
 
44
46
  Or pass `--base-url` per command.
@@ -47,6 +49,8 @@ Or pass `--base-url` per command.
47
49
 
48
50
  ```bash
49
51
  # Auth
52
+ runtime # first run in a real terminal prompts for email + verification code, then creates your first computer
53
+ runtime login # interactive email + verification code flow
50
54
  runtime login you@example.com
51
55
  runtime verify 123456
52
56
  runtime whoami
@@ -16,10 +16,12 @@ uv tool upgrade runtime-sdk
16
16
 
17
17
  ## Configure
18
18
 
19
- The CLI talks to `http://127.0.0.1:8080` by default. Override it with:
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=https://api.runruntime.dev
24
+ export RUNTIME_BASE_URL=http://127.0.0.1:8080
23
25
  ```
24
26
 
25
27
  Or pass `--base-url` per command.
@@ -28,6 +30,8 @@ Or pass `--base-url` per command.
28
30
 
29
31
  ```bash
30
32
  # Auth
33
+ runtime # first run in a real terminal prompts for email + verification code, then creates your first computer
34
+ runtime login # interactive email + verification code flow
31
35
  runtime login you@example.com
32
36
  runtime verify 123456
33
37
  runtime whoami
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "runtime-sdk"
3
- version = "0.4.12"
3
+ version = "0.4.14"
4
4
  description = "Runtime Python SDK and CLI"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -132,7 +132,7 @@ class _UI:
132
132
 
133
133
  def build_parser() -> argparse.ArgumentParser:
134
134
  parser = argparse.ArgumentParser(prog="runtime")
135
- parser.add_argument("--base-url", help="Runtime API base URL")
135
+ parser.add_argument("--base-url", help="Override the Runtime API base URL")
136
136
  parser.add_argument("--version", action="version", version=f"runtime-sdk {__version__}")
137
137
 
138
138
  # Subcommand is optional: bare `runtime` defaults to `create`.
@@ -263,20 +263,21 @@ def main(argv: list[str] | None = None) -> int:
263
263
  parser = build_parser()
264
264
  args = parser.parse_args(argv)
265
265
 
266
- # Bare `runtime` → create flow.
267
- if args.command is None:
268
- args.command = "create"
269
- args.name = None
270
-
271
266
  try:
272
267
  config = load_config()
273
- base_url = (
274
- args.base_url
275
- or os.environ.get("RUNTIME_BASE_URL")
276
- or config.base_url
277
- or DEFAULT_BASE_URL
278
- )
279
- config.base_url = base_url
268
+ if args.base_url:
269
+ config.base_url = args.base_url
270
+ elif os.environ.get("RUNTIME_BASE_URL"):
271
+ config.base_url = str(os.environ.get("RUNTIME_BASE_URL"))
272
+ else:
273
+ config.base_url = config.configured_base_url or DEFAULT_BASE_URL
274
+
275
+ # Bare `runtime` → first-run onboarding for interactive users, create otherwise.
276
+ if args.command is None:
277
+ if _interactive() and not config.api_key:
278
+ return handle_onboarding(config)
279
+ args.command = "create"
280
+ args.name = None
280
281
 
281
282
  if args.command == "signup":
282
283
  return handle_signup(config, args.email, args.name)
@@ -1181,14 +1182,7 @@ def _looks_like_api_key(value: str | None) -> bool:
1181
1182
  return candidate.startswith("rt_live_")
1182
1183
 
1183
1184
 
1184
- def _start_verification_flow(config: RuntimeConfig, email: str | None, name: str | None) -> int:
1185
- if email is None:
1186
- if not _interactive():
1187
- return report_error("email is required")
1188
- email = _prompt_text("Email")
1189
- if not email:
1190
- return report_error("email is required")
1191
-
1185
+ def _begin_verification_flow(config: RuntimeConfig, email: str, name: str | None) -> dict[str, Any]:
1192
1186
  client = RuntimeClient(base_url=config.base_url)
1193
1187
  result = client.start_verification(email, name)
1194
1188
  config.pending_signup = PendingSignup(
@@ -1197,6 +1191,19 @@ def _start_verification_flow(config: RuntimeConfig, email: str | None, name: str
1197
1191
  expires_at=result.get("expires_at"),
1198
1192
  )
1199
1193
  save_config(config)
1194
+ return result
1195
+
1196
+
1197
+
1198
+ def _start_verification_flow(config: RuntimeConfig, email: str | None, name: str | None) -> int:
1199
+ if email is None:
1200
+ if not _interactive():
1201
+ return report_error("email is required")
1202
+ email = _prompt_text("Email")
1203
+ if not email:
1204
+ return report_error("email is required")
1205
+
1206
+ result = _begin_verification_flow(config, email, name)
1200
1207
 
1201
1208
  def render(payload: dict[str, Any]) -> None:
1202
1209
  _UI.console().print(
@@ -1209,6 +1216,33 @@ def _start_verification_flow(config: RuntimeConfig, email: str | None, name: str
1209
1216
  return report_success(result, render)
1210
1217
 
1211
1218
 
1219
+
1220
+ def _interactive_email_auth_flow(config: RuntimeConfig, email: str | None, name: str | None) -> int:
1221
+ pending = config.pending_signup
1222
+ if email is None and pending is not None and pending.email:
1223
+ _UI.console().print(
1224
+ f"[green]✓[/green] verification code already sent to [bold]{pending.email}[/bold]"
1225
+ )
1226
+ code = _prompt_text("Verification code")
1227
+ if not code:
1228
+ return report_error("verification code is required")
1229
+ return handle_verify(config, code, pending.flow_id)
1230
+
1231
+ if email is None:
1232
+ email = _prompt_text("Email")
1233
+ if not email:
1234
+ return report_error("email is required")
1235
+
1236
+ _begin_verification_flow(config, email, name)
1237
+ _UI.console().print(
1238
+ f"[green]✓[/green] verification code sent to [bold]{email}[/bold]"
1239
+ )
1240
+ code = _prompt_text("Verification code")
1241
+ if not code:
1242
+ return report_error("verification code is required")
1243
+ return handle_verify(config, code, None)
1244
+
1245
+
1212
1246
  def _login_with_api_key(config: RuntimeConfig, api_key: str | None) -> int:
1213
1247
  if api_key is None or api_key == "":
1214
1248
  if not _interactive():
@@ -1235,6 +1269,8 @@ def _login_with_api_key(config: RuntimeConfig, api_key: str | None) -> int:
1235
1269
 
1236
1270
 
1237
1271
  def handle_signup(config: RuntimeConfig, email: str | None, name: str | None) -> int:
1272
+ if _interactive():
1273
+ return _interactive_email_auth_flow(config, email, name)
1238
1274
  return _start_verification_flow(config, email, name)
1239
1275
 
1240
1276
 
@@ -1284,6 +1320,8 @@ def handle_login(
1284
1320
  if _looks_like_api_key(identifier):
1285
1321
  return _login_with_api_key(config, identifier)
1286
1322
 
1323
+ if _interactive():
1324
+ return _interactive_email_auth_flow(config, identifier, name)
1287
1325
  return _start_verification_flow(config, identifier, name)
1288
1326
 
1289
1327
 
@@ -1369,6 +1407,14 @@ def handle_api_keys_revoke(config: RuntimeConfig, key_id: str | None) -> int:
1369
1407
 
1370
1408
 
1371
1409
 
1410
+ def handle_onboarding(config: RuntimeConfig) -> int:
1411
+ code = handle_login(config, None, None, None)
1412
+ if code != 0:
1413
+ return code
1414
+ return handle_create(config, None, None, None, None)
1415
+
1416
+
1417
+
1372
1418
  def handle_create(
1373
1419
  config: RuntimeConfig,
1374
1420
  name: str | None,
@@ -2141,7 +2187,7 @@ def _strip_terminal_noise(text: str) -> str:
2141
2187
  filtered: list[str] = []
2142
2188
  for line in lines:
2143
2189
  stripped = line.rstrip("\r\n")
2144
- if stripped == "Running bootstrap command: export PS1='\\[\\033[1;34m\\]runtime@\\h\\[\\033[0m\\]:\\w\\$ '":
2190
+ if stripped.startswith("Running bootstrap command: export PS1='\\[\\033[1;34m\\]runtime@"):
2145
2191
  continue
2146
2192
  if stripped == "Connected! Press Ctrl+] to exit.":
2147
2193
  continue
@@ -6,7 +6,7 @@ from dataclasses import asdict, dataclass
6
6
  from pathlib import Path
7
7
 
8
8
 
9
- DEFAULT_BASE_URL = "http://127.0.0.1:8080"
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(base_url=os.environ.get("RUNTIME_BASE_URL", DEFAULT_BASE_URL))
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=data.get("base_url") or os.environ.get("RUNTIME_BASE_URL", DEFAULT_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
- payload = asdict(config)
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.12
3
+ Version: 0.4.14
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 `http://127.0.0.1:8080` by default. Override it with:
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=https://api.runruntime.dev
43
+ export RUNTIME_BASE_URL=http://127.0.0.1:8080
42
44
  ```
43
45
 
44
46
  Or pass `--base-url` per command.
@@ -47,6 +49,8 @@ Or pass `--base-url` per command.
47
49
 
48
50
  ```bash
49
51
  # Auth
52
+ runtime # first run in a real terminal prompts for email + verification code, then creates your first computer
53
+ runtime login # interactive email + verification code flow
50
54
  runtime login you@example.com
51
55
  runtime verify 123456
52
56
  runtime whoami
File without changes