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.
- {simulac-0.0.2 → simulac-0.0.4}/PKG-INFO +8 -8
- {simulac-0.0.2 → simulac-0.0.4}/README.md +1 -1
- {simulac-0.0.2 → simulac-0.0.4}/pyproject.toml +5 -5
- simulac-0.0.4/simulac/.DS_Store +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/__init__.py +3 -2
- simulac-0.0.4/simulac/cli/__init__.py +90 -0
- simulac-0.0.4/simulac/cli/auth.py +127 -0
- simulac-0.0.4/simulac/cli/benchmark.py +40 -0
- simulac-0.0.4/simulac/cli/common.py +147 -0
- simulac-0.0.4/simulac/cli/config.py +40 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/lib/gym_style/__init__.py +23 -32
- simulac-0.0.4/simulac/lib/gym_style/gym_style_environment.py +512 -0
- simulac-0.0.4/simulac/lib/test/benchmark_test.py +333 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/lib/test/entity_test.py +4 -4
- simulac-0.0.4/simulac/lib/test/gym_style_environment_integration_test.py +139 -0
- simulac-0.0.4/simulac/lib/test/gym_style_environment_test.py +348 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/lib/world_maker/__init__.py +2 -1
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/world_maker.py +7 -4
- simulac-0.0.2/simulac/cli/__init__.py +0 -9
- simulac-0.0.2/simulac/cli/auth.py +0 -160
- simulac-0.0.2/simulac/lib/gym_style/gym_style_environment.py +0 -349
- simulac-0.0.2/simulac/lib/test/benchmark_test.py +0 -1174
- simulac-0.0.2/simulac/lib/test/benchmark_test2.py +0 -917
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/envvar/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/envvar/envvar.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/envvar/envvar_service.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/error/error.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/descriptor.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/extensions.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/graph.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/instantiate.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/instantiate_service.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/instantiate/service_collection.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/network/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/network/network.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/result/result.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/runtime/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/runtime/runtime.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/base/test/graph_test.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/bddl/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/bddl/example.json +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/data_types/duckdb_types/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/gym_style.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/lib/world_maker/entity.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/lib/world_maker/object.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/environment.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/environment_build_service.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/environment_service.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/model/component.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/model/entity.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/environment_service/common/utils/mjcf_parser.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/file_service/common/file_service.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/file_service/common/files.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/file_service/local/disk_file_service_provider.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/file_service/remote/remote_file_service_provider.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/log_service/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/log_service/common/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/log_service/common/log_service.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/main.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/common/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/common/physics_engine_adapter.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/common/runner.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/common/runner_service.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/local/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/local/mujoco_adapter.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/local/newton_adapter.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/remote/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/remote/remote_adapter.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runner_service/remote/runner.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/runtime.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/telemetry_service/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/telemetry_service/common/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/telemetry_service/common/telemetry.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/telemetry_service/common/telemetry_service.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/telemetry_service/test/telemetry_service_test.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/world_service/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/world_service/common/__init__.py +0 -0
- {simulac-0.0.2 → simulac-0.0.4}/simulac/sdk/world_service/common/world_service.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
27
|
-
Requires-Dist: torch>=2.
|
|
28
|
-
Requires-Dist: newton>=1.0
|
|
29
|
-
Requires-Dist: warp-lang>=1.12.
|
|
30
|
-
Requires-Dist: mujoco-warp>=3.
|
|
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.
|
|
53
|
+
Simulac requires Python 3.12 or later.
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
56
|
$ pip install simulac
|
|
@@ -5,7 +5,7 @@ build-backend = "uv_build"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "simulac"
|
|
8
|
-
version = "0.0.
|
|
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.
|
|
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.
|
|
60
|
+
genesis = ["genesis-world==0.4.6", "torch>=2.11.0"]
|
|
61
61
|
|
|
62
|
-
newton = ["newton>=1.0
|
|
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 = ".
|
|
100
|
+
stubPath = ".typing"
|
|
101
101
|
typeCheckingMode = "strict"
|
|
102
102
|
|
|
103
103
|
[tool.ruff.lint.isort]
|
|
Binary file
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from
|
|
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/
|
|
42
|
-
env_id (
|
|
43
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
"
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
return vec_env
|
|
68
|
+
# Rename
|
|
69
|
+
(owner_id, world_id) = split_benchmark_id
|
|
82
70
|
|
|
83
71
|
env = BenchmarkEnvironment(
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|