get-embodied-tasks 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,4 @@
1
+ # Copyright 2025. Apache-2.0.
2
+ """get-embodied-tasks: task spec -> dataset + runnable commands across backends."""
3
+
4
+ __version__ = "0.1.0"
@@ -0,0 +1 @@
1
+ # Copyright 2025. Apache-2.0.
@@ -0,0 +1,35 @@
1
+ # Copyright 2025. Apache-2.0.
2
+ """Backend protocol: a source of embodied-AI tasks + datasets + runnable commands."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import abc
7
+
8
+ from get_embodied_tasks.discovery import Resolution
9
+
10
+
11
+ class Backend(abc.ABC):
12
+ """One task source (e.g. a LeRobot checkout). Adapters subclass this."""
13
+
14
+ name: str
15
+
16
+ @abc.abstractmethod
17
+ def locate(self) -> Resolution:
18
+ """Where is this backend? (does not import it)."""
19
+
20
+ def available(self) -> bool:
21
+ return bool(self.locate())
22
+
23
+ @abc.abstractmethod
24
+ def bundles(
25
+ self,
26
+ query: str | None = None,
27
+ benchmark: str | None = None,
28
+ limit: int = 20,
29
+ policy: str | None = None,
30
+ ) -> tuple[list[dict], list[dict], list[dict]]:
31
+ """Return (matched bundles, fuzzy-suggestion bundles, warnings). Bundles are plain dicts."""
32
+
33
+ @abc.abstractmethod
34
+ def dump(self, policy: str | None = None) -> tuple[list[dict], list[dict]]:
35
+ """Return (all bundles, warnings)."""
@@ -0,0 +1,99 @@
1
+ # Copyright 2025. Apache-2.0.
2
+ """LeRobot backend adapter.
3
+
4
+ Locates a (vanilla) LeRobot checkout or installed package, reads its task catalog by
5
+ AST (`catalog.build`), and attaches the LeRobot dataset map + train/eval commands
6
+ (`data.lerobot_datasets`). Never imports LeRobot.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import importlib.util
12
+ from pathlib import Path
13
+
14
+ from get_embodied_tasks import catalog
15
+ from get_embodied_tasks.backends.base import Backend
16
+ from get_embodied_tasks.data import lerobot_datasets as ds
17
+ from get_embodied_tasks.discovery import Resolution, resolve_root
18
+ from get_embodied_tasks.matching import find_tasks
19
+
20
+ ENV_VAR = "LEROBOT_HOME"
21
+ SUBPATH = "lerobot"
22
+ IMPORT_NAME = "lerobot"
23
+
24
+
25
+ class LeRobotBackend(Backend):
26
+ name = "lerobot"
27
+
28
+ def __init__(self, cwd: Path | None = None):
29
+ self._cwd = cwd
30
+
31
+ def locate(self) -> Resolution:
32
+ return resolve_root(SUBPATH, env_var=ENV_VAR, import_name=IMPORT_NAME, cwd=self._cwd)
33
+
34
+ def envs_dir(self) -> Path | None:
35
+ """Resolve the `lerobot/envs` directory for a checkout or installed package."""
36
+ res = self.locate()
37
+ if not res:
38
+ return None
39
+ if res.root is not None: # checkout
40
+ return res.root / "src" / "lerobot" / "envs"
41
+ spec = importlib.util.find_spec(IMPORT_NAME) # installed
42
+ if spec and spec.origin:
43
+ return Path(spec.origin).parent / "envs"
44
+ return None
45
+
46
+ def _build(self) -> tuple[list[catalog.TaskEntry], list[dict]]:
47
+ envs = self.envs_dir()
48
+ if envs is None:
49
+ res = self.locate()
50
+ warn = {
51
+ "benchmark": "lerobot",
52
+ "what": "LeRobot backend not found",
53
+ "where": res.detail,
54
+ "hint": (
55
+ f"set ${ENV_VAR}=<lerobot checkout>, vendor it under "
56
+ f"third_party/ei_ws/{SUBPATH}, or pip install lerobot"
57
+ ),
58
+ }
59
+ return [], [warn]
60
+ entries, warnings = catalog.build(envs)
61
+ return entries, [w.as_dict() for w in warnings]
62
+
63
+ def _bundle(self, e: catalog.TaskEntry, policy: str | None) -> dict:
64
+ p = policy or ds.default_policy(e.env_type)
65
+ d = ds.dataset_for(e.env_type)
66
+ return {
67
+ "backend": self.name,
68
+ "benchmark": e.benchmark,
69
+ "env_type": e.env_type,
70
+ "task": e.task_name,
71
+ "description": e.description,
72
+ "category": e.category,
73
+ "enumerable": e.enumerable,
74
+ "dataset": ({"repo_id": d.repo_id, "url": d.url, "note": d.note} if d else None),
75
+ "policy": p,
76
+ "train_command": ds.train_command(e.env_type, e.task_name, p),
77
+ "eval_command": ds.eval_command(e.env_type, e.task_name),
78
+ "upstream_url": e.upstream_url,
79
+ }
80
+
81
+ def bundles(self, query=None, benchmark=None, limit=20, policy=None):
82
+ entries, warnings = self._build()
83
+ if not query:
84
+ corpus = entries
85
+ if benchmark:
86
+ b = benchmark.lower()
87
+ corpus = [e for e in corpus if b in (e.benchmark.lower(), e.env_type.lower())]
88
+ matches, suggestions = corpus[:limit], []
89
+ else:
90
+ matches, suggestions = find_tasks(entries, query, benchmark=benchmark, limit=limit)
91
+ return (
92
+ [self._bundle(e, policy) for e in matches],
93
+ [self._bundle(e, policy) for e in suggestions],
94
+ warnings,
95
+ )
96
+
97
+ def dump(self, policy=None):
98
+ entries, warnings = self._build()
99
+ return [self._bundle(e, policy) for e in entries], warnings
@@ -0,0 +1,8 @@
1
+ # Backend registry for get-embodied-tasks. Each table is one backend.
2
+ # Discovery for a backend tries, in order:
3
+ # $env_var -> ./third_party/ei_ws/<subpath> -> ~/ei_ws/<subpath> -> installed package
4
+ [lerobot]
5
+ adapter = "lerobot" # maps to backends/lerobot.py:LeRobotBackend
6
+ subpath = "lerobot" # dir name under the ei_ws roots
7
+ env_var = "LEROBOT_HOME" # explicit override
8
+ enabled = true
@@ -0,0 +1,335 @@
1
+ # Copyright 2025. Apache-2.0.
2
+ """Import-light task catalog read from a *vanilla* LeRobot checkout.
3
+
4
+ Reads LeRobot's `envs/` source by AST (task-list constants) and JSON (MetaWorld
5
+ config) — it never imports LeRobot, so no torch/gymnasium and no dependency on any
6
+ patched `task_index.py`. RoboTwin/RoboMME task descriptions ship with this package
7
+ (`data/*.json`); they are not in upstream LeRobot.
8
+
9
+ `build(envs_dir)` returns `(entries, warnings)`. Anything it cannot read (a renamed
10
+ constant, a moved module, a missing JSON) becomes a structured warning instead of a
11
+ silent drop, so drift against a future LeRobot is surfaced, not hidden.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import ast
17
+ import json
18
+ import re
19
+ from dataclasses import dataclass
20
+ from pathlib import Path
21
+
22
+ _DATA_DIR = Path(__file__).parent / "data"
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class TaskEntry:
27
+ benchmark: str
28
+ env_type: str
29
+ task_name: str
30
+ description: str | None = None
31
+ category: str | None = None
32
+ enumerable: bool = True
33
+ upstream_url: str | None = None
34
+ extra: str | None = None
35
+
36
+ @property
37
+ def search_text(self) -> str:
38
+ return " ".join([self.task_name, self.description or "", self.category or ""]).lower()
39
+
40
+ @property
41
+ def tokens(self) -> set[str]:
42
+ return set(re.split(r"[-_\s]+", self.search_text)) - {""}
43
+
44
+
45
+ @dataclass
46
+ class CatalogWarning:
47
+ benchmark: str
48
+ what: str
49
+ where: str
50
+ hint: str
51
+
52
+ def as_dict(self) -> dict:
53
+ return {"benchmark": self.benchmark, "what": self.what, "where": self.where, "hint": self.hint}
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class _BenchMeta:
58
+ name: str
59
+ env_type: str
60
+ upstream_url: str | None = None
61
+ extra: str | None = None
62
+
63
+
64
+ _BENCHMARKS: dict[str, _BenchMeta] = {
65
+ "metaworld": _BenchMeta(
66
+ "Meta-World", "metaworld", "https://github.com/Farama-Foundation/Metaworld", "metaworld"
67
+ ),
68
+ "robotwin": _BenchMeta(
69
+ "RoboTwin", "robotwin", "https://github.com/RoboTwin-Platform/RoboTwin", "robotwin"
70
+ ),
71
+ "robomme": _BenchMeta("RoboMME", "robomme", "https://github.com/RoboMME/robomme_benchmark", "robomme"),
72
+ "vlabench": _BenchMeta("VLABench", "vlabench", "https://github.com/OpenMOSS/VLABench", "vlabench"),
73
+ "libero": _BenchMeta("LIBERO", "libero", "https://github.com/Lifelong-Robot-Learning/LIBERO", "libero"),
74
+ "libero_plus": _BenchMeta("LIBERO-plus", "libero_plus", "https://github.com/sylvestf/LIBERO-plus", None),
75
+ "robocasa": _BenchMeta("RoboCasa", "robocasa", "https://github.com/robocasa/robocasa", "robocasa"),
76
+ "pusht": _BenchMeta("PushT", "pusht", "https://github.com/huggingface/gym-pusht", "pusht"),
77
+ "aloha": _BenchMeta("Aloha", "aloha", "https://github.com/huggingface/gym-aloha", "aloha"),
78
+ }
79
+
80
+
81
+ # --- AST / JSON readers ----------------------------------------------------
82
+
83
+
84
+ def _read_literal(envs_dir: Path, filename: str, name: str):
85
+ """`ast.literal_eval` the RHS of a top-level `name = <literal>` in envs/<filename>."""
86
+ source = (envs_dir / filename).read_text()
87
+ tree = ast.parse(source, filename=filename)
88
+ for node in tree.body:
89
+ targets = (
90
+ node.targets
91
+ if isinstance(node, ast.Assign)
92
+ else ([node.target] if isinstance(node, ast.AnnAssign) else [])
93
+ )
94
+ for target in targets:
95
+ if isinstance(target, ast.Name) and target.id == name and node.value is not None:
96
+ return ast.literal_eval(node.value)
97
+ raise KeyError(f"{name} not found in {filename}")
98
+
99
+
100
+ def _read_docstring(envs_dir: Path, filename: str) -> str:
101
+ return ast.get_docstring(ast.parse((envs_dir / filename).read_text(), filename=filename)) or ""
102
+
103
+
104
+ def _scan_env_types(envs_dir: Path) -> dict[str, str]:
105
+ """Map env.type -> default task by AST-scanning the register_subclass decorators."""
106
+ tree = ast.parse((envs_dir / "configs.py").read_text(), filename="configs.py")
107
+ out: dict[str, str] = {}
108
+ for node in tree.body:
109
+ if not isinstance(node, ast.ClassDef):
110
+ continue
111
+ env_type = None
112
+ for deco in node.decorator_list:
113
+ if (
114
+ isinstance(deco, ast.Call)
115
+ and isinstance(deco.func, ast.Attribute)
116
+ and deco.func.attr == "register_subclass"
117
+ ):
118
+ if deco.args and isinstance(deco.args[0], ast.Constant):
119
+ env_type = deco.args[0].value
120
+ for kw in deco.keywords:
121
+ if kw.arg == "name" and isinstance(kw.value, ast.Constant):
122
+ env_type = kw.value.value
123
+ if env_type is None:
124
+ continue
125
+ default = ""
126
+ for stmt in node.body:
127
+ if (
128
+ isinstance(stmt, ast.AnnAssign)
129
+ and isinstance(stmt.target, ast.Name)
130
+ and stmt.target.id == "task"
131
+ and isinstance(stmt.value, ast.Constant)
132
+ ):
133
+ default = stmt.value.value
134
+ out[env_type] = default
135
+ return out
136
+
137
+
138
+ def _load_descriptions(data_dir: Path, filename: str) -> dict[str, str]:
139
+ return json.loads((data_dir / filename).read_text()).get("descriptions", {})
140
+
141
+
142
+ # --- per-benchmark builders (each appends to `warnings` on failure) ---------
143
+
144
+
145
+ def _try(warnings: list[CatalogWarning], benchmark: str, where: str, hint: str, fn):
146
+ try:
147
+ return fn()
148
+ except Exception as exc: # noqa: BLE001 — any read failure becomes a warning, not a crash
149
+ warnings.append(CatalogWarning(benchmark, f"{type(exc).__name__}: {exc}", where, hint))
150
+ return None
151
+
152
+
153
+ def build(
154
+ envs_dir: str | Path, data_dir: str | Path | None = None
155
+ ) -> tuple[list[TaskEntry], list[CatalogWarning]]:
156
+ """Build the task corpus from a LeRobot `envs/` dir. Returns (entries, warnings)."""
157
+ envs = Path(envs_dir)
158
+ data = Path(data_dir) if data_dir else _DATA_DIR
159
+ entries: list[TaskEntry] = []
160
+ warnings: list[CatalogWarning] = []
161
+ hint_ver = "likely a LeRobot version change — check LEROBOT_HOME or update the lerobot adapter"
162
+
163
+ # MetaWorld (descriptions + difficulty from the checkout's JSON).
164
+ mw = _try(
165
+ warnings,
166
+ "Meta-World",
167
+ "envs/metaworld_config.json",
168
+ hint_ver,
169
+ lambda: json.loads((envs / "metaworld_config.json").read_text()),
170
+ )
171
+ if mw:
172
+ difficulty = {t: d for d, ts in mw.get("DIFFICULTY_TO_TASKS", {}).items() for t in ts}
173
+ m = _BENCHMARKS["metaworld"]
174
+ for name, desc in sorted(mw.get("TASK_DESCRIPTIONS", {}).items()):
175
+ entries.append(
176
+ TaskEntry(
177
+ m.name,
178
+ m.env_type,
179
+ name,
180
+ desc,
181
+ difficulty.get(name),
182
+ upstream_url=m.upstream_url,
183
+ extra=m.extra,
184
+ )
185
+ )
186
+
187
+ # RoboTwin (names from checkout, descriptions from this package).
188
+ rt_names = _try(
189
+ warnings,
190
+ "RoboTwin",
191
+ "envs/robotwin.py:ROBOTWIN_TASKS",
192
+ hint_ver,
193
+ lambda: _read_literal(envs, "robotwin.py", "ROBOTWIN_TASKS"),
194
+ )
195
+ rt_desc = (
196
+ _try(
197
+ warnings,
198
+ "RoboTwin",
199
+ "data/robotwin_task_descriptions.json",
200
+ "shipped with this package",
201
+ lambda: _load_descriptions(data, "robotwin_task_descriptions.json"),
202
+ )
203
+ or {}
204
+ )
205
+ if rt_names:
206
+ m = _BENCHMARKS["robotwin"]
207
+ for n in rt_names:
208
+ entries.append(
209
+ TaskEntry(
210
+ m.name, m.env_type, n, rt_desc.get(n) or None, upstream_url=m.upstream_url, extra=m.extra
211
+ )
212
+ )
213
+
214
+ # VLABench (names only, by suite).
215
+ m = _BENCHMARKS["vlabench"]
216
+ for const, cat in (("PRIMITIVE_TASKS", "primitive"), ("COMPOSITE_TASKS", "composite")):
217
+ names = _try(
218
+ warnings,
219
+ "VLABench",
220
+ f"envs/vlabench.py:{const}",
221
+ hint_ver,
222
+ lambda const=const: _read_literal(envs, "vlabench.py", const),
223
+ )
224
+ for n in names or []:
225
+ entries.append(
226
+ TaskEntry(m.name, m.env_type, n, category=cat, upstream_url=m.upstream_url, extra=m.extra)
227
+ )
228
+
229
+ # RoboMME (names from checkout, suites from its docstring, descriptions from this package).
230
+ rm_names = _try(
231
+ warnings,
232
+ "RoboMME",
233
+ "envs/robomme.py:ROBOMME_TASKS",
234
+ hint_ver,
235
+ lambda: _read_literal(envs, "robomme.py", "ROBOMME_TASKS"),
236
+ )
237
+ rm_desc = (
238
+ _try(
239
+ warnings,
240
+ "RoboMME",
241
+ "data/robomme_task_descriptions.json",
242
+ "shipped with this package",
243
+ lambda: _load_descriptions(data, "robomme_task_descriptions.json"),
244
+ )
245
+ or {}
246
+ )
247
+ suite_of: dict[str, str] = {}
248
+ doc = (
249
+ _try(
250
+ warnings,
251
+ "RoboMME",
252
+ "envs/robomme.py docstring",
253
+ hint_ver,
254
+ lambda: _read_docstring(envs, "robomme.py"),
255
+ )
256
+ or ""
257
+ )
258
+ for line in doc.splitlines():
259
+ mm = re.match(r"\s*(Counting|Permanence|Reference|Imitation):\s*(.+)", line)
260
+ if mm:
261
+ for t in mm.group(2).split(","):
262
+ suite_of[t.strip()] = mm.group(1)
263
+ if rm_names:
264
+ m = _BENCHMARKS["robomme"]
265
+ for n in rm_names:
266
+ entries.append(
267
+ TaskEntry(
268
+ m.name,
269
+ m.env_type,
270
+ n,
271
+ rm_desc.get(n) or None,
272
+ suite_of.get(n),
273
+ upstream_url=m.upstream_url,
274
+ extra=m.extra,
275
+ )
276
+ )
277
+
278
+ # Upstream-only suites/groups (LIBERO, LIBERO-plus, RoboCasa) + single-task envs.
279
+ libero_suites = _try(
280
+ warnings,
281
+ "LIBERO",
282
+ "envs/libero.py:TASK_SUITE_MAX_STEPS",
283
+ hint_ver,
284
+ lambda: list(_read_literal(envs, "libero.py", "TASK_SUITE_MAX_STEPS")),
285
+ )
286
+ for env_type in ("libero", "libero_plus"):
287
+ m = _BENCHMARKS[env_type]
288
+ for suite in libero_suites or []:
289
+ entries.append(
290
+ TaskEntry(
291
+ m.name,
292
+ m.env_type,
293
+ suite,
294
+ category="suite",
295
+ enumerable=False,
296
+ upstream_url=m.upstream_url,
297
+ extra=m.extra,
298
+ )
299
+ )
300
+ rc_groups = _try(
301
+ warnings,
302
+ "RoboCasa",
303
+ "envs/robocasa.py:_TASK_GROUP_SPLITS",
304
+ hint_ver,
305
+ lambda: list(_read_literal(envs, "robocasa.py", "_TASK_GROUP_SPLITS")),
306
+ )
307
+ m = _BENCHMARKS["robocasa"]
308
+ for g in rc_groups or []:
309
+ entries.append(
310
+ TaskEntry(
311
+ m.name,
312
+ m.env_type,
313
+ g,
314
+ category="group",
315
+ enumerable=False,
316
+ upstream_url=m.upstream_url,
317
+ extra=m.extra,
318
+ )
319
+ )
320
+
321
+ defaults = _try(warnings, "PushT/Aloha", "envs/configs.py", hint_ver, lambda: _scan_env_types(envs)) or {}
322
+ for env_type in ("pusht", "aloha"):
323
+ m = _BENCHMARKS[env_type]
324
+ entries.append(
325
+ TaskEntry(
326
+ m.name,
327
+ m.env_type,
328
+ defaults.get(env_type) or env_type,
329
+ enumerable=False,
330
+ upstream_url=m.upstream_url,
331
+ extra=m.extra,
332
+ )
333
+ )
334
+
335
+ return entries, warnings
@@ -0,0 +1,195 @@
1
+ # Copyright 2025. Apache-2.0.
2
+ """`get-embodied-tasks` dispatcher.
3
+
4
+ Resolves an embodied-AI task query across enabled backends (LeRobot, ...) and prints,
5
+ per match, the dataset + ready-to-run train/eval commands. Facts come from each
6
+ backend's adapter; this layer only routes and merges. Stdlib-only.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import sys
14
+ import tomllib
15
+ from pathlib import Path
16
+
17
+ from get_embodied_tasks.backends.base import Backend
18
+ from get_embodied_tasks.backends.lerobot import LeRobotBackend
19
+
20
+ _ADAPTERS: dict[str, type[Backend]] = {"lerobot": LeRobotBackend}
21
+ _CONFIG = Path(__file__).parent / "backends.toml"
22
+
23
+
24
+ def _load_backends(only: str | None = None) -> list[Backend]:
25
+ cfg = tomllib.loads(_CONFIG.read_text())
26
+ backends: list[Backend] = []
27
+ for name, spec in cfg.items():
28
+ if not spec.get("enabled", True):
29
+ continue
30
+ if only and name != only:
31
+ continue
32
+ adapter = _ADAPTERS.get(spec.get("adapter", name))
33
+ if adapter:
34
+ backends.append(adapter())
35
+ return backends
36
+
37
+
38
+ # --- rendering -------------------------------------------------------------
39
+
40
+
41
+ def _render_bundle(b: dict) -> str:
42
+ head = f"### [{b['backend']}] {b['benchmark']} — `{b['task']}`"
43
+ if b.get("category"):
44
+ head += f" ({b['category']})"
45
+ lines = [head]
46
+ if b.get("description"):
47
+ lines.append(b["description"])
48
+ d = b.get("dataset")
49
+ if d:
50
+ note = f" ({d['note']})" if d.get("note") else ""
51
+ lines.append(f"- **Dataset:** [{d['repo_id']}]({d['url']}){note}")
52
+ else:
53
+ lines.append("- **Dataset:** none published — data is generated locally (see the benchmark docs)")
54
+ if not b.get("enumerable", True):
55
+ lines.append("- _suite/group; concrete tasks come from the upstream package_")
56
+ lines.append(f"- **Train:** `{b['train_command']}`")
57
+ lines.append(f"- **Eval:** `{b['eval_command']}`")
58
+ lines.append(f"- _policy `{b['policy']}` is a default; swap via `--policy.type=`_")
59
+ return "\n".join(lines)
60
+
61
+
62
+ def _render(query: str, matches: list[dict], suggestions: list[dict]) -> str:
63
+ out: list[str] = []
64
+ if matches:
65
+ out.append(f"## Tasks matching {query!r}\n")
66
+ out.append("\n\n".join(_render_bundle(b) for b in matches))
67
+ else:
68
+ out.append(f"## No exact match for {query!r}\n")
69
+ if suggestions:
70
+ out.append("Closest existing tasks to adapt from:\n")
71
+ out.append("\n\n".join(_render_bundle(b) for b in suggestions))
72
+ else:
73
+ out.append(
74
+ "No task matched across the available backends. Next: research whether another "
75
+ "open-source project/dataset covers it; if none, draft an implementation plan."
76
+ )
77
+ return "\n".join(out).rstrip() + "\n"
78
+
79
+
80
+ def _emit_warnings(warnings: list[dict]) -> None:
81
+ if not warnings:
82
+ return
83
+ print("⚠ Diagnostics — some catalog facts could not be read:", file=sys.stderr)
84
+ for w in warnings:
85
+ print(f" - [{w['benchmark']}] {w['what']} @ {w['where']} — {w['hint']}", file=sys.stderr)
86
+
87
+
88
+ # --- subcommands -----------------------------------------------------------
89
+
90
+
91
+ def _doctor(only: str | None) -> int:
92
+ backends = _load_backends(only)
93
+ if not backends:
94
+ print("No backends enabled in backends.toml.")
95
+ return 1
96
+ rc = 0
97
+ for be in backends:
98
+ res = be.locate()
99
+ print(f"## backend: {be.name}")
100
+ print(f"- located: {'yes' if res else 'NO'} ({res.source}) — {res.detail}")
101
+ if not res:
102
+ print(" → set the env var, vendor under third_party/ei_ws/, or pip install it.")
103
+ rc = 1
104
+ continue
105
+ all_bundles, warnings = be.dump()
106
+ from collections import Counter
107
+
108
+ by_b = Counter(b["benchmark"] for b in all_bundles)
109
+ with_ds = sum(1 for b in all_bundles if b["dataset"])
110
+ print(f"- tasks: {len(all_bundles)} across {len(by_b)} benchmarks; {with_ds} with a dataset")
111
+ print(f"- benchmarks: {dict(by_b)}")
112
+ if warnings:
113
+ rc = 1
114
+ print(f"- ⚠ {len(warnings)} warning(s):")
115
+ for w in warnings:
116
+ print(f" [{w['benchmark']}] {w['what']} @ {w['where']} — {w['hint']}")
117
+ else:
118
+ print("- ✓ no warnings")
119
+ return rc
120
+
121
+
122
+ def _query(args: argparse.Namespace) -> int:
123
+ backends = _load_backends(args.backend)
124
+ matches: list[dict] = []
125
+ suggestions: list[dict] = []
126
+ warnings: list[dict] = []
127
+ for be in backends:
128
+ if not be.available():
129
+ warnings.append(
130
+ {
131
+ "benchmark": be.name,
132
+ "what": "backend unavailable",
133
+ "where": be.locate().detail,
134
+ "hint": f"set its env var / vendor it / pip install {be.name}",
135
+ }
136
+ )
137
+ continue
138
+ if args.dump:
139
+ b, w = be.dump(args.policy)
140
+ matches += b
141
+ warnings += w
142
+ else:
143
+ m, s, w = be.bundles(args.query, benchmark=args.benchmark, limit=args.limit, policy=args.policy)
144
+ matches += m
145
+ suggestions += s
146
+ warnings += w
147
+
148
+ label = args.query or (f"benchmark {args.benchmark}" if args.benchmark else "all tasks")
149
+ if args.json or args.dump:
150
+ payload = {"query": label, "matches": matches, "suggestions": suggestions, "warnings": warnings}
151
+ if args.dump:
152
+ payload = {"bundles": matches, "warnings": warnings}
153
+ print(json.dumps(payload, indent=2))
154
+ else:
155
+ print(_render(label, matches, suggestions))
156
+ _emit_warnings(warnings)
157
+
158
+ if args.strict and warnings:
159
+ return 2
160
+ return 0 if matches else 1
161
+
162
+
163
+ def main(argv: list[str] | None = None) -> int:
164
+ argv = list(sys.argv[1:] if argv is None else argv)
165
+ if argv and argv[0] == "doctor":
166
+ sub = argparse.ArgumentParser(prog="get-embodied-tasks doctor")
167
+ sub.add_argument("--backend", default=None)
168
+ a = sub.parse_args(argv[1:])
169
+ return _doctor(a.backend)
170
+
171
+ parser = argparse.ArgumentParser(
172
+ prog="get-embodied-tasks",
173
+ description="Resolve a task query to its dataset and runnable train/eval commands across backends.",
174
+ )
175
+ parser.add_argument("query", nargs="?", help="Task to search for, e.g. 'stack blocks'.")
176
+ parser.add_argument("-b", "--benchmark", default=None, help="Restrict to a benchmark (name or env.type).")
177
+ parser.add_argument("--backend", default=None, help="Restrict to a single backend (e.g. lerobot).")
178
+ parser.add_argument("-n", "--limit", type=int, default=20, help="Max results (default: 20).")
179
+ parser.add_argument("--policy", default=None, help="Override the policy in generated commands.")
180
+ parser.add_argument("--json", action="store_true", help="Emit JSON instead of markdown.")
181
+ parser.add_argument(
182
+ "--dump", action="store_true", help="Emit every task (with datasets/commands) as JSON."
183
+ )
184
+ parser.add_argument(
185
+ "--strict", action="store_true", help="Exit non-zero if any diagnostics warning is emitted."
186
+ )
187
+ args = parser.parse_args(argv)
188
+
189
+ if not args.query and not args.benchmark and not args.dump:
190
+ parser.error("provide a query, --benchmark, --dump, or the `doctor` subcommand")
191
+ return _query(args)
192
+
193
+
194
+ if __name__ == "__main__":
195
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ # Copyright 2025. Apache-2.0.
@@ -0,0 +1,69 @@
1
+ # Copyright 2025. Apache-2.0.
2
+ """LeRobot-backend facts: dataset map + command templates + default policy.
3
+
4
+ These are the get-task value-add for the LeRobot backend — which Hugging Face
5
+ dataset backs each benchmark and the exact train/eval commands to run it. Kept as
6
+ data (not recalled by a model) so the commands are always correct. Repo ids were
7
+ verified to resolve at authoring time; if LeRobot publishes/moves a dataset, update
8
+ the map here.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class DatasetInfo:
18
+ repo_id: str
19
+ note: str | None = None
20
+
21
+ @property
22
+ def url(self) -> str | None:
23
+ return f"https://huggingface.co/datasets/{self.repo_id}" if self.repo_id else None
24
+
25
+
26
+ # env_type -> canonical HF dataset. None == no downloadable dataset (generated locally).
27
+ DATASETS: dict[str, DatasetInfo | None] = {
28
+ "pusht": DatasetInfo("lerobot/pusht"),
29
+ "aloha": DatasetInfo("lerobot/aloha_sim_insertion_human", "several aloha sim datasets exist"),
30
+ "libero": DatasetInfo("HuggingFaceVLA/libero"),
31
+ "libero_plus": DatasetInfo("Sylvest/libero_plus_lerobot"),
32
+ "metaworld": DatasetInfo("lerobot/metaworld_mt50"),
33
+ "robocasa": DatasetInfo("pepijn223/robocasa_CloseFridge", "per-task; see the RoboCasa docs"),
34
+ "robotwin": DatasetInfo("lerobot/robotwin_unified"),
35
+ "robomme": DatasetInfo("lerobot/robomme"),
36
+ "vlabench": None, # generates data locally; no published HF dataset
37
+ }
38
+
39
+ # Benchmarks better served by a small VLA than ACT (multi-task / language-conditioned).
40
+ _VLA_BENCHMARKS = frozenset(
41
+ {"libero", "libero_plus", "metaworld", "robocasa", "robotwin", "robomme", "vlabench"}
42
+ )
43
+
44
+
45
+ def dataset_for(env_type: str) -> DatasetInfo | None:
46
+ return DATASETS.get(env_type)
47
+
48
+
49
+ def default_policy(env_type: str) -> str:
50
+ """SmolVLA for multi-task/VLA benchmarks, else ACT."""
51
+ return "smolvla" if env_type in _VLA_BENCHMARKS else "act"
52
+
53
+
54
+ def train_command(env_type: str, task_name: str, policy: str | None = None) -> str:
55
+ policy = policy or default_policy(env_type)
56
+ ds = dataset_for(env_type)
57
+ repo = ds.repo_id if ds else "<your_dataset>"
58
+ return (
59
+ f"lerobot-train --policy.type={policy} --dataset.repo_id={repo} "
60
+ f"--env.type={env_type} --env.task={task_name} "
61
+ f"--batch_size=8 --steps=50000 --policy.device=cuda"
62
+ )
63
+
64
+
65
+ def eval_command(env_type: str, task_name: str, policy_path: str = "<your_policy>") -> str:
66
+ return (
67
+ f"lerobot-eval --policy.path={policy_path} "
68
+ f"--env.type={env_type} --env.task={task_name} --policy.device=cuda"
69
+ )
@@ -0,0 +1,22 @@
1
+ {
2
+ "_source": "https://robomme.github.io/ (RoboMME official website, 'RoboMME Task Examples' instructions)",
3
+ "_note": "Verbatim task instructions; keys match ROBOMME_TASKS in robomme.py",
4
+ "descriptions": {
5
+ "BinFill": "Put two green cubes into the bin and press the button to stop.",
6
+ "PickXtimes": "Pick up the blue cube and place it on the target, repeating this pick-and-place action three times, then press the button to stop.",
7
+ "SwingXtimes": "Pick up the red cube, move it to the right-side target and then to the left-side target, repeating this right-to-left swing motion three times, then put down the cube and press the button to stop.",
8
+ "StopCube": "Press the button to stop the cube exactly at the target on its third visit.",
9
+ "VideoUnmask": "Watch the video carefully, then pick up the container hiding the green cube.",
10
+ "VideoUnmaskSwap": "Watch the video carefully, then pick up the container hiding the blue cube, finally pick up another container hiding the red cube.",
11
+ "ButtonUnmask": "First press the button, then pick up the container hiding the green cube, finally pick up another container hiding the red cube.",
12
+ "ButtonUnmaskSwap": "First press both buttons on the table, then pick up the container hiding the red cube.",
13
+ "PickHighlight": "First press the button, then pick up all highlighted cubes, finally press the button again to stop.",
14
+ "VideoRepick": "Watch the video carefully, identify the cube that was picked up, then pick up and place down the same cube twice, finally press the button to stop.",
15
+ "VideoPlaceButton": "Watch the video carefully, then place the blue cube on the target where it was last placed before the button was pressed.",
16
+ "VideoPlaceOrder": "Watch the video carefully and place the green cube on the third target where it was placed.",
17
+ "MoveCube": "Watch the video carefully, then move the cube to the target in the same manner shown in the video.",
18
+ "InsertPeg": "Watch the video carefully, then grasp the same peg at the same end and insert it into the same side of the box as in the video.",
19
+ "PatternLock": "Watch the video carefully, then use the stick attached to the robot to retrace the same pattern shown in the video.",
20
+ "RouteStick": "Watch the video carefully, then use the stick attached to the robot to navigate around the sticks on the table, following the same path shown in the video."
21
+ }
22
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "_source": "https://robotwin-platform.github.io/doc/tasks/ (RoboTwin 2.0 official docs)",
3
+ "_note": "Verbatim task descriptions; keys match ROBOTWIN_TASKS in robotwin.py",
4
+ "descriptions": {
5
+ "adjust_bottle": "Pick up the bottle on the table headup with the correct arm.",
6
+ "beat_block_hammer": "There is a hammer and a block on the table, use the arm to grab the hammer and beat the block.",
7
+ "blocks_ranking_rgb": "Place the red block, green block, and blue block in the order of red, green, and blue from left to right, placing in a row.",
8
+ "blocks_ranking_size": "There are three blocks on the table, the color of the blocks is random, move the blocks to the center of the table, and arrange them from largest to smallest, from left to right.",
9
+ "click_alarmclock": "Click the alarm clock's center of the top side button on the table.",
10
+ "click_bell": "Click the bell's top center on the table.",
11
+ "dump_bin_bigbin": "Grab the small bin and pour the balls into the big bin.",
12
+ "grab_roller": "Use both arms to grab the roller on the table.",
13
+ "handover_block": "Use the left arm to grasp the red block on the table, handover it to the right arm and place it on the blue pad.",
14
+ "handover_mic": "Use one arm to grasp the microphone on the table and handover it to the other arm.",
15
+ "hanging_mug": "Use left arm to pick the mug on the table, rotate the mug and put the mug down in the middle of the table, use the right arm to pick the mug and hang it onto the rack.",
16
+ "lift_pot": "Use arms to lift the pot.",
17
+ "move_can_pot": "There is a can and a pot on the table, use one arm to pick up the can and move it to beside the pot.",
18
+ "move_pillbottle_pad": "Use one arm to pick the pillbottle and place it onto the pad.",
19
+ "move_playingcard_away": "Use the arm to pick up the playing card and move it away from the table. For example, if the playing card is on the outward side of the table, you should move it further outward side of the table.",
20
+ "move_stapler_pad": "Use appropriate arm to move the stapler to a colored mat.",
21
+ "open_laptop": "Use one arm to open the laptop.",
22
+ "open_microwave": "Use one arm to open the microwave.",
23
+ "pick_diverse_bottles": "Pick up one bottle with one arm, and pick up another bottle with the other arm.",
24
+ "pick_dual_bottles": "Pick up one bottle with one arm, and pick up another bottle with the other arm.",
25
+ "place_a2b_left": "Use appropriate arm to place object A on the left of object B.",
26
+ "place_a2b_right": "Use appropriate arm to place object A on the right of object B.",
27
+ "place_bread_basket": "If there is one bread on the table, use one arm to grab the bread and put it in the basket, if there are two breads on the table, use two arms to simultaneously grab up two breads and put them in the basket.",
28
+ "place_bread_skillet": "If there is one bread on the table, use one arm to grab the bread and put it into the skillet.",
29
+ "place_burger_fries": "Use dual arm to pick the hamburg and frenchfries and put them onto the tray.",
30
+ "place_can_basket": "Use one arm to pick up the can, put it into the basket, and use another arm to lift the basket",
31
+ "place_cans_plasticbox": "Use dual arm to pick and place cans into plasticbox.",
32
+ "place_container_plate": "Place the container onto the plate.",
33
+ "place_dual_shoes": "Use both arms to pick up the two shoes on the table and put them in the shoebox, with the shoe tip pointing to the left.",
34
+ "place_empty_cup": "Use an arm to place the empty cup on the coaster.",
35
+ "place_fan": "Grab the fan and place it on a colored mat, and make sure the fan is facing the robot.",
36
+ "place_mouse_pad": "Grab the mouse and place it on a colored mat.",
37
+ "place_object_basket": "Use one arm to grab the target object and put it in the basket, then use the other arm to grab the basket, and finally move the basket slightly away.",
38
+ "place_object_scale": "Use one arm to grab the object and put it on the scale.",
39
+ "place_object_stand": "Use appropriate arm to place the object on the stand.",
40
+ "place_phone_stand": "Pick up the phone and put it on the phone stand.",
41
+ "place_shoe": "Use one arm to grab the shoe from the table and place it on the mat.",
42
+ "press_stapler": "Use one arm to press the stapler.",
43
+ "put_bottles_dustbin": "Use arms to grab the bottles and put them into the dustbin to the left of the table.",
44
+ "put_object_cabinet": "Use one arm to open the cabinet's drawer, and use another arm to put the object on the table to the drawer.",
45
+ "rotate_qrcode": "Use arm to catch the qrcode board on the table, pick it up and rotate to let the qrcode face towards the robot.",
46
+ "scan_object": "Use one arm to pick the scanner and use the other arm to pick the object, and use the scanner to scan the object.",
47
+ "shake_bottle": "Shake the bottle with proper arm.",
48
+ "shake_bottle_horizontally": "Shake the bottle horizontally with proper arm.",
49
+ "stack_blocks_three": "There are three blocks on the table, the color of the blocks is red, green and blue. Move the blocks to the center of the table, and stack the blue block on the green block, and the green block on the red block.",
50
+ "stack_blocks_two": "There are two blocks on the table, the color of the blocks is red, green. Move the blocks to the center of the table, and stack the geen block on the red block.",
51
+ "stack_bowls_three": "Stack the three bowls on top of each other.",
52
+ "stack_bowls_two": "Stack the two bowls on top of each other.",
53
+ "stamp_seal": "Grab the stamp and stamp onto the specific color mat.",
54
+ "turn_switch": "Use the robotic arm to click the switch."
55
+ }
56
+ }
@@ -0,0 +1,66 @@
1
+ # Copyright 2025. Apache-2.0.
2
+ """Locate a backend's checkout, trying several conventions in order.
3
+
4
+ Precedence (first hit wins):
5
+ 1. `$<ENV_VAR>` explicit override (e.g. $LEROBOT_HOME)
6
+ 2. `<cwd>/third_party/ei_ws/<sub>` vendored inside the current project
7
+ 3. `~/ei_ws/<sub>` the global embodied-intelligence workspace
8
+ 4. installed package (`find_spec`) pip-installed backend, no checkout needed
9
+
10
+ Returns a `Resolution` describing where the backend was found (or that it wasn't),
11
+ without importing the backend.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import importlib.util
17
+ import os
18
+ from dataclasses import dataclass
19
+ from pathlib import Path
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class Resolution:
24
+ found: bool
25
+ source: str # "env" | "third_party" | "ei_ws" | "installed" | "missing"
26
+ root: Path | None = None # checkout root (None for "installed"/"missing")
27
+ detail: str = ""
28
+
29
+ def __bool__(self) -> bool:
30
+ return self.found
31
+
32
+
33
+ def resolve_root(
34
+ subpath: str, env_var: str | None = None, import_name: str | None = None, cwd: Path | None = None
35
+ ) -> Resolution:
36
+ """Resolve a backend location. `subpath` is the dir name under the ei_ws roots."""
37
+ cwd = cwd or Path.cwd()
38
+
39
+ if env_var and os.environ.get(env_var):
40
+ p = Path(os.environ[env_var]).expanduser()
41
+ if p.exists():
42
+ return Resolution(True, "env", p, f"${env_var}={p}")
43
+ return Resolution(False, "missing", None, f"${env_var}={p} does not exist")
44
+
45
+ candidates = [
46
+ ("third_party", cwd / "third_party" / "ei_ws" / subpath),
47
+ ("ei_ws", Path.home() / "ei_ws" / subpath),
48
+ ]
49
+ for source, path in candidates:
50
+ if path.exists():
51
+ return Resolution(True, source, path, str(path))
52
+
53
+ if import_name:
54
+ spec = importlib.util.find_spec(import_name)
55
+ if spec and spec.origin:
56
+ return Resolution(
57
+ True, "installed", None, f"installed package {import_name} at {Path(spec.origin).parent}"
58
+ )
59
+
60
+ hint = env_var or "the workspace"
61
+ return Resolution(
62
+ False,
63
+ "missing",
64
+ None,
65
+ f"not found via ${hint}, ./third_party/ei_ws/{subpath}, ~/ei_ws/{subpath}, or pip",
66
+ )
@@ -0,0 +1,46 @@
1
+ # Copyright 2025. Apache-2.0.
2
+ """Query matching over a task corpus: substring + token-subset, with a fuzzy fallback."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import difflib
7
+ import re
8
+
9
+ from get_embodied_tasks.catalog import TaskEntry
10
+
11
+
12
+ def _tokenize(text: str) -> set[str]:
13
+ return set(re.split(r"[-_\s]+", text.lower())) - {""}
14
+
15
+
16
+ def find_tasks(
17
+ entries: list[TaskEntry], query: str, benchmark: str | None = None, limit: int = 20
18
+ ) -> tuple[list[TaskEntry], list[TaskEntry]]:
19
+ """Return (matches, fuzzy_suggestions).
20
+
21
+ A match is a substring hit on the entry's search text, or an entry whose tokens
22
+ contain every query token. When there are no matches, suggestions are the closest
23
+ task names (difflib) ranked by token overlap.
24
+ """
25
+ corpus = entries
26
+ if benchmark:
27
+ b = benchmark.lower()
28
+ corpus = [e for e in corpus if b in (e.benchmark.lower(), e.env_type.lower())]
29
+
30
+ q = query.strip().lower()
31
+ q_tokens = _tokenize(q)
32
+
33
+ matches = [e for e in corpus if (q and q in e.search_text) or (q_tokens and q_tokens <= e.tokens)]
34
+ if matches:
35
+ matches.sort(key=lambda e: (not e.enumerable, e.benchmark, e.task_name))
36
+ return matches[:limit], []
37
+
38
+ names = [e.task_name for e in corpus]
39
+ close = set(difflib.get_close_matches(query, names, n=limit, cutoff=0.4))
40
+ scored: list[tuple[float, TaskEntry]] = []
41
+ for e in corpus:
42
+ score = len(q_tokens & e.tokens) + (0.5 if e.task_name in close else 0.0)
43
+ if score > 0:
44
+ scored.append((score, e))
45
+ scored.sort(key=lambda s: (-s[0], s[1].benchmark, s[1].task_name))
46
+ return [], [e for _, e in scored[:limit]]
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: get-embodied-tasks
3
+ Version: 0.1.0
4
+ Summary: Resolve an embodied-AI task description to its dataset and ready-to-run train/eval commands across pluggable backends (LeRobot, ...).
5
+ Project-URL: Homepage, https://github.com/OpenGHz/get-embodied-tasks
6
+ Project-URL: Repository, https://github.com/OpenGHz/get-embodied-tasks
7
+ Author: OpenGHz
8
+ License: Apache-2.0
9
+ Keywords: agent-skill,benchmark,datasets,embodied-ai,lerobot,robotics
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
13
+ Requires-Python: >=3.11
14
+ Provides-Extra: test
15
+ Requires-Dist: pytest; extra == 'test'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # get-embodied-tasks
19
+
20
+ Resolve an embodied-AI task description to its **existing dataset** and
21
+ **ready-to-run train/eval commands**, across pluggable backends. **LeRobot** is the
22
+ first backend; more can be added. Ships as both a CLI and an agent skill (Claude +
23
+ Codex).
24
+
25
+ ```bash
26
+ get-embodied-tasks "stack blocks"
27
+ # → [lerobot] RoboTwin stack_blocks_two
28
+ # Dataset: lerobot/robotwin_unified
29
+ # Train: lerobot-train --policy.type=smolvla --dataset.repo_id=lerobot/robotwin_unified ...
30
+ # Eval: lerobot-eval --env.type=robotwin --env.task=stack_blocks_two ...
31
+ ```
32
+
33
+ ## Design
34
+
35
+ - **Facts in the tool, judgment in the skill.** The CLI emits real dataset ids and
36
+ command flags (never model-recalled). The `SKILL.md` playbook parses a fuzzy task
37
+ description, calls the CLI, and on a miss researches alternatives or drafts a plan.
38
+ - **Backends are read, not vendored.** The LeRobot adapter reads a *vanilla* LeRobot
39
+ checkout by AST over its `envs/` source (no import of torch/gymnasium; no patches to
40
+ LeRobot). RoboTwin/RoboMME task descriptions ship here in `data/`.
41
+ - **Drift is surfaced, never hidden.** If a backend's expected constant/file changes,
42
+ the catalog emits structured warnings (stderr, `--json` `warnings`, `--strict`,
43
+ `doctor`) so it can be fixed.
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install -e .
49
+ bash install.sh # symlink into ~/.agents/skills (Claude + Codex pick it up)
50
+ ```
51
+
52
+ ## Backend discovery
53
+
54
+ For each backend, in order: `$<ENV_VAR>` → `./third_party/ei_ws/<name>` →
55
+ `~/ei_ws/<name>` → installed package. For LeRobot set `LEROBOT_HOME=<checkout>` or
56
+ just `pip install lerobot`. Configure backends in
57
+ [`src/get_embodied_tasks/backends.toml`](src/get_embodied_tasks/backends.toml).
58
+
59
+ ## CLI
60
+
61
+ ```
62
+ get-embodied-tasks "<query>" [-b BENCHMARK] [--backend NAME] [-n N] [--policy P] [--json]
63
+ get-embodied-tasks --benchmark <b> # list a benchmark
64
+ get-embodied-tasks --dump # whole catalog as JSON
65
+ get-embodied-tasks doctor # backend health / drift report
66
+ ```
67
+
68
+ ## Reference docs (per backend)
69
+
70
+ [`docs/backends/lerobot/`](docs/backends/lerobot/) holds reference material for the
71
+ LeRobot backend, regeneratable from a vanilla checkout:
72
+
73
+ - [`cli_reference.md`](docs/backends/lerobot/cli_reference.md) — every `lerobot-*` command + its extra (generated)
74
+ - [`components_reference.md`](docs/backends/lerobot/components_reference.md) — registered policies/robots/teleops/cameras/optimizers/schedulers/processors (generated)
75
+ - [`troubleshooting.md`](docs/backends/lerobot/troubleshooting.md) — consolidated FAQ (hand-written, links upstream)
76
+
77
+ Refresh the generated ones:
78
+
79
+ ```bash
80
+ python docs/scripts/gen_lerobot_reference.py --lerobot-root <checkout> # or rely on discovery
81
+ python docs/scripts/gen_lerobot_reference.py --check # CI: fail if stale
82
+ ```
83
+
84
+ ## Adding a backend
85
+
86
+ Add an adapter under `src/get_embodied_tasks/backends/` implementing
87
+ `backends/base.py:Backend`, register it in `cli.py:_ADAPTERS`, and add a table to
88
+ `backends.toml`.
@@ -0,0 +1,17 @@
1
+ get_embodied_tasks/__init__.py,sha256=t4xMYNZ57wK_qGztXaSvQeKVUJ7qwKp8xD_QmXMwwXE,137
2
+ get_embodied_tasks/backends.toml,sha256=lEeNKlRm-J4hZ3d8S8OnT6UqqjuVOCdykNC2KSE6dYc,411
3
+ get_embodied_tasks/catalog.py,sha256=7fd7gv79LOtc8pvZvcfsN58F_X1do-XIlEiYjP1Gfs8,11495
4
+ get_embodied_tasks/cli.py,sha256=u026atAWwMNYQYB9vktv8m0kdzUvUbpVKV-7PZePSWc,7427
5
+ get_embodied_tasks/discovery.py,sha256=dcISWq6keh39Jt41-YVFnn6Q1Izj48Da4TZ7SloJ9KI,2243
6
+ get_embodied_tasks/matching.py,sha256=J3QUmIrPrhimlJ7fBnoXg3FUrpBbVBR86rxDYPFjmKw,1654
7
+ get_embodied_tasks/backends/__init__.py,sha256=dJZfelbSlqWJYcO6ramHYvTMnZtegylLGC5zx78aElg,30
8
+ get_embodied_tasks/backends/base.py,sha256=ynCMJ9xWXHHbTWK920ZwU1MAfn-KjyXg7CcwKtk9T2c,1016
9
+ get_embodied_tasks/backends/lerobot.py,sha256=oLW0cYcg75yjB3rhfKf4z4kGNrlS8TVvyc2_DsTuko8,3646
10
+ get_embodied_tasks/data/__init__.py,sha256=dJZfelbSlqWJYcO6ramHYvTMnZtegylLGC5zx78aElg,30
11
+ get_embodied_tasks/data/lerobot_datasets.py,sha256=T1CiOSqgazCrCLp2zlqCKomb573MdsY-sdh5MhApG9s,2612
12
+ get_embodied_tasks/data/robomme_task_descriptions.json,sha256=0rbEg_EVDhGyA-0zl4hAdPf4S0Whme2z7PDdNKr8WjE,2427
13
+ get_embodied_tasks/data/robotwin_task_descriptions.json,sha256=MfCetXbFJKZ2aehNIVk15JEXU4AkbOeqAKkGYoZWUew,5691
14
+ get_embodied_tasks-0.1.0.dist-info/METADATA,sha256=2N1-mjSbsOj1ChEL5_LjaFgUHyjHFXImOBkfyS2YlqA,3780
15
+ get_embodied_tasks-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ get_embodied_tasks-0.1.0.dist-info/entry_points.txt,sha256=nFwS_I7hrs8ejrjkH9_RIJ_-G235JxKZK2lktnToICg,67
17
+ get_embodied_tasks-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ get-embodied-tasks = get_embodied_tasks.cli:main