openral-cli 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.
openral_cli/prompt.py ADDED
@@ -0,0 +1,128 @@
1
+ """``openral prompt`` CLI adapter.
2
+
3
+ Publishes a one-shot ``openral_msgs/PromptStamped`` onto
4
+ ``/openral/prompt_in/cli`` and exits. The ``prompt_router_node``
5
+ (``packages/openral_prompt_router``) fans the message out onto
6
+ ``/openral/prompt`` after stamping ``{"source": "cli", "priority": 100}``
7
+ onto ``metadata_json``; the F4 reasoner consumes ``/openral/prompt``.
8
+
9
+ Wire shape:
10
+
11
+ * QoS: ``RELIABLE + VOLATILE + KEEP_LAST=10`` (matches the router's
12
+ subscription so the message survives a one-shot publish-and-exit
13
+ even if the router was a hair late to subscribe).
14
+ * ``metadata_json``: ``{"source_cli": true}`` — minimal; the router
15
+ appends the canonical ``source`` / ``priority`` fields.
16
+
17
+ ``rclpy`` is imported lazily inside the typer command so ``openral --help``
18
+ stays sub-second even when ROS is not sourced.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import time
25
+
26
+ import typer
27
+
28
+ __all__ = ["prompt_command"]
29
+
30
+
31
+ def prompt_command(
32
+ text: str = typer.Argument(..., help='Prompt text, e.g. "pick the red cube"'),
33
+ topic: str = typer.Option(
34
+ "/openral/prompt_in/cli",
35
+ help="ROS topic to publish on; defaults to the prompt-router's CLI input.",
36
+ ),
37
+ wait_s: float = typer.Option(
38
+ 1.0,
39
+ help=(
40
+ "Seconds to wait after publish before exiting. Gives the prompt-router "
41
+ "time to receive on a fresh DDS discovery; 0 exits immediately."
42
+ ),
43
+ ),
44
+ discovery_wait_s: float = typer.Option(
45
+ 5.0,
46
+ help=(
47
+ "Seconds to spin waiting for the prompt-router to discover the publisher "
48
+ "before giving up and publishing anyway. Cold-boot deploys with stale "
49
+ "Fast-DDS shared-memory remnants need 10-15 s; the default 5 s matches "
50
+ "the interactive use case where the launch has been up for a while."
51
+ ),
52
+ ),
53
+ ) -> None:
54
+ """Publish a one-shot PromptStamped on the prompt-router CLI input topic.
55
+
56
+ Requires a sourced ROS 2 install. Exits 0 on success, 2 when
57
+ ``rclpy`` / ``openral_msgs`` are not importable (after pointing
58
+ the user at ``just ros2-build`` + ``source install/setup.bash``).
59
+
60
+ Example::
61
+
62
+ openral prompt "pick the red cube"
63
+ """
64
+ try:
65
+ import rclpy # noqa: PLC0415 # reason: heavy ROS dep deferred
66
+ from openral_msgs.msg import ( # noqa: PLC0415 # reason: heavy ROS dep deferred
67
+ PromptStamped,
68
+ )
69
+ from rclpy.qos import ( # noqa: PLC0415
70
+ QoSDurabilityPolicy,
71
+ QoSHistoryPolicy,
72
+ QoSProfile,
73
+ QoSReliabilityPolicy,
74
+ )
75
+ except ImportError as exc:
76
+ typer.echo(
77
+ f"openral prompt: cannot import rclpy / openral_msgs ({exc!s}). "
78
+ "Run `just ros2-build` then `source install/setup.bash` first.",
79
+ err=True,
80
+ )
81
+ raise typer.Exit(code=2) from exc
82
+
83
+ rclpy.init()
84
+ try:
85
+ node = rclpy.create_node("openral_cli_prompt")
86
+ qos = QoSProfile(
87
+ history=QoSHistoryPolicy.KEEP_LAST,
88
+ depth=10,
89
+ reliability=QoSReliabilityPolicy.RELIABLE,
90
+ durability=QoSDurabilityPolicy.VOLATILE,
91
+ )
92
+ pub = node.create_publisher(PromptStamped, topic, qos)
93
+
94
+ msg = PromptStamped()
95
+ msg.header.stamp = node.get_clock().now().to_msg()
96
+ msg.header.frame_id = "openral_cli_prompt"
97
+ msg.text = text
98
+ msg.metadata_json = json.dumps({"source_cli": True}, sort_keys=True)
99
+
100
+ # Spin until a subscriber matches so the one-shot publish is
101
+ # not delivered into the void. 0.5 s was too tight on hosts
102
+ # where the shared-memory transport falls back to UDP
103
+ # discovery (we saw `RTPS_TRANSPORT_SHM Error: Failed init_port`
104
+ # on this box, which adds ~1 s before UDP picks up the
105
+ # prompt_router subscription). 5 s covers normal interactive
106
+ # use; cold-boot deploys (where the prompt is sent within the
107
+ # same shell session as the launch start) can need 10-15 s,
108
+ # exposed via ``--discovery-wait-s``.
109
+ deadline = time.monotonic() + discovery_wait_s
110
+ while time.monotonic() < deadline and pub.get_subscription_count() == 0:
111
+ rclpy.spin_once(node, timeout_sec=0.05)
112
+ n_subs = pub.get_subscription_count()
113
+ if n_subs == 0:
114
+ typer.echo(
115
+ f"openral prompt: no subscriber on {topic} after {discovery_wait_s:.1f} s; "
116
+ "is the launch up?",
117
+ err=True,
118
+ )
119
+ pub.publish(msg)
120
+ typer.echo(
121
+ f"openral prompt: published on {topic} text={text!r} (matched_subs={n_subs})",
122
+ )
123
+
124
+ if wait_s > 0:
125
+ time.sleep(wait_s)
126
+ node.destroy_node()
127
+ finally:
128
+ rclpy.shutdown()
openral_cli/py.typed ADDED
File without changes
openral_cli/robot.py ADDED
@@ -0,0 +1,301 @@
1
+ """``openral robot vendor-urdf <id>`` — expand an upstream xacro to a flat URDF.
2
+
3
+ The flat, committed URDF means end users need no xacro tooling at runtime.
4
+ The xacro-only arms (ur5e/ur10e/rizon4) ship a ``XACRO_PATH`` in
5
+ ``robot_descriptions``; we let ``robot_descriptions``' ``yourdfpy`` loader run
6
+ ``xacrodoc`` to expand every ``${…}`` substitution, then serialize the resulting
7
+ flat URDF with a provenance header. ``openarm`` ships only MJCF upstream, so its
8
+ flattened URDF is cloned separately and passed as a ``file:`` upstream; the
9
+ ``--rename`` hook strips the ``openarm_`` joint/link prefix to the OpenRAL HAL
10
+ convention (``left_joint1..7`` / ``right_joint1..7``).
11
+
12
+ **Portable mesh refs.** The yourdfpy round-trip absolutizes every mesh
13
+ ``filename`` into the vendoring machine's ``robot_descriptions`` cache — paths
14
+ that resolve nowhere else, which once broke collision lowering for the whole
15
+ xacro fleet in CI (empty ACMs). For ``rd:`` upstreams the round-trip output is
16
+ therefore post-processed by :func:`_portable_mesh_refs`: cache-absolute paths
17
+ become ``rd:<module>:<path-relative-to-repository>`` refs, expanded at load
18
+ time by ``openral_safety.urdf_lowering`` through the same pinned clone.
19
+
20
+ **Raw-text mode** (``raw_text=True``). The same yourdfpy round-trip also
21
+ mangles ``package://`` mesh paths, which is fatal for already-flat upstream
22
+ URDFs that ship relative or ``package://`` meshes (so100/so101/gr1/h1). For
23
+ those we copy the upstream text verbatim and apply joint-name renames with
24
+ ``re.sub`` directly on the raw XML — preserving every mesh path byte-for-byte.
25
+ The renames target **joint names only** (``<joint name="X"`` and any
26
+ ``joint="X"`` mimic/transmission references); link names are never touched. A
27
+ :class:`list` of ``(pattern, repl)`` pairs is applied in order, so so100/so101
28
+ take six numeric renames and gr1/h1 take one ``_joint``-suffix strip.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import re
34
+ from collections.abc import Sequence
35
+ from pathlib import Path
36
+ from typing import cast
37
+
38
+ _PROVENANCE = (
39
+ "<!-- Vendored by `openral robot vendor-urdf {id}` from {src}. Upstream license applies. -->\n"
40
+ )
41
+
42
+ # SO-ARM numeric-joint → semantic HAL-name map, applied by joint NUMBER (the
43
+ # upstream so100/so101 URDFs name joints "1".."6"; the SO-ARM motor convention
44
+ # and the manifest use these semantic names). Both follower arms share the map.
45
+ _SO_ARM_JOINT_NAMES: tuple[str, ...] = (
46
+ "shoulder_pan", # 1
47
+ "shoulder_lift", # 2
48
+ "elbow_flex", # 3
49
+ "wrist_flex", # 4
50
+ "wrist_roll", # 5
51
+ "gripper", # 6
52
+ )
53
+
54
+ # Per-robot raw-text joint renames (regex pattern, replacement), applied in
55
+ # order. Patterns are scoped to the joint-name context so no <link …> is hit:
56
+ # * so100/so101: rewrite ``name="N"`` (only ever a <joint> in these URDFs;
57
+ # links are semantic, transmissions are ``N_trans`` / ``motorN``).
58
+ # * gr1/h1: strip the ``_joint`` suffix from every ``name="…_joint"`` (no link
59
+ # ends in ``_joint`` in either URDF — verified).
60
+ # * gr1 also collapses ``*_elbow_pitch`` → ``*_elbow`` to match the manifest's
61
+ # HAL joint name (the upstream URDF spells the single-DoF elbow joint
62
+ # ``*_elbow_pitch_joint``; the manifest/control contract calls it ``*_elbow``).
63
+ # Applied AFTER the ``_joint`` strip; link-safe (no ``*_elbow_pitch`` link
64
+ # exists — only the two ``*_elbow_pitch_joint`` joints — verified).
65
+ _RAW_RENAMES: dict[str, list[tuple[str, str]]] = {
66
+ "so100_follower": [
67
+ (rf'name="{n}"', f'name="{sem}"') for n, sem in enumerate(_SO_ARM_JOINT_NAMES, start=1)
68
+ ],
69
+ "so101_follower": [
70
+ (rf'name="{n}"', f'name="{sem}"') for n, sem in enumerate(_SO_ARM_JOINT_NAMES, start=1)
71
+ ],
72
+ "gr1": [
73
+ (r'name="([^"]*)_joint"', r'name="\1"'),
74
+ (r'name="(left|right)_elbow_pitch"', r'name="\1_elbow"'),
75
+ ],
76
+ "h1": [(r'name="([^"]*)_joint"', r'name="\1"')],
77
+ }
78
+
79
+ # Joint-name normalization to the OpenRAL HAL convention. openarm: strip the
80
+ # "openarm_" prefix so joints become left_joint1..7 / right_joint1..7.
81
+ _RENAME: dict[str, tuple[str, str]] = {"openarm": (r'"openarm_', '"')}
82
+
83
+ # A ``(pattern, repl)`` rename is a 2-tuple; named to satisfy the magic-value lint.
84
+ _PAIR_LEN = 2
85
+
86
+
87
+ def _model_to_xml(model: object) -> str:
88
+ """Serialize a loaded ``yourdfpy.URDF`` to a pretty-printed flat URDF string.
89
+
90
+ yourdfpy ≥0.0.56 exposes ``write_xml_string()`` returning the serialized URDF
91
+ as ASCII ``bytes`` on a single line (verified against the installed package —
92
+ it wraps ``lxml.etree.tostring``; its ``**kwargs`` forwarding is broken so we
93
+ cannot pass ``pretty_print`` through it). We re-parse and pretty-print via lxml
94
+ so the committed file is human-reviewable + git-diffable.
95
+ """
96
+ from lxml import etree
97
+
98
+ # ``write_xml_string`` is the public serializer in yourdfpy ≥0.0.56; one line.
99
+ raw = model.write_xml_string() # type: ignore[attr-defined] # reason: yourdfpy.URDF has no stubs
100
+ root = etree.fromstring(raw if isinstance(raw, bytes) else raw.encode("utf-8"))
101
+ pretty: bytes = etree.tostring(root, pretty_print=True, xml_declaration=True, encoding="utf-8")
102
+ return pretty.decode("utf-8")
103
+
104
+
105
+ def _load_model(upstream: str) -> object:
106
+ """Load an upstream description into a ``yourdfpy.URDF``.
107
+
108
+ ``rd:<module>`` resolves through ``robot_descriptions`` (xacro expanded via
109
+ ``xacrodoc``); ``file:<path>`` loads an already-flat URDF directly.
110
+ """
111
+ if upstream.startswith("file:"):
112
+ import yourdfpy
113
+
114
+ path = Path(upstream[len("file:") :])
115
+ # build_scene_graph/build_collision_scene_graph are off so an
116
+ # upstream that references missing meshes still parses for re-export.
117
+ return yourdfpy.URDF.load(
118
+ str(path),
119
+ build_scene_graph=False,
120
+ build_collision_scene_graph=False,
121
+ load_meshes=False,
122
+ load_collision_meshes=False,
123
+ )
124
+ from robot_descriptions.loaders.yourdfpy import load_robot_description
125
+
126
+ module = upstream[len("rd:") :] if upstream.startswith("rd:") else upstream
127
+ return load_robot_description(module) # expands xacro via xacrodoc
128
+
129
+
130
+ def vendor_urdf(
131
+ robot_id: str,
132
+ *,
133
+ upstream: str,
134
+ out_dir: Path,
135
+ rename: tuple[str, str] | Sequence[tuple[str, str]] | None = None,
136
+ raw_text: bool = False,
137
+ ) -> Path:
138
+ """Expand ``upstream`` to a flat URDF at ``out_dir/<robot_id>.urdf``.
139
+
140
+ Args:
141
+ robot_id: OpenRAL robot id; names the output file and selects the
142
+ default rename rule.
143
+ upstream: ``rd:<robot_descriptions module>`` for xacro arms, or
144
+ ``file:<path>`` for an already-flat upstream URDF (openarm).
145
+ out_dir: Directory to write ``<robot_id>.urdf`` into (created if absent).
146
+ rename: A ``(pattern, repl)`` pair, or a sequence of them, applied with
147
+ ``re.sub`` in order. ``None`` selects the per-robot default
148
+ (``_RAW_RENAMES[robot_id]`` in raw-text mode, else ``_RENAME``).
149
+ raw_text: When ``True``, copy the already-flat upstream URDF text
150
+ verbatim and apply ``rename`` directly to the raw XML — no yourdfpy
151
+ round-trip — so ``package://`` / relative mesh paths are preserved
152
+ byte-for-byte. Only valid for ``file:`` / ``rd:`` URDFs that are
153
+ already flat (so100/so101/gr1/h1). Mutually exclusive with xacro
154
+ expansion: the upstream must contain no ``${…}``.
155
+
156
+ Returns:
157
+ Path to the written URDF.
158
+
159
+ Example:
160
+ >>> from pathlib import Path
161
+ >>> import tempfile
162
+ >>> d = Path(tempfile.mkdtemp())
163
+ >>> # vendor-urdf needs the optional `lowering` group (yourdfpy/xacrodoc):
164
+ >>> out = vendor_urdf( # doctest: +SKIP
165
+ ... "ur5e", upstream="rd:ur5e_description", out_dir=d
166
+ ... )
167
+ >>> out.name # doctest: +SKIP
168
+ 'ur5e.urdf'
169
+ """
170
+ out_dir.mkdir(parents=True, exist_ok=True)
171
+ out = out_dir / f"{robot_id}.urdf"
172
+ if raw_text:
173
+ xml = _read_raw_text(upstream)
174
+ renames = _resolve_renames(robot_id, rename, default=_RAW_RENAMES.get(robot_id))
175
+ for pat, repl in renames:
176
+ xml = re.sub(pat, repl, xml)
177
+ # Preserve the upstream's exact bytes apart from the renamed joint names
178
+ # and the inserted provenance comment — including its newline style — so
179
+ # the only diff vs upstream is the joint renames (gate: lowering must
180
+ # stay byte-identical, mesh paths verbatim). newline="" disables write-side
181
+ # newline translation (Path.write_text gained the kwarg only in 3.13).
182
+ with out.open("w", encoding="utf-8", newline="") as fh:
183
+ fh.write(_with_provenance_raw(xml, id=robot_id, src=upstream))
184
+ return out
185
+ model = _load_model(upstream)
186
+ xml = _model_to_xml(model)
187
+ if upstream.startswith("rd:"):
188
+ xml = _portable_mesh_refs(xml, module=upstream[len("rd:") :])
189
+ default = [_RENAME[robot_id]] if robot_id in _RENAME else None
190
+ renames = _resolve_renames(robot_id, rename, default=default)
191
+ for pat, repl in renames:
192
+ xml = re.sub(pat, repl, xml)
193
+ out.write_text(_with_provenance(xml, id=robot_id, src=upstream))
194
+ return out
195
+
196
+
197
+ def _portable_mesh_refs(xml: str, *, module: str) -> str:
198
+ """Rewrite cache-absolutized mesh paths to portable ``rd:<module>:<relpath>`` refs.
199
+
200
+ The yourdfpy round-trip resolves every mesh ``filename`` to an absolute path
201
+ inside the vendoring machine's ``robot_descriptions`` clone
202
+ (``~/.cache/robot_descriptions/<repo>/…``) — paths that exist nowhere else, so
203
+ committing them breaks collision lowering on every other host (CI's re-lower
204
+ then finds no meshes and emits an empty ACM). Rewrite them to
205
+ ``rd:<module>:<path-relative-to-repository>``, which
206
+ ``openral_safety.urdf_lowering`` expands at load time through the same pinned
207
+ ``robot_descriptions`` clone on any machine.
208
+ """
209
+ import importlib
210
+
211
+ mod = importlib.import_module(f"robot_descriptions.{module}")
212
+ repo = str(Path(mod.REPOSITORY_PATH))
213
+ return xml.replace(f'filename="{repo}/', f'filename="rd:{module}:')
214
+
215
+
216
+ def _resolve_renames(
217
+ robot_id: str,
218
+ rename: tuple[str, str] | Sequence[tuple[str, str]] | None,
219
+ *,
220
+ default: list[tuple[str, str]] | None,
221
+ ) -> list[tuple[str, str]]:
222
+ """Normalize the ``rename`` argument to a flat list of ``(pattern, repl)``.
223
+
224
+ A bare ``(pattern, repl)`` pair (two strings) is wrapped into a one-element
225
+ list; a sequence of pairs is passed through; ``None`` falls back to the
226
+ per-robot ``default``.
227
+ """
228
+ if rename is None:
229
+ return list(default) if default else []
230
+ # A bare ``(pattern, repl)`` is a 2-tuple of strings; a sequence of such pairs
231
+ # is anything else iterable. ``_PAIR_LEN`` names the 2 to satisfy PLR2004.
232
+ if (
233
+ isinstance(rename, tuple)
234
+ and len(rename) == _PAIR_LEN
235
+ and all(isinstance(x, str) for x in rename)
236
+ ):
237
+ return [cast("tuple[str, str]", rename)]
238
+ return list(cast("Sequence[tuple[str, str]]", rename))
239
+
240
+
241
+ def _read_raw_text(upstream: str) -> str:
242
+ """Read an already-flat upstream URDF's text verbatim (no XML round-trip).
243
+
244
+ ``file:<path>`` reads that path; ``rd:<module>`` resolves the cached
245
+ upstream ``.urdf`` via ``robot_descriptions`` and reads it directly. The
246
+ upstream must already be flat — raising if any ``${…}`` xacro substitution
247
+ survives, since raw-text mode does no expansion.
248
+ """
249
+ if upstream.startswith("file:"):
250
+ path = Path(upstream[len("file:") :])
251
+ elif upstream.startswith("rd:"):
252
+ import importlib
253
+
254
+ module = importlib.import_module(f"robot_descriptions.{upstream[len('rd:') :]}")
255
+ path = Path(module.URDF_PATH)
256
+ else:
257
+ raise ValueError(f"raw_text mode needs a 'file:' or 'rd:' upstream, got {upstream!r}")
258
+ # newline="" disables universal-newline translation so an upstream that uses
259
+ # CRLF (e.g. gr1) is preserved verbatim rather than silently rewritten to LF.
260
+ # (Path.read_text gained a newline kwarg only in 3.13; open() is the 3.12 path.)
261
+ with path.open("r", encoding="utf-8", newline="") as fh:
262
+ text = fh.read()
263
+ if "${" in text:
264
+ raise ValueError(f"raw_text mode requires a flat URDF; {path} still has '${{…}}' xacro")
265
+ return text
266
+
267
+
268
+ def _with_provenance_raw(xml: str, *, id: str, src: str) -> str:
269
+ """Insert the provenance comment into a raw-text URDF, preserving its bytes.
270
+
271
+ Unlike :func:`_with_provenance` (which rewrites the XML declaration emitted
272
+ by the yourdfpy round-trip), this keeps the upstream document verbatim and
273
+ only inserts the provenance comment on the line *after* the declaration (or
274
+ at the top if there is none), matching the document's own newline style so
275
+ the diff vs upstream is exactly the renamed joint names plus this comment.
276
+ """
277
+ nl = "\r\n" if "\r\n" in xml else "\n"
278
+ header = _PROVENANCE.format(id=id, src=src).rstrip("\n")
279
+ decl_match = re.match(r"^\s*<\?xml[^>]*\?>[ \t]*\r?\n?", xml)
280
+ if decl_match:
281
+ return xml[: decl_match.end()] + header + nl + xml[decl_match.end() :]
282
+ return header + nl + xml
283
+
284
+
285
+ def _with_provenance(xml: str, *, id: str, src: str) -> str:
286
+ """Prepend the provenance comment without breaking XML well-formedness.
287
+
288
+ An XML declaration (``<?xml …?>``) must be byte 0 of the document — a
289
+ comment may not precede it. When the serialized URDF leads with one, the
290
+ provenance comment is inserted on the line *after* the declaration; otherwise
291
+ it goes at the top.
292
+ """
293
+ header = _PROVENANCE.format(id=id, src=src)
294
+ decl_match = re.match(r"^\s*<\?xml[^>]*\?>\s*\n?", xml)
295
+ if decl_match:
296
+ # The provenance comment carries a non-ASCII em-dash, so normalize the
297
+ # declared encoding to UTF-8 (yourdfpy emits ``encoding='ASCII'``) to
298
+ # keep the document well-formed.
299
+ body = xml[decl_match.end() :]
300
+ return '<?xml version="1.0" encoding="utf-8"?>\n' + header + body
301
+ return header + xml
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: openral-cli
3
+ Version: 0.1.0
4
+ Summary: OpenRAL command-line tool
5
+ License: Apache-2.0
6
+ Requires-Python: <3.13,>=3.12
7
+ Requires-Dist: opencv-python-headless<5,>=4.9
8
+ Requires-Dist: openral-core
9
+ Requires-Dist: openral-detect
10
+ Requires-Dist: openral-observability[dashboard]
11
+ Requires-Dist: openral-rskill
12
+ Requires-Dist: openral-sensors
13
+ Requires-Dist: openral-sim
14
+ Requires-Dist: pyudev>=0.24.4
15
+ Requires-Dist: rich>=13.7
16
+ Requires-Dist: typer>=0.12
17
+ Description-Content-Type: text/markdown
18
+
19
+ # openral-cli
20
+
21
+ OpenRAL command-line tool
22
+
23
+ Part of [**OpenRAL**](https://github.com/OpenRAL/openral) — the open Robot
24
+ Abstraction Layer for vision-language-action robotics. This package is one
25
+ member of the OpenRAL Python workspace; see the architecture overview and the
26
+ eight-layer model in the project docs.
27
+
28
+ - **Docs:** https://openral.github.io/openral/
29
+ - **Source:** https://github.com/OpenRAL/openral
30
+ - **License:** Apache-2.0
31
+
32
+ > All OpenRAL workspace packages move in lockstep at `0.1.x` until the first
33
+ > public release.
@@ -0,0 +1,20 @@
1
+ openral_cli/__init__.py,sha256=m3UphUxCyvMi46_Hk0z9TUEHUJsEzvaCupzu_g3uq8Q,128
2
+ openral_cli/_hf_publish.py,sha256=OKAdqRPF0DLutocQAzBACrP3-ND0DxiYqY56xPmBams,4649
3
+ openral_cli/_rskill_doc_validator.py,sha256=Qx4CqXEuMkZY4fLozLMMe4i_qidEe94StJItFPxXiqg,22578
4
+ openral_cli/_rskill_intel.py,sha256=iLyn1qSZdtf2PlECE9xEo8M6lzL9NjH_xJCpffs3gmk,13203
5
+ openral_cli/_rskill_readme.py,sha256=cHmeTYUGxyBWOXXoZtTs1oCCOwcEeG0UVC-_C3yF1g0,9368
6
+ openral_cli/_rskill_scaffolder.py,sha256=bQV53SyIt8lojETTsDnmq4HohBTOFT7-Hfw-EbRcuNw,10122
7
+ openral_cli/autodetect.py,sha256=olVg6UMLvxXvu24HSgQvgNbzEYEEcQ6JIHqo8bbm-1k,16521
8
+ openral_cli/check.py,sha256=Fjfla9BFfQDnrjkl8-_YN4YKpyNLjabhe6dhvaL98v8,14892
9
+ openral_cli/collision.py,sha256=6g-C4FrqnKvLMTKD_J6M-8FywpxZ92mDYiXd-6R6mEw,15685
10
+ openral_cli/dataset.py,sha256=1ayK3DHmjHJn8TXZV2ucaX04ru-gdVcyllZkxMBzgtk,15451
11
+ openral_cli/deploy_sim.py,sha256=Wp1CXJpRBa59HPWkvWUMmEPzOWL2mLS0Jcbtq5I39Y4,125143
12
+ openral_cli/install.py,sha256=HKxuxwIcm6yf9JFwGK6XpCpHkOgPRVnta3MzfHE34Gk,14793
13
+ openral_cli/main.py,sha256=ExGdLcmP2YLZik15K7fux5jojKdq3CXsjbItSR1FxkI,158280
14
+ openral_cli/prompt.py,sha256=FH6mCLRNUDwsobY11vv8CG-j6qwwwXHd26z4jqMj308,4794
15
+ openral_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ openral_cli/robot.py,sha256=3axrbc1lcnynDd4ns5ncUA3gf3ByVA9kuebRgvtgNEo,14069
17
+ openral_cli-0.1.0.dist-info/METADATA,sha256=q3NT7VxZigWtgG0aQgesvI_ZdJzVB7KzAOxwoBvUkQ0,1034
18
+ openral_cli-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
19
+ openral_cli-0.1.0.dist-info/entry_points.txt,sha256=MA7LRsTMvTgm_JLd-6QjU9tRzWaNipFGZlyaFNLGNIU,49
20
+ openral_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ openral = openral_cli.main:app