simulac 0.0.2__tar.gz → 0.0.4__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.
Files changed (85) hide show
  1. {simulac-0.0.2 → simulac-0.0.4}/PKG-INFO +8 -8
  2. {simulac-0.0.2 → simulac-0.0.4}/README.md +1 -1
  3. {simulac-0.0.2 → simulac-0.0.4}/pyproject.toml +5 -5
  4. simulac-0.0.4/simulac/.DS_Store +0 -0
  5. {simulac-0.0.2 → simulac-0.0.4}/simulac/__init__.py +3 -2
  6. simulac-0.0.4/simulac/cli/__init__.py +90 -0
  7. simulac-0.0.4/simulac/cli/auth.py +127 -0
  8. simulac-0.0.4/simulac/cli/benchmark.py +40 -0
  9. simulac-0.0.4/simulac/cli/common.py +147 -0
  10. simulac-0.0.4/simulac/cli/config.py +40 -0
  11. {simulac-0.0.2 → simulac-0.0.4}/simulac/lib/gym_style/__init__.py +23 -32
  12. simulac-0.0.4/simulac/lib/gym_style/gym_style_environment.py +512 -0
  13. simulac-0.0.4/simulac/lib/test/benchmark_test.py +333 -0
  14. {simulac-0.0.2 → simulac-0.0.4}/simulac/lib/test/entity_test.py +4 -4
  15. simulac-0.0.4/simulac/lib/test/gym_style_environment_integration_test.py +139 -0
  16. simulac-0.0.4/simulac/lib/test/gym_style_environment_test.py +348 -0
  17. {simulac-0.0.2 → simulac-0.0.4}/simulac/lib/world_maker/__init__.py +2 -1
  18. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/world_maker.py +7 -4
  19. simulac-0.0.2/simulac/cli/__init__.py +0 -9
  20. simulac-0.0.2/simulac/cli/auth.py +0 -160
  21. simulac-0.0.2/simulac/lib/gym_style/gym_style_environment.py +0 -349
  22. simulac-0.0.2/simulac/lib/test/benchmark_test.py +0 -1174
  23. simulac-0.0.2/simulac/lib/test/benchmark_test2.py +0 -917
  24. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/__init__.py +0 -0
  25. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/envvar/__init__.py +0 -0
  26. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/envvar/envvar.py +0 -0
  27. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/envvar/envvar_service.py +0 -0
  28. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/error/error.py +0 -0
  29. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/__init__.py +0 -0
  30. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/descriptor.py +0 -0
  31. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/extensions.py +0 -0
  32. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/graph.py +0 -0
  33. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/instantiate.py +0 -0
  34. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/instantiate_service.py +0 -0
  35. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/service_collection.py +0 -0
  36. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/network/__init__.py +0 -0
  37. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/network/network.py +0 -0
  38. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/result/result.py +0 -0
  39. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/runtime/__init__.py +0 -0
  40. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/runtime/runtime.py +0 -0
  41. {simulac-0.0.2 → simulac-0.0.4}/simulac/base/test/graph_test.py +0 -0
  42. {simulac-0.0.2 → simulac-0.0.4}/simulac/bddl/__init__.py +0 -0
  43. {simulac-0.0.2 → simulac-0.0.4}/simulac/bddl/example.json +0 -0
  44. {simulac-0.0.2 → simulac-0.0.4}/simulac/data_types/duckdb_types/__init__.py +0 -0
  45. {simulac-0.0.2 → simulac-0.0.4}/simulac/gym_style.py +0 -0
  46. {simulac-0.0.2 → simulac-0.0.4}/simulac/lib/world_maker/entity.py +0 -0
  47. {simulac-0.0.2 → simulac-0.0.4}/simulac/lib/world_maker/object.py +0 -0
  48. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/__init__.py +0 -0
  49. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/__init__.py +0 -0
  50. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/__init__.py +0 -0
  51. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/environment.py +0 -0
  52. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/environment_build_service.py +0 -0
  53. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/environment_service.py +0 -0
  54. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/model/component.py +0 -0
  55. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/model/entity.py +0 -0
  56. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/utils/mjcf_parser.py +0 -0
  57. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/file_service/common/file_service.py +0 -0
  58. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/file_service/common/files.py +0 -0
  59. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/file_service/local/disk_file_service_provider.py +0 -0
  60. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/file_service/remote/remote_file_service_provider.py +0 -0
  61. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/log_service/__init__.py +0 -0
  62. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/log_service/common/__init__.py +0 -0
  63. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/log_service/common/log_service.py +0 -0
  64. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/main.py +0 -0
  65. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/__init__.py +0 -0
  66. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/common/__init__.py +0 -0
  67. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/common/physics_engine_adapter.py +0 -0
  68. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/common/runner.py +0 -0
  69. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/common/runner_service.py +0 -0
  70. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/local/__init__.py +0 -0
  71. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/local/mujoco_adapter.py +0 -0
  72. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/local/newton_adapter.py +0 -0
  73. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/remote/__init__.py +0 -0
  74. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/remote/remote_adapter.py +0 -0
  75. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/remote/runner.py +0 -0
  76. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runtime.py +0 -0
  77. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/telemetry_service/__init__.py +0 -0
  78. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/telemetry_service/common/__init__.py +0 -0
  79. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/telemetry_service/common/telemetry.py +0 -0
  80. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/telemetry_service/common/telemetry_service.py +0 -0
  81. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/telemetry_service/test/telemetry_service_test.py +0 -0
  82. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/world_service/__init__.py +0 -0
  83. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/world_service/common/__init__.py +0 -0
  84. {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/world_service/common/world_service.py +0 -0
  85. {simulac-0.0.2 → simulac-0.0.4}/simulac/server/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: simulac
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: A CLI, library, and local server for interacting with the Tektonian backend.
5
5
  Keywords: robotics,robot-simulation,simulation,physics-engine,mujoco,newton,genesis,mjcf,urdf,usd
6
6
  Author: Jeuk Kang
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3.12
13
13
  Requires-Dist: pydantic<3
14
14
  Requires-Dist: duckdb>=1.4
15
15
  Requires-Dist: structlog>=25.5.0
16
- Requires-Dist: mujoco>=3.5.0
16
+ Requires-Dist: mujoco>=3.7.0
17
17
  Requires-Dist: websockets>=15.0.1
18
18
  Requires-Dist: typer>=0.24.1
19
19
  Requires-Dist: zstd>=1.5.7.3
@@ -23,11 +23,11 @@ Requires-Dist: autodoc-pydantic>=2.2.0 ; extra == 'doc'
23
23
  Requires-Dist: furo>=2025.12.19 ; extra == 'doc'
24
24
  Requires-Dist: myst-parser>=4.0.1 ; extra == 'doc'
25
25
  Requires-Dist: sphinx>=8.1.3 ; extra == 'doc'
26
- Requires-Dist: genesis-world==0.3.12 ; extra == 'genesis'
27
- Requires-Dist: torch>=2.10.0 ; extra == 'genesis'
28
- Requires-Dist: newton>=1.0.0rc0 ; extra == 'newton'
29
- Requires-Dist: warp-lang>=1.12.0 ; extra == 'newton'
30
- Requires-Dist: mujoco-warp>=3.5.0.2 ; extra == 'newton'
26
+ Requires-Dist: genesis-world==0.4.6 ; extra == 'genesis'
27
+ Requires-Dist: torch>=2.11.0 ; extra == 'genesis'
28
+ Requires-Dist: newton>=1.1.0 ; extra == 'newton'
29
+ Requires-Dist: warp-lang>=1.12.1 ; extra == 'newton'
30
+ Requires-Dist: mujoco-warp>=3.7.0.1 ; extra == 'newton'
31
31
  Requires-Python: >=3.12
32
32
  Project-URL: Homepage, https://tektonian.com
33
33
  Project-URL: Repository, https://github.com/Tektonian/Simulac
@@ -50,7 +50,7 @@ Simulac helps transition between the two worlds, and provides developer-friendly
50
50
 
51
51
  ## Installation
52
52
 
53
- Simulac requires Python 3.10 or later.
53
+ Simulac requires Python 3.12 or later.
54
54
 
55
55
  ```bash
56
56
  $ pip install simulac
@@ -12,7 +12,7 @@ Simulac helps transition between the two worlds, and provides developer-friendly
12
12
 
13
13
  ## Installation
14
14
 
15
- Simulac requires Python 3.10 or later.
15
+ Simulac requires Python 3.12 or later.
16
16
 
17
17
  ```bash
18
18
  $ pip install simulac
@@ -5,7 +5,7 @@ build-backend = "uv_build"
5
5
 
6
6
  [project]
7
7
  name = "simulac"
8
- version = "0.0.2"
8
+ version = "0.0.4"
9
9
  description = "A CLI, library, and local server for interacting with the Tektonian backend."
10
10
  authors = [{ name = "Jeuk Kang", email = "gangjeuk@tektonian.com" }]
11
11
  keywords = [
@@ -41,7 +41,7 @@ dependencies = [
41
41
  "pydantic<3",
42
42
  "duckdb>=1.4",
43
43
  "structlog>=25.5.0",
44
- "mujoco>=3.5.0",
44
+ "mujoco>=3.7.0",
45
45
  "websockets>=15.0.1",
46
46
  "typer>=0.24.1",
47
47
  "zstd>=1.5.7.3",
@@ -57,9 +57,9 @@ Homepage = "https://tektonian.com"
57
57
  Repository = "https://github.com/Tektonian/Simulac"
58
58
 
59
59
  [project.optional-dependencies]
60
- genesis = ["genesis-world==0.3.12", "torch>=2.10.0"]
60
+ genesis = ["genesis-world==0.4.6", "torch>=2.11.0"]
61
61
 
62
- newton = ["newton>=1.0.0rc", "warp-lang>=1.12.0", "mujoco-warp>=3.5.0.2"]
62
+ newton = ["newton>=1.1.0", "warp-lang>=1.12.1", "mujoco-warp>=3.7.0.1"]
63
63
 
64
64
  doc = [
65
65
  "autodoc-pydantic>=2.2.0",
@@ -97,7 +97,7 @@ docstring-code-format = true
97
97
  exclude = ["trash"]
98
98
 
99
99
  [tool.pyright]
100
- stubPath = ".typings"
100
+ stubPath = ".typing"
101
101
  typeCheckingMode = "strict"
102
102
 
103
103
  [tool.ruff.lint.isort]
Binary file
@@ -1,4 +1,6 @@
1
- from .lib.world_maker.entity import Camera, Light, Robot, Stuff
1
+ from __future__ import annotations
2
+
3
+ from .lib.world_maker.entity import Camera, Robot, Stuff
2
4
  from .lib.world_maker.object import (
3
5
  CameraObject,
4
6
  Environment,
@@ -11,7 +13,6 @@ __all__ = [
11
13
  "Robot",
12
14
  "Stuff",
13
15
  "Camera",
14
- "Light",
15
16
  "Environment",
16
17
  "RobotObject",
17
18
  "StuffObject",
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib.metadata import PackageNotFoundError
4
+ from importlib.metadata import version as pkg_version
5
+ from typing import Annotated
6
+
7
+ import typer
8
+
9
+ from simulac.sdk import obtain_runtime
10
+
11
+ from .auth import app as auth_app
12
+ from .benchmark import app as benchmark_app
13
+ from .common import TOKEN_PORTAL_URL
14
+ from .config import show_config, show_envvars
15
+
16
+ APP_HELP = "Simulac CLI"
17
+
18
+ APP_EPILOG = "\n\n".join(
19
+ [
20
+ "If you are new to Simulac, start with `simulac auth login` and paste an API "
21
+ f"key from {TOKEN_PORTAL_URL}.",
22
+ "Examples:",
23
+ "- $ simulac auth login",
24
+ "- $ simulac auth whoami",
25
+ "- $ simulac benchmark list Tektonian/Metaworld",
26
+ "- $ simulac env",
27
+ "- $ simulac config",
28
+ ]
29
+ )
30
+
31
+ app = typer.Typer(
32
+ add_completion=False,
33
+ no_args_is_help=True,
34
+ help=APP_HELP,
35
+ epilog=APP_EPILOG,
36
+ )
37
+
38
+
39
+ def _package_version() -> str:
40
+ try:
41
+ return pkg_version("simulac")
42
+ except PackageNotFoundError:
43
+ return "unknown"
44
+
45
+
46
+ def _version_option_callback(value: bool) -> None:
47
+ if not value:
48
+ return
49
+ typer.echo(f"{_package_version()}")
50
+ raise typer.Exit()
51
+
52
+
53
+ @app.callback()
54
+ def root_callback(
55
+ ctx: typer.Context,
56
+ version: Annotated[
57
+ bool,
58
+ typer.Option(
59
+ "--version",
60
+ help="Show the installed Simulac version and exit.",
61
+ is_eager=True,
62
+ callback=_version_option_callback,
63
+ ),
64
+ ] = False,
65
+ ) -> None:
66
+ runtime = obtain_runtime()
67
+ ctx.obj = {"runtime": runtime}
68
+ del version
69
+
70
+
71
+ app.add_typer(auth_app, name="auth", rich_help_panel="Main commands")
72
+ app.add_typer(benchmark_app, name="benchmark", rich_help_panel="Main commands")
73
+ app.command(
74
+ "config",
75
+ short_help="Show the effective Simulac configuration.",
76
+ rich_help_panel="Help commands",
77
+ )(show_config)
78
+ app.command(
79
+ "env",
80
+ short_help="Show raw Simulac environment variables.",
81
+ rich_help_panel="Help commands",
82
+ )(show_envvars)
83
+
84
+
85
+ def main() -> None:
86
+ app()
87
+
88
+
89
+ if __name__ == "__main__":
90
+ main()
@@ -0,0 +1,127 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Annotated
5
+
6
+ import typer
7
+
8
+ from simulac.base.error.error import SimulacBaseError
9
+
10
+ from .common import (
11
+ TOKEN_PORTAL_URL,
12
+ cast_context,
13
+ fetch_identity,
14
+ get_token_state,
15
+ mask_secret,
16
+ read_token,
17
+ )
18
+
19
+ AUTH_EPILOG = "\n\n".join(
20
+ [
21
+ "\b",
22
+ "Examples:",
23
+ " $ simulac auth whoami",
24
+ " $ simulac auth login",
25
+ " $ simulac auth logout",
26
+ ]
27
+ )
28
+
29
+ app = typer.Typer(
30
+ add_completion=False,
31
+ no_args_is_help=True,
32
+ help="Authentication commands.",
33
+ epilog=AUTH_EPILOG,
34
+ )
35
+
36
+
37
+ def _save_token(token_path: Path, token: str) -> None:
38
+ token_path.parent.mkdir(parents=True, exist_ok=True)
39
+ token_path.write_text(token.strip() + "\n", encoding="utf-8")
40
+
41
+
42
+ @app.command(
43
+ "login",
44
+ short_help="Save a Simulac API key locally.",
45
+ help=(
46
+ "Save a Simulac API key in the local credential cache.\n\n"
47
+ "If you are new to Simulac, create a key at "
48
+ f"{TOKEN_PORTAL_URL} and paste it when prompted."
49
+ ),
50
+ )
51
+ def login(
52
+ ctx: typer.Context,
53
+ apikey: Annotated[
54
+ str | None,
55
+ typer.Option(
56
+ "--apikey",
57
+ "-k",
58
+ prompt="Paste your Simulac API key from https://tektonian.com/settings/token\n\nAPI KEY",
59
+ hide_input=True,
60
+ help="Simulac API key to save in the local credential cache.",
61
+ ),
62
+ ] = None,
63
+ ) -> None:
64
+ env = cast_context(ctx).runtime.environment_variable
65
+
66
+ if apikey is None:
67
+ typer.echo("No API key provided. Nothing was saved.", err=True)
68
+ raise typer.Exit(code=1)
69
+
70
+ clean_apikey = apikey.strip()
71
+ if not clean_apikey:
72
+ typer.echo(
73
+ "The API key was empty after trimming whitespace. Nothing was saved.",
74
+ err=True,
75
+ )
76
+ raise typer.Exit(code=1)
77
+
78
+ _save_token(env.token_path, clean_apikey)
79
+
80
+ typer.echo(f"Saved: Simulac API key ({mask_secret(clean_apikey)})")
81
+ typer.echo(f"Location: {env.token_path}")
82
+
83
+
84
+ @app.command(
85
+ "logout",
86
+ short_help="Delete the locally stored API key.",
87
+ help=(
88
+ "Remove the API key stored in the local credential cache.\n\n"
89
+ "This deletes the local file created by `simulac auth login` when it exists."
90
+ ),
91
+ )
92
+ def logout(ctx: typer.Context) -> None:
93
+ env = cast_context(ctx).runtime.environment_variable
94
+ stored_token = read_token(env.token_path)
95
+
96
+ if env.token_path.exists():
97
+ env.token_path.unlink(missing_ok=True)
98
+ typer.echo(f"Removed: API key ({mask_secret(stored_token)})")
99
+ typer.echo(f"Location: {env.token_path}")
100
+ typer.echo("Deleted file: local plain-text credential cache.")
101
+ else:
102
+ typer.echo("No local credential file was removed.")
103
+ typer.echo(f"Checked: {env.token_path}")
104
+ typer.echo("Data: No Simulac API key file was present.")
105
+
106
+
107
+ @app.command("whoami", short_help="Validate the API key against the server.")
108
+ def whoami(ctx: typer.Context) -> None:
109
+ env = cast_context(ctx).runtime.environment_variable
110
+ token_state = get_token_state(env)
111
+
112
+ if token_state.status != "PRESENT" or token_state.value is None:
113
+ typer.echo(
114
+ "A valid API key is required. Run `simulac auth login` or set `SIMULAC_API_KEY`.",
115
+ err=True,
116
+ )
117
+ raise typer.Exit(code=1)
118
+
119
+ try:
120
+ identity = fetch_identity(env.base_url, token_state.value)
121
+ except SimulacBaseError as exc:
122
+ typer.echo(exc.message, err=True)
123
+ raise typer.Exit(code=1) from exc
124
+
125
+ typer.echo(f"Endpoint: {identity.endpoint}")
126
+ typer.echo(f"User: {identity.user or 'unknown'}")
127
+ typer.echo(f"Email: {identity.email or 'unknown'}")
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from simulac.base.error.error import SimulacBaseError
6
+ from simulac.lib.gym_style import get_env_list
7
+
8
+ EPILOG = "\n\n".join(
9
+ [
10
+ "\b",
11
+ "Examples:",
12
+ " $ simulac benchmark list Tektonian/Libero",
13
+ " $ simulac benchmark list Tektonian/Metaworld",
14
+ ]
15
+ )
16
+ app = typer.Typer(
17
+ add_completion=False,
18
+ no_args_is_help=True,
19
+ help="Benchmark discovery commands.",
20
+ epilog=EPILOG,
21
+ )
22
+
23
+
24
+ @app.command("list", short_help="List environments for a benchmark.")
25
+ def list_benchmark(
26
+ benchmark_id: str,
27
+ ) -> None:
28
+ try:
29
+ env_ids = get_env_list(benchmark_id)
30
+ except SimulacBaseError as exc:
31
+ typer.echo(exc.message, err=True)
32
+ raise typer.Exit(code=1) from exc
33
+ except Exception as exc:
34
+ typer.echo(f"Failed to fetch benchmark environments: {exc}", err=True)
35
+ raise typer.Exit(code=1) from exc
36
+
37
+ typer.echo(f"Benchmark: {benchmark_id}")
38
+ typer.echo(f"Environment Count: {len(env_ids)}")
39
+ for env_id in env_ids:
40
+ typer.echo(env_id)
@@ -0,0 +1,147 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING, Any, Literal
7
+
8
+ import requests
9
+ import typer
10
+
11
+ from simulac.base.envvar.envvar_service import EnvvarKeyValue
12
+ from simulac.base.error.error import SimulacBaseError
13
+
14
+ if TYPE_CHECKING:
15
+ from simulac.sdk.runtime import SimulacRuntime
16
+ from simulac.base.envvar.envvar_service import IEnvvarService
17
+
18
+ TOKEN_PORTAL_URL = "https://tektonian.com/settings/token"
19
+ LOG_LEVEL_NAMES = ("off", "trace", "debug", "info", "warning", "error")
20
+
21
+
22
+ @dataclass(slots=True)
23
+ class TokenState:
24
+ status: Literal["PRESENT", "INVALID", "MISSING"]
25
+ source: Literal["SIMULAC_API_KEY", "FILE", "NONE"]
26
+ value: str | None
27
+ preview: str | None
28
+
29
+
30
+ @dataclass(slots=True)
31
+ class IdentityResult:
32
+ endpoint: str
33
+ user: str | None
34
+ email: str | None
35
+
36
+
37
+ @dataclass(slots=True)
38
+ class CliContext:
39
+ runtime: SimulacRuntime
40
+
41
+
42
+ def cast_context(ctx: typer.Context) -> CliContext:
43
+ runtime = ctx.obj["runtime"]
44
+ return CliContext(runtime)
45
+
46
+
47
+ def mask_secret(secret: str | None) -> str | None:
48
+ if not secret:
49
+ return None
50
+ return f"{secret[: len('tt_sim_')]}..."
51
+
52
+
53
+ def read_token(token_path: Path) -> str | None:
54
+ if not token_path.exists() or not token_path.is_file():
55
+ return None
56
+
57
+ lines = token_path.read_text(encoding="utf-8").splitlines()
58
+ if not lines:
59
+ return None
60
+
61
+ token = lines[0].strip()
62
+ return token or None
63
+
64
+
65
+ def get_token_state(env: IEnvvarService) -> TokenState:
66
+ env_token = os.environ.get(EnvvarKeyValue.SIMULAC_API_KEY.value)
67
+ file_token = read_token(env.token_path)
68
+
69
+ source = "NONE"
70
+ token = None
71
+ if env_token is not None and env_token.strip():
72
+ source = "SIMULAC_API_KEY"
73
+ token = env_token.strip()
74
+ elif file_token:
75
+ source = "FILE"
76
+ token = file_token
77
+
78
+ status = "MISSING"
79
+ if token:
80
+ status = "PRESENT" if len(token) > 40 else "INVALID"
81
+ return TokenState(
82
+ status=status,
83
+ source=source,
84
+ value=token,
85
+ preview=mask_secret(token),
86
+ )
87
+
88
+
89
+ def collect_config_snapshot(env: IEnvvarService) -> dict[str, Any]:
90
+ env = env
91
+ token_state = get_token_state(env)
92
+ log_level_index = env.log_level
93
+ log_level = (
94
+ LOG_LEVEL_NAMES[log_level_index]
95
+ if 0 <= log_level_index < len(LOG_LEVEL_NAMES)
96
+ else f"unknown({log_level_index})"
97
+ )
98
+
99
+ return {
100
+ "app_root": str(env.app_root),
101
+ "asset_dir": str(env.asset_dir),
102
+ "base_url": env.base_url,
103
+ "cache_dir": str(env.simulac_cache_dir),
104
+ "log_file": str(env.log_file),
105
+ "log_level": log_level,
106
+ "tmp_dir": str(env.tmp_dir),
107
+ "token_path": str(env.token_path),
108
+ "token_source": token_state.source,
109
+ "token_status": token_state.status,
110
+ }
111
+
112
+
113
+ def fetch_identity(base_url: str, token: str) -> IdentityResult:
114
+ headers = {"tt-apikey": token}
115
+ url = f"{base_url}/auth/whoami"
116
+
117
+ try:
118
+ response = requests.get(url, headers=headers, timeout=10)
119
+ except requests.RequestException as exc:
120
+ raise SimulacBaseError(
121
+ f"Failed to reach Simulac identity endpoint: {exc}"
122
+ ) from exc
123
+
124
+ if response.status_code >= 400:
125
+ raise SimulacBaseError("Authentication failed while validating the API key.")
126
+
127
+ try:
128
+ response.raise_for_status()
129
+ except Exception as exc:
130
+ detail = response.text.strip() or str(exc)
131
+ raise SimulacBaseError(
132
+ f"Identity lookup failed: {response.status_code} {detail}"
133
+ ) from exc
134
+
135
+ try:
136
+ payload_raw = response.json()
137
+ except ValueError as exc:
138
+ raise SimulacBaseError("Identity lookup returned a non-JSON response.") from exc
139
+
140
+ payload: dict[str, str] = (
141
+ payload_raw if isinstance(payload_raw, dict) else {"value": payload_raw}
142
+ )
143
+ return IdentityResult(
144
+ endpoint=url,
145
+ user=payload.get("user", None),
146
+ email=payload.get("email", None),
147
+ )
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ import typer
6
+
7
+ from simulac.base.envvar.envvar_service import EnvvarKeyValue
8
+
9
+ from .common import cast_context, collect_config_snapshot, mask_secret
10
+
11
+
12
+ def show_config(ctx: typer.Context) -> None:
13
+ env = cast_context(ctx).runtime.environment_variable
14
+ payload = collect_config_snapshot(env)
15
+ max_key_length = max(len(key) for key in payload.keys())
16
+
17
+ for key in payload.keys():
18
+ label = key.replace("_", " ").title()
19
+ typer.echo(f"{label:<{max_key_length}}: {payload[key]}")
20
+
21
+
22
+ def show_envvars() -> None:
23
+ payload: dict[str, str | None] = {}
24
+ max_key_length = max(len(key.value) for key in EnvvarKeyValue)
25
+
26
+ for env_var in EnvvarKeyValue:
27
+ variable_name = env_var.value
28
+ raw_value = os.environ.get(variable_name)
29
+ display_value = (
30
+ mask_secret(raw_value.strip())
31
+ if variable_name == EnvvarKeyValue.SIMULAC_API_KEY.value and raw_value
32
+ else raw_value
33
+ )
34
+ payload[variable_name] = display_value
35
+
36
+ for env_var in EnvvarKeyValue:
37
+ variable_name = env_var.value
38
+ variable = payload[variable_name]
39
+ value = variable if variable is not None else "<unset>"
40
+ typer.echo(f"{variable_name:<{max_key_length}}: {value}")
@@ -11,38 +11,20 @@ from simulac.sdk.runtime import obtain_runtime
11
11
  from .gym_style_environment import BenchmarkEnvironment, BenchmarkVecEnvironment
12
12
 
13
13
 
14
- @overload
15
14
  def init_bench(
16
15
  benchmark_id: str,
17
16
  env_id: str,
18
17
  seed: int = 0,
19
18
  /,
20
19
  benchmark_specific: dict[str, Any] = {},
21
- ) -> BenchmarkEnvironment: ...
22
- @overload
23
- def init_bench(
24
- benchmark_id: str,
25
- env_id: None,
26
- seed: int = 0,
27
- /,
28
- benchmark_specific: dict[str, Any] = {},
29
- ) -> BenchmarkVecEnvironment: ...
30
- def init_bench(
31
- benchmark_id: str,
32
- env_id: Optional[str],
33
- seed: int = 0,
34
- /,
35
- benchmark_specific: dict[str, Any] = {},
36
- ):
20
+ ) -> BenchmarkEnvironment:
37
21
  """Initalize benchmark service
38
22
 
39
23
  Args:
40
24
  benchmark_id (str): Full benchmark id.\n
41
- Example: benchmark_id="Tektonian/Libero"
42
- env_id (Optional[str]): Environment id of the benchmark.\n
43
- `env_id=None` means run all benchmark list\n
44
- `env_id="libero_10"` means run all `"libero_10"` benchmark list\n
45
- `env_id="libero_10/TASK_EXAMPLE"` means run one specific test\n
25
+ Example: benchmark_id="Tektonian/Metaworld"
26
+ env_id (str): Environment id of the benchmark.\n
27
+ Example: envid="reach-v3"
46
28
  If you want to see the full list of the `env_id` visit https://tektonian.com/benchmark
47
29
  seed (int, optional): Seed for inital state. Defaults to 0.
48
30
  benchmark_specific (dict[str, Any], optional): Benchmark specific option field.\n
@@ -50,19 +32,26 @@ def init_bench(
50
32
  Defaults to {}.
51
33
 
52
34
  Returns:
53
- ret (BenchmarkEnvironment|BenchmarkVecEnvironment):
35
+ ret (BenchmarkEnvironment):
54
36
  """
55
37
  runtime = obtain_runtime()
56
38
  split_benchmark_id = benchmark_id.split("/")
57
39
  normalized_benchmark_id = benchmark_id.strip()
58
40
 
59
- if len(split_benchmark_id) != 2:
41
+ MESSAGE = "\n".join(
42
+ [
43
+ f"Invalid benchmark_id {benchmark_id!r}. ",
44
+ "Expected '<organization>/<benchmark>', (e.g., 'Tektonian/Metaworld')",
45
+ ]
46
+ )
47
+ if len(split_benchmark_id) == 1:
48
+ raise SimulacBaseError(MESSAGE)
49
+ elif len(split_benchmark_id) != 2:
60
50
  runtime.logger.warn(
61
51
  "\n".join(
62
52
  [
63
- f"Invalid benchmark_id {benchmark_id!r}. ",
64
- "Expected '<organization>/<benchmark>', ",
65
- "for example 'Tektonian/Libero'.",
53
+ MESSAGE,
54
+ f"Unused fields: '{split_benchmark_id[2:]}' will be removed",
66
55
  ]
67
56
  )
68
57
  )
@@ -76,16 +65,16 @@ def init_bench(
76
65
  )
77
66
  )
78
67
 
79
- if env_id is None:
80
- vec_env = BenchmarkVecEnvironment([])
81
- return vec_env
68
+ # Rename
69
+ (owner_id, world_id) = split_benchmark_id
82
70
 
83
71
  env = BenchmarkEnvironment(
84
- "id",
85
- benchmark_id,
72
+ owner_id,
73
+ world_id,
86
74
  env_id,
87
75
  seed,
88
76
  benchmark_specific,
77
+ error_recovery_enabled=False,
89
78
  )
90
79
 
91
80
  return env
@@ -129,6 +118,8 @@ def get_env_list(benchmark_id: str) -> list[str]:
129
118
 
130
119
 
131
120
  def make_vec(envs: list[BenchmarkEnvironment]):
121
+ for env in envs:
122
+ env._set_error_recovery_enabled(True)
132
123
  vec_env = BenchmarkVecEnvironment(envs)
133
124
  return vec_env
134
125