CLI
Part of the OpenRAL public-symbol inventory. Hand-curated;
(LNN)markers are refreshed bytools/refresh_methods_linenos.py.
python/cli/src/openral_cli/main.py
openral CLI entry point — openral command (typer-based).
_root(ctx)— Top-level callback. Picks the tracesample_ratiofrom_SAMPLE_RATIO_BY_MODE(hardware → 0.1, others → None → ALWAYS_ON), callsconfigure_observability(service_name="ral", sample_ratio=...), and opens acli.commandroot span viacli_command_span(ctx.invoked_subcommand, mode=…), registered on the clickContextso the span closes after the subcommand returns. Mode is derived from_RUN_MODE_BY_SUBCOMMAND(sim→ sim,benchmark→ benchmark,deploy/connect→ hardware). (L377)_RUN_MODE_BY_SUBCOMMAND: dict[str, str]— Subcommand →openral.run.modemapping consumed by_root. (L359)_SAMPLE_RATIO_BY_MODE: dict[str, float]—openral.run.mode→ trace sample ratio. Hardware: 0.1 per ADR-0010 (2026-05-17 amendment). (L371)class CheckResult(NamedTuple)— One row inopenral doctoroutput. (L408) fields:check, status, details_check_python() -> CheckResult(L425)_check_platform() -> CheckResult(L430)_check_openral_core() -> CheckResult(L434)_check_ros2() -> list[CheckResult]— ROS 2 binary, distro, RMW. (L442)_check_colcon() -> CheckResult(L483)_check_gpu() -> list[CheckResult]— One row per GPU. (L488)_check_usb() -> list[CheckResult]— Candidate robot USB serial devices. (L527)_check_just() -> CheckResult(L545)_check_reasoner_llm() -> list[CheckResult]— Returns aReasoner LLMsummary row plus follow-up rows naming each missing variable (MODEL / API_KEY) when PROVIDER is set but the config is incomplete. TCP-probes the Ollama / local endpoint whenBASE_URLis loopback and emits anOllamarow with thejust bootstrap-ollamaremediation hint when unreachable. Never prints the API key value (onlyset/unset). Pure env read, no SDK import, no cloud round-trip. (L582)_REASONER_PROVIDERS_REQUIRING_KEY: frozenset[str]—{anthropic, openrouter}; bareopenai-compatibleis the exception because a local Ollama / llama-server doesn't enforce auth. (L556)_REASONER_PROVIDER_DEFAULT_BASE_URL: dict[str, str]— Provider → default base URL surfaced in the doctor summary whenBASE_URLisn't set. Kept local tomain.pyto avoid forcing the CLI to importopenral_reasoneron every invocation. (L562)_is_local_base_url(url) -> bool— True when host resolves to a loopback name. (L569)_probe_tcp(host, port, *, timeout_s=0.2) -> bool— Fast non-blocking TCP probe used to diagnose the Ollama daemon. (L575)_gather_checks() -> list[CheckResult](L691)doctor(--json)— Diagnose host: Python, OS, ROS 2, GPU, USB. Delegates GPU enumeration toprobe_gpus. (L714)detect(--output, --report, --dds-timeout, --include, --no-write, --yes)— Probe + assemble + write a complete RobotDescriptionrobot.yaml. (replaces the oldral init)_render_detection_summary(detection)— Print per-probe Rich table foropenral detect.connect(--robot, --port)— Open HAL, read state, disconnect._connect_so100(port: str) -> Nonecalibrate_camera(--sensor, --topic, --chessboard-size, --square-size, --dry-run)— Run ROS 2camera_calibration.skill_install(HUB_ID, --revision, --force, --non-commercial, --yes)— Download rSkill, validate, register. An org-lessHUB_ID(no/) fails fast with anOpenRAL/<name>suggestion +rskill searchhint instead of a raw Hub 404; a 404 on a qualified id appends the same search hint.rskill_search(QUERY?, --kind, --role, --embodiment, --license, --limit, --json)— Search the OpenRAL HF Hub org (HfApi.list_models(author="OpenRAL", search=QUERY)) for installable rSkills (ADR-0055 D4). Each hit'srskill.yamlis fetched + validated; repos without a valid manifest are skipped (count surfaced), survivors filtered client-side by the facet flags and rendered as a paste-ablerepo_idtable._load_hub_rskill_manifest(repo_id) -> RSkillManifest | None— Fetch + validate one Hub repo'srskill.yaml;None(skip) when absent or invalid._rskill_matches_filters(m, *, kind, role, embodiment, license_) -> bool— Whether a manifest passes every non-emptyrskill searchfacet filter._render_rskill_search_results(rows, skipped, query) -> None— Print therskill searchtable or the no-results notice.skill_list(--json)— List installed rSkills.skill_check(rskill_id?, --robot, --rskills-dir, --json)— Two modes. With a positional id, resolves it viaload_rskill_manifestand renders a per-section breakdown viacheck_single_rskill. Without an id, falls back to the legacy walk-all path (check_installed_rskills).--rskills-dirdefaults torskills/and is silently skipped when the directory does not exist. Exits 1 on any blocking failure.rskill_new(ID, --out-dir, --owner, --license, --embodiment-tag, --family, --from-hf, --yes, --overwrite)— Scaffold a new local rSkill fromrskills/template/via_rskill_scaffolder.scaffold_rskill. Three modes: (1)--from-hf <repo>introspects the Hub config to auto-fill policy_id / chunk_size / sensors / state_contract / aliases / weights_uri; (2)--family <act|smolvla|pi05|xvla|diffusion>overlays family-aware defaults; (3) interactive prompts for any missing flag (skipped under--yes). (L1616)_resolve_or_prompt(value, *, prompt, default, skip_prompt) -> str— Drives the owner / license / embodiment prompts only when the flag was not provided and--yesis off._resolve_family_and_patch(*, family, from_hf, yes) -> tuple[RSkillFamily | None, RSkillPatch | None]— Resolves--family/--from-hfinto a family + manifest patch forscaffold_rskill. Prompts for family in interactive mode; bails non-zero with a clear message when--from-hfintrospection fails or--familyis unrecognized._display_license_banner(name, license_value, version, con) -> Nonesensor_list(--vendor, --modality, --kind, --json)— List entries in sensor catalog. (L1912)sensor_show(SENSOR_ID, --name, --parent-frame, --json)— Resolve catalog entry to aSensorSpec/SensorBundle. (L2003)benchmark_report(--rskills-dir, --json)— Aggregaterskills/*/eval/*.jsonbenchmark blocks into a rich-table or JSON dump. Validates every JSON againstRSkillEvalResult.--rskills-dirdefaults torskills/. (L2630)benchmark_run(--suite, --rskill, --out, --device, --save-dir, --benchmarks-dir, --n-episodes, --dry-run, --update-manifest/--no-update-manifest, --dashboard, --dashboard-port)— Resolve--suite(built-in id or path) to a barelist[BenchmarkScene]viaopenral_core.load_benchmark_suite+raise_on_invalid_suite(ADR-0042), parse the rSkill reference from--rskill, dispatch toopenral_sim.run_benchmark(scenes, vla, suite_id=<id>), and write a validatedRSkillEvalResultJSON.--n-episodesoverrides everyBenchmarkScene.n_episodesin the suite for smoke runs (mirrorsbenchmark scene --n-episodes). Default output path isrskills/<rskill-dir>/eval/<suite_id>.json. With--update-manifest(default on), also writesavg_success_rateback into the manifest'sbenchmarks.<suite_id>field viaupdate_rskill_benchmarks. ADR-0009 PR D. (L2096)benchmark_scene(--config, --rskill, --out, --device, --save-dir, --n-episodes, --view/--no-view, --dry-run, --update-manifest/--no-update-manifest, --dashboard, --dashboard-port)— Single-scene sibling ofbenchmark_run(ADR-0009 + scene-hierarchy refactor).--view/--no-view(default unset = headless) mirrorssim run --viewfor parity — opens a livemujoco.viewerper episode; threaded intorun_benchmark_scene(view=…). Strictly accepts aBenchmarkSceneYAML viaload_scene_strict(DeployScene/SimScene rejected with a redirect), optionally overridesn_episodesfor smoke runs, dispatches toopenral_sim.run_benchmark_scene, writesrskills/<dir>/eval/scene_<scene_id>.json, and surgically updates the rSkill manifest'sbenchmarks.<scene_id>field.--dry-runshort-circuits before rSkill resolution so it never touches the Hub. (L2369)_default_benchmark_scene_out_path(vla_spec, scene) -> Path— Mirrors_default_benchmark_out_pathbut for single-scene JSONs; thescene_prefix distinguishes per-scene outputs from multi-task suite outputs under the same rSkill directory. (L2613)deploy(--config, --max-ticks, --rate-hz)— Sibling ofopenral sim runfor hardware deployments (ADR-0010 PR G). Loads aRobotEnvironmentYAML, callsopenral_runner.build_runner, drives the skill lifecycle (configure / activate / deactivate / shutdown) aroundrunner.activate / run / deactivate, prints a Rich summary table (n_ticks, mean / p99 timings, budget violations, trace id). CLI overrides for--max-ticksand--rate-hz. Heavy runtime deps (lerobot, opencv) are deferred soopenral --helpstays sub-second.deploy sim(--config, --robot, --dashboard-port, --foxglove/--no-foxglove, --foxglove-port, --reset-to-pose-service, --hal, --dry-run)— Boot the full ROS graph (dashboard + C++ safety_kernel + reasoner + prompt_router + runtime + HAL) against a digital-twin HAL by shardingros2 launch openral_rskill_ros sim_e2e.launch.py(one generic launch — no per-robot launch files).--foxglove(default off, ADR-0059) also spawns the read-onlyfoxglove_bridgelive-scene surface onws://127.0.0.1:<foxglove-port>(view-only — cannot actuate; seepackages/openral_foxglove_bringup). Loads aDeploySceneYAML (mirrorsopenral sim run --config); picks the HAL package/executable/node-name from_ROBOT_HAL_REGISTRY[robot_id]; asserts the registered HAL'ssupported_robot_namesmatches the manifest'snamefield (mismatch fails loud at resolution time). No envelope YAML is written or read: the launch'sOpaqueFunctionloadsrobot.yaml, callsopenral_safety.envelope_loader.compute_intersection(robot, skill=None), and forwards eachEnvelopeIntersectionfield as a ROS parameter on the kernel node (the C++ kernel grew a parameter-based loader in ADR-0020 PR-K alongside the legacy file path).--robotoverrides the YAML'srobot_id.--hal key=value(repeatable) overrides per-robot HAL defaults (JSON-parsed where possible). No--rskillflag: the reasoner picks the active rSkill dynamically from the in-treerskills/palette aton_configure.--dry-runprints the resolved argv without writing the HAL params temp file. Defined inopenral_cli.deploy_sim.dashboard(--host, --port, --log-level, --inprocess)—openral dashboard(ADR-0017, closes #44). Boot a live debug pane that doubles as an OTLP/HTTP receiver on the same port; lazy-importsopenral_observability.dashboard.run_dashboardsoopenral --helpstays sub-second.--inprocesstakes a single shell-quoted string (shlex-tokenised) and spawns it as a child workload withOTEL_EXPORTER_OTLP_ENDPOINT+OTEL_EXPORTER_OTLP_PROTOCOL=http/protobufpre-set so a one-keystroke demo doesn't need a second shell. Defaults: bind127.0.0.1:4318(OTLP/HTTP standard; was8000until issue #132), uvicorn atwarning. Works without Jaeger/Tempo (the dashboard is its own receiver). Inverse path:openral sim run --dashboard(sim spawns the dashboard).replay(BAG, --trace, --frame, --dataset-root, --dashboard, --out)— ADR-0018 F7 + ISSUE-109. Read a.mcapfile or rosbag2 directory, join with OTel spans from--dashboard(http://host:port), and emit a chronological JSON timeline keyed bytrace_id.--frame <repo_id>/<episode>/<frame>(with--dataset-root) pivots from a written LeRobotDataset frame: it resolves that frame'strace_idviaopenral_dataset.read_frame_traceand uses it as the join key (mutually exclusive with--trace).--outwrites to a file; otherwise prints to stdout. Bag-only when--dashboardis omitted._resolve_frame_trace_id(frame_spec, dataset_root) -> str— Parse a<repo_id>/<episode>/<frame>spec (rsplit('/', 2); repo_id keeps its own slash) and return that frame's storedtrace_idviaread_frame_trace.typer.Exit(2)on a malformed spec, missing frame, or a frame with no trace.record(--out, --profile, --storage, --extra-topic, --extra-regex, --dry-run)— ADR-0018 F7. Spawnros2 bag recordwithslim(default) orfullprofile presets;--dry-runprints the composed argv without forking.profile session ACTION (start | stop | view) (--output, --name)— ADR-0018 F9. Drive an LTTng session for the realtime hot path; surfacesLttngSessionErrorcleanly whenlttngis missing on PATH. SetOPENRAL_ROS2_TRACING=1on the agent process to emit tracepoints; without the gate every tracepoint is a no-op._resolve_benchmark_suite(suite: str, benchmarks_dir: Path) -> tuple[list[BenchmarkScene], str]— Accept either a built-in id (looked up atbenchmarks/<id>.yaml) or a direct YAML path; raisetyper.BadParameterlisting catalogue entries on a typo._parse_rskill_cli_arg(raw)— Parse--rskill <ref>into aVLASpec. Accepts bare names (smolvla-libero), paths (rskills/smolvla-libero), or HF repo ids; validates viaopenral_rskill.loader._validate_skill_refsoVLASpec.weights_urirejects explicit URI schemes. The adapter id is read from the manifest'smodel_family. Raisestyper.BadParameteron an invalid scheme or empty input._summarize_results(results: dict[str, object]) -> str— Headline-line picker for free-formresultsblocks (*_avg→ numeric → status fallback). (L2720)_path_completer(text: str, state: int) -> str | None— Stdlibreadline-shaped Tab completer wired into_run_repl. Globstext*(with~expansion), adds trailing/to directory matches, and rewrites a leading$HOMEback to~so a user who typed~/fookeeps their literal tilde. Lets the REPL complete filesystem paths aftersim run --config,--rskill rskills/, etc. Installed by_run_repltogether withreadline.set_completer_delims(" \t\n=;|&><")(shell-shaped delims so/,.,-,~are not word boundaries) andparse_and_bind("tab: complete")(or libedit's equivalent on macOS). ReturnsNonepast the last match per readline's state contract.render_banner(version_str: str, *, width: int | None = None) -> RenderableType— Build the interactive-REPL welcome box as a richPanel(Claude-Code style), content-sized (expand=False) and with the layout chosen to fitwidth: a white-bordered rounded box withOPENRAL v<version>inline (left-aligned) in the top border. Wide (>= _WIDE_MIN): two columns split by aMINIMAL-box vertical divider — left holds the logo mark beside the OPENRAL wordmark (_logo_wordmark/_identity) over the tagline + capability strip; right holds the community links (_LINKS) above aRuleabove the quick-start commands (_COMMANDS). Narrow: a single stacked column keeping every section, with the logo beside the wordmark while it fits (>= _SIDE_BY_SIDE_MIN) and stacked above it below that. The box is sized to its content rather than stretched to the terminal, so it stays compact on wide terminals (already-printed output cannot reflow if the window is later dragged narrower than the box). Returns a renderable (not a print) so it exports to plain text in tests independent of TTY/colour state._logo_wordmark(*, stacked: bool) -> RenderableType— Logo mark + OPENRAL wordmark side by side (vertical-middle grid) or stacked._identity(*, stacked: bool) -> RenderableType—_logo_wordmarkabove the tagline + capability strip._kv_grid(rows: tuple[tuple[str, str], ...], key_style: str) -> Table— Borderless two-columnkey valuegrid (styled key, dim value) used for the links and commands cells._LOGO_ART: str/_WORDMARK_ART: str— White (single-weight, no gradient) OpenRAL logo mark — a 6-row block icon (horns flaring out and down into a rounded head, eyes below) matching the wordmark height — and the OPENRAL block-letter wordmark._LINKS/_COMMANDS: tuple[tuple[str, str], ...]— Community links (Discord / GitHub / Hugging Face / Website) and quick-start commands (doctor,rskill search,help,exit) shown in the right cell._WIDE_MIN/_SIDE_BY_SIDE_MIN: int— Minimum terminal columns each content-sized layout occupies (127 / 82), measured from the rendered box so the richest layout that fits is chosen and the box never overflows._cli_version() -> str— Best-effortopenral-clipackage version for the banner title; suppressesPackageNotFoundErrorand falls back to"0.0.0"._print_banner() -> None— Printrender_banner(_cli_version(), width=console.width)to the REPLconsoleat_run_replstartup, sizing to the live terminal.
python/cli/src/openral_cli/_hf_publish.py
ADR-0019 PR5 — shared HF Hub publishing helpers, de-duped from tools/rskill_publisher.py.
resolve_token(token_arg: str | None = None) -> str— Resolve the HF token from arg →HF_TOKEN→HUGGINGFACE_HUB_TOKEN. RaisesROSConfigErrorwith actionable hint when missing. (L55)ensure_private(api: HfApi, repo_id: str, *, repo_type: str = "model") -> None— Re-fetch repo metadata and raiseROSConfigErrorif the repo is public. Critical safety gate;repo_typesupports"model"/"dataset"/"space". (L88)IGNORE_PATTERNS: Final[list[str]]— Glob patterns excluded from every upload (.env,__pycache__,*.key, etc.). (L43)
python/cli/src/openral_cli/_rskill_doc_validator.py
rSkill README + manifest publish-readiness validator (CLAUDE.md §6.4 publish gate).
Hard gate consumed by tools/rskill_publisher.py (and printed in dry-run mode): refuses publish when the README is missing / too short / missing canonical sections / contains template sentinels, or when the manifest still has the template-default description, lacks both paper_url and source_repo, or carries a TEMPLATE substring in name / weights_uri / source_repo. Supports single-hop delegation via the <!-- openral:rskill-readme-delegates-to: <path> --> marker so RLDX-1-family stubs can share one canonical README.
class DocValidationIssue(BaseModel)— one problem record:severity("error"/"warning"),field(readme.section.License/manifest.description/ …),message.class DocValidationReport(BaseModel)—skill_dir+manifest_name+issues;.is_valid/.errors/.warningsderived properties.validate_rskill_docs(skill_dir: Path, manifest: RSkillManifest) -> DocValidationReport— Public entry point; composes README and manifest checks into one report. (L~175)_validate_readme(skill_dir) -> list[DocValidationIssue]— Presence, min-length, required-sections-via-heading, placeholder-sentinel scan. Honors single-hop delegation._resolve_delegation(skill_dir, body) -> (list[str] | None, list[DocValidationIssue])— Resolve aopenral:rskill-readme-delegates-tomarker; rejects double-hop and missing-target._validate_manifest_content(manifest) -> list[DocValidationIssue]— Description / provenance (paper_url∨source_repo) /name/weights_uri/source_repochecks beyond what the Pydantic schema catches.format_report(report) -> str— Human-readable summary used by the publisher dry-run.- Module-level constants:
README_REQUIRED_SECTIONS,PLACEHOLDER_SENTINELS,PLACEHOLDER_MANIFEST_DESCRIPTION_MARKERS,DELEGATION_MARKER_NAME.
python/cli/src/openral_cli/deploy_sim.py
openral deploy sim — boot the full ROS graph against a digital-twin HAL via ros2 launch openral_rskill_ros sim_e2e.launch.py (one generic launch, no --rskill).
deploy_sim_command(--config, --robot, --dashboard-port, --foxglove/--no-foxglove, --foxglove-port, --reset-to-pose-service, --hal, --dry-run)— Typer callback registered underdeploy sim. Forwards--foxglove/--foxglove-port(ADR-0059) intoresolve_launch_invocation→enable_foxglove:=/foxglove_port:=onsim_e2e.launch.py. Callsresolve_launch_invocation, runsassert_ros2_packages_discoverableonopenral_rskill_ros+ the resolved HAL package (catches an un-sourced overlay or stale build beforeros2 launchreturns its terse "Package not found" error), runs_preflight_palette_deps(advisory: warn-and-drop rSkills blocked on missing extras and boot the rest,OPENRAL_AUTO_INSTALL_DEPS=1auto-installs, hard-fail only when the palette would be empty), writes the synthesised envelope + HAL params to twotempfile.NamedTemporaryFiles (lifetime = subprocess), substitutes their paths into the argv template, and runsros2 launchvia_run_launchwithPATHprepended to.venv/binso spawned#!/usr/bin/env python3node shebangs resolve to the venv interpreter (the only one that processes editable.pthfiles)._run_launchspawns the launch in its own session (start_new_session=True) and tears it down in three escalating stages on exit so nothing orphans onto/tf_staticor the GPU: (1) forward SIGINT/SIGTERM to the launch's group for ros2 launch's graceful shutdown; (2)_terminate_launch_groupSIGKILLs the group after a grace; (3)_kill_orphan_openral_graph_processessweeps by argv signature — the bulletproof backstop, since ros2 launch spawns nodes in their own process groups (and the rldx sidecar in its own session) thatkillpgcan't reach directly. The same signature reaper runs at startup via_reap_orphans_with_log, matching_cmdline_is_openral_graph_processagainst_ORPHAN_GRAPH_NEEDLES(now covering thestatic_transform_publisher/robot_state_publisherTF chain + therldx-sidecar, closing the stale-z=0.4/tf_staticpoisoning hole that made the rldx-rc365 arm reach 40 cm high). Cleanup unlinks both temp files infinally.assert_ros2_packages_discoverable(packages, *, prefix_lookup=_ros2_pkg_prefix) -> None— RaiseROSConfigErrorlisting everypkgros2 pkg prefixcannot resolve. Catches the most commonopenral deploy simfailure (operator sourced/opt/ros/jazzy/setup.bashbut not the OpenRAL workspace overlay; or the localjust ros2-buildis stale and never installed the requestedopenral_hal_<X>).prefix_lookupis injectable so unit tests drive the path with a deterministic fake (process-boundary fake, CLAUDE.md §1.11). Error message names the missing packages, points atjust ros2-build && source install/setup.bash, and disambiguates "is the package calledrskillnow?" — it is not._preflight_palette_deps(*, repo_root, robot_yaml, commercial_deployment=False) -> None— Advisory (not a gate) policy-extras preflight. MirrorsReasonerNode._maybe_seed_palette_from_search_paths: globs<repo_root>/rskills/*/rskill.yaml, runsbuild_tool_paletteagainst the robot'sRobotCapabilities, then probes each capability-matching manifest'smodel_familyviapolicy_deps.can_import_policy_family. The palette is robot-WIDE (afranka_pandaconfig matches six model families), so a partially-installed venv is the common case and the reasoner already drops unimportable rSkills aton_configure— this mirrors that contract instead of blocking. When ≥1 matching skill is blocked: withOPENRAL_AUTO_INSTALL_DEPS=1(the unattended-consent env varopenral_sim._assets/_depsalso honor) it runsjust sync --all-packages --group …(union ofmodel_family_install_groups,cwd=repo_root), re-probes, and continues — a non-zero sync surfaces its exit code; on a TTY it insteadtyper.confirms the same install. Otherwise (declined / non-TTY / no install cmd / partial install) it drops the blocked skills from the palette and proceeds, printing the enable hint — UNLESS that leaves the palette empty, in which case ittyper.Exit(1)s with thejust sync --all-packages --group …command (--all-packagesis required so the workspace members survive the install — without it the nextros2 launchfails withNo module named 'openral_core'). Silent return when nothing blocked, when no rSkills are installed, or when the palette is empty for capability/role/license reasons (the reasoner's domain to surface aton_configure).run_launch_invocation(invocation, *, run_preflight=True) -> int— Shared shelling path fordeploy sim+deploy run(ADR-0032): runs_preflight_palette_deps, writes the ephemeral HAL params YAML, exportsOPENRAL_VENV_SITE+ prepends the venv bin/PATH, and shells the resolved argv via_run_launch; returns the launch exit code. Defined inopenral_cli.deploy_sim.resolve_launch_invocation(*, config=None, robot_override, dashboard_port, reset_to_pose_service, approach_skill_id=None, hal_param_overrides=None, hal_mode="sim", enable_slam=None, enable_nav2=None, enable_octomap=None, enable_dashboard=True) -> LaunchInvocation— Pure resolver shared bydeploy sim(hal_mode="sim", aDeploySceneconfig) anddeploy run(hal_mode="real",robot_overridefrom aRobotEnvironment,config=None). ADR-0032: real mode skips the sim digital-twin / scene-attach injections (so the so100/so101 node opens its serial bus) and forwardshal_mode="real"tomanifest_drivennodes, and fast-fails withROSCapabilityMismatchwhen a simulation-only robot (hal.realnull) is asked for real mode. LoadsDeployScene→robot_idonly whenconfigis given; otherwiserobot_overrideis required. Looks uprobots/<robot_id>/robot.yamland validates it viaRobotDescription.validate_for_e2e_pipeline(), asserts the loaded manifest'snameis in_ROBOT_HAL_REGISTRY[robot_id].supported_robot_names(mismatch raisesROSConfigError), merges per-robot HAL defaults with operator overrides, and builds the["ros2", "launch", "openral_rskill_ros", "sim_e2e.launch.py", …]argv template.enable_slamauto-on tracksdescription.capabilities.has_lidar;enable_nav2auto-on tracksenable_slam(ADR-0025 amendment: lidar-equipped mobile robots auto-co-enable both);enable_octomap(ADR-0030) auto-on when the manifest declares a depthSensorSpec— forwardsenable_octomap:=true, which brings up octomap_server + the openral_octomap_bridge and turns on the kernel's capsule-vs-voxel world-collision check. The template carries aHAL_PARAMS_FILE_PLACEHOLDERsentinel the dispatcher fills in after writing the temp HAL params YAML. No envelope file path exists — the launch readsrobot_yamland feeds the kernel via ROS params._parse_hal_overrides(raw: list[str] | None) -> dict[str, object]— Parse repeated--hal key=valueflags. Values are JSON-decoded where possible (so--hal viewer_enabled=falseparses as bool); fall back to raw string for--hal port=/dev/ttyUSB0.class LaunchInvocation— Frozen dataclass:robot_id,robot_yaml,envelope_payload(the synthesised dict),hal: _HalSpec,hal_params,hal_mode(ADR-0036 —"sim"|"real"; forwarded ashal_mode:=…so the reasoner's action-mode palette gate matches the HAL the graph brings up),reset_to_pose_service,approach_skill_id(ADR-0053 — MoveIt approach rSkill URI, e.g.rskills/rskill-moveit-joints; when set the runner dispatches it retargeted at each skill'sstarting_poseand aborts the goal on a plan failure, vs. the best-effort snap. Empty default = legacy snap; forwarded asapproach_skill_id:=…only when non-empty),argv_template. Object-detector fields:enable_object_detector,object_detector_onnx,object_detector_manifest(ADR-0037 2026-06-09 — a kind:detector manifest path;runtime:pytorchselects the LocateAnything VLM sidecar and auto-enables the leg with no ONNX file),object_detector_query(initial open-vocab query),object_detector_locators(ADR-0056 — tuple of resolved on-demand locator manifest paths; the launch builds one namespaced/openral/perception/<alias>/locate_in_viewlifecycle node per entry; forwarded comma-joined asobject_detector_locators:=…only when non-empty).resolve_launch_invocationgainsobject_detector_onnx/object_detector_manifest/object_detector_query/object_detector_locatorskwargs forwarded asobject_detector_*:=…launch args. The object-detection leg is on by default (--object-detector/--no-object-detector, default on); when no explicit--object-detector-manifest/--object-detector-onnxis given the default backend resolves to the open-vocabomdet-turbo-indoormanifest via_omdet_runtime_available(), gracefully falling back to the in-tree RT-DETR COCO ONNX when the omdet deps are absent, and auto-downgrading the leg to off (with a console notice) when neither backend is available. On-demand locators default toomdet-turbo-locator(when omdet deps import) and accept a repeatable--object-detector-locator <manifest|alias>(LocateAnything opt-in).openral deploy simexposes--object-detector-manifest/--object-detector-query/--object-detector-locator. Reward-monitor (ADR-0057):resolve_launch_invocationalso gainsenable_reward_monitor/reward_monitor_manifest/reward_monitor_task, forwarded asenable_reward_monitor:=…(+ the optional overrides only when set);openral deploy simexposes--enable-reward-monitor/--no-enable-reward-monitor/--reward-monitor-manifest(akind:rewardYAML;weights_urimay behf://org/repoorlocal:///abs/dir) /--reward-monitor-task. When on, the launch brings upreward_monitor_nodeparallel to the VLA and sets the reasoner'stask_progress_available:=true._omdet_runtime_available() -> bool— Probe (importlib.util.find_specfortransformers+timm) deciding whetherresolve_launch_invocation's default object detector is the open-vocabomdet-turbo-indoorcontinuous backend or the in-tree RT-DETR COCO ONNX fallback. Patched in unit tests to exercise both branches deterministically. Defined inopenral_cli.deploy_sim.class _HalSpec— Frozen dataclass withpackage,executable,node_name,default_params, and opt-in flagssupports_sim_env_yaml(scene-attach injection),manifest_driven(ADR-0032 — nodes built viamake_lifecycle_main_from_manifest; the resolver forwardsrobot_yaml+hal_mode), andbare_twin_sim(issue #191 — a manifest arm that builds its OWN sim MJCF rather than scene-attaching: so100/so101 derive a bareMujocoArmHALtwin, openarm composes a tabletop MJCF fromscene_defaults.composition; suppresses thesim_env_yamlinjection). Every robot is nowmanifest_driven— Phase 3 migrated the last two bespoke nodes (panda_mobile, openarm), so noopenral_hal_*package ships a node subclass. One entry per supported robot lives in_ROBOT_HAL_REGISTRY._ROBOT_HAL_REGISTRY: dict[str, _HalSpec]— Robot-id → HAL spawn descriptor (openarm→(openral_hal_openarm, lifecycle_node.py, openral_hal_openarm, {viewer_enabled, robot_lift_z, robot_forward_x, scene_white_background}),so100_follower→(openral_hal_so100, lifecycle_node, openral_hal_so100, {port})). Add a new robot by extending this dict; no per-robot launch file is needed becausesim_e2e.launch.pyis generic.
python/cli/src/openral_cli/dataset.py
ADR-0019 PR5 — openral dataset Typer app + push subcommand.
dataset_app: typer.Typer— Public Typer group mounted underopenralatname="dataset". (L43)push_command(root, *, repo_id, yes, dry_run, token, commit_message) -> None—openral dataset push <root>. Readsmeta/info.json, resolves the repo_id, runs the PII consent prompt (skippable via--yesorOPENRAL_DATASET_CONSENT=1), thenHfApi.create_repo(private=True) → ensure_private → upload_folder. (L296)from_bag_command(bag_path, *, robot, output, repo_id, license, fps) -> None—openral dataset from-bag <bag.mcap> --robot robots/<x>/robot.yaml --output <ds-root>. CallsRosbag2ToLeRobotConverter.from_bag; produces a v3 dataset ready foropenral dataset push. (L61)_read_info_json(root: Path) -> dict[str, object]— Parsemeta/info.json; raisesROSConfigErroron missing / malformed file. (L187)_camera_keys_from_info(info) -> list[str]— Extractobservation.images.*feature keys for the consent prompt's camera disclosure. (L214)_confirm_consent(repo_id, root, info, yes) -> None— Render the PII / regulatory disclosure Panel, accept--yes/ env-var overrides, refuse non-TTY without override. (L222)_resolve_repo_id(root, info, cli_repo_id) -> str— CLI override → info.json → error. Validates<org>/<name>format. (L268)
python/cli/src/openral_cli/collision.py
ADR-0030 — openral collision lower|check Typer app: offline URDF/SRDF → manifest self-collision model. Defers openral_safety.urdf_lowering.lower_robot (yourdfpy/trimesh) so openral --help stays fast.
collision_app: typer.Typer— Public Typer group mounted underopenralatname="collision".lower(robot, *, write, acm_only, geometry_only) -> None—openral collision lower --robot <yaml>. Prints a unified diff of the regeneratedcollision_geometry/allowed_collision_pairsblock(s) and mutates the manifest only with--write(a regenerated ACM is a safety input — never silent; CLAUDE.md §3).--acm-only/--geometry-onlyare mutually exclusive.check(robot, *, all_robots, acm_only, geometry_only) -> None—openral collision check (--robot <yaml> | --all). Exits 1 if any manifest drifts from its lowered model (the fleet-wide ACM drift guard).splice_collision_blocks(text, *, geometry_block=None, acm_block=None) -> str— Replace only the two collision blocks in a manifest's text, preserving every other line + comment (absorbs the block's own header comment so repeated lowers stay idempotent).render_blocks(model) -> tuple[str, str]— Render aLoweredCollisionModelto(geometry_block, acm_block)YAML text with a generated-provenance header; floats rounded to 4 dp for a stable diff.inject_joint_fk(text, joint_fk) -> str— Injectorigin_xyz/origin_rpy/axis_xyzinto the named manifest joint blocks (matched by name), dropping any pre-existing FK lines. Used when onboarding a robot onto self-collision (the kernel needs joint FK to place capsules). Idempotent; preserves all other lines/comments.
python/cli/src/openral_cli/robot.py
ADR-0058 — openral robot vendor-urdf <id>: expand an upstream xacro to a flat, committed URDF so end users need no xacro tooling at runtime. Defers robot_descriptions/xacrodoc/yourdfpy inside the command so openral --help stays fast.
vendor_urdf(robot_id, *, upstream, out_dir, rename=None, raw_text=False) -> Path— Loadupstream(rd:<robot_descriptions module>→ xacro expanded via xacrodoc, orfile:<path>→ already-flat URDF), serialize to a flat URDF, applyrename(a(pattern, repl)pair or a sequence of them,re.subin order; default per-robot from_RENAME/_RAW_RENAMES), and write<robot_id>.urdfwith an ADR-0058 provenance header after the XML declaration.raw_text=Truecopies an already-flat upstream URDF's text verbatim (no yourdfpy round-trip) and applies the renames to the raw XML, preservingpackage:/// relative mesh paths and CRLF byte-for-byte — used for joint-name-only patches (h1 strips_joint). Returns the written path.
python/cli/src/openral_cli/_rskill_scaffolder.py
Scaffolder helper backing openral rskill new and tools/rskill_scaffolder.py.
Copies rskills/template/ into a target directory, rewrites manifest sentinels (name / license / embodiment_tags / weights_uri / source_repo) plus README sentinels, then re-validates the result through RSkillManifest.from_yaml + rSkill.from_yaml so a malformed scaffold fails at scaffold-time. Partial scaffolds are cleaned up on validation failure.
_default_template_dir() -> Path— Walks parents of this file until arskills/template/directory is found. (L41)scaffold_rskill(rskill_id, *, out_dir, owner, license_, embodiment_tag, family=None, patch=None, template_dir=None, overwrite=False) -> Path— Public entry point; copies the template, applies family defaults + introspection patch, rewrites placeholders, re-validates the manifest. (L60)_rewrite_manifest(manifest_path, *, rskill_id, owner, license_, embodiment_tag, family, patch) -> None— Layered rewrite: (1) template baseline → (2) family defaults from_rskill_intel.family_defaults→ (3) explicitpatch→ (4) CLI rename/license/embodiment_tags. (L173)_apply_patch(raw, patch) -> None— Overlay patch keys onto the raw manifest dict; aNonevalue removes the key (so e.g. ACT family clearsmin_vram_gb)._rewrite_readme(readme_path, *, rskill_id, owner) -> None— ReplaceTEMPLATE_ORG/TEMPLATE_IDsentinels inREADME.md._validate_scaffold(scaffold_dir) -> None— Round-trip throughRSkillManifest.from_yaml+rSkill.from_yaml.
python/cli/src/openral_cli/_rskill_intel.py
Per-family scaffold defaults + HF Hub config introspection for openral rskill new.
RSkillFamily—Literal["act", "smolvla", "pi05", "xvla", "diffusion"]; mirrors the keys ofopenral_sim.registry.POLICIESminus the mock entries.RSKILL_FAMILIES: tuple[RSkillFamily, ...]— Tuple form of the above for menu rendering / validation.RSkillPatch(TypedDict, total=False)— Subset of manifest fields the scaffolder overlays:model_family,policy_id,chunk_size,quantization,latency_budget,min_vram_gb,n_action_steps,image_preprocessing,state_contract,sensors_required,weights_uri,source_repo,description.family_defaults(family: RSkillFamily) -> RSkillPatch— Per-family manifest baseline mirroring the in-tree reference manifests (act-aloha,smolvla-libero,pi05-libero-nf4,xvla-libero,diffusion-pusht). (L67)introspect_hf(repo_id, *, default_family=None) -> tuple[RSkillFamily, RSkillPatch]— Fetchesconfig.jsonfrom a HF Hub repo, infers the policy family fromtype, and derives chunk_size / sensors / state_contract / image_preprocessing.aliases / weights_uri frominput_features. (L168)_fetch_hf_json(repo_id, filename) -> Any—huggingface_hub.hf_hub_download+json.load; raisesValueErroron network / parse error._sensors_from_input_features(input_features) -> list[dict]— OneSensorRequirement-shaped dict perobservation.images.*feature, with min_width / min_height pulled off the CHW shape._state_dim_from_input_features(input_features) -> int | None— Readsobservation.state.shape[0]._aliases_from_input_features(input_features) -> dict[str, str]— Pairscamera<N>source keys with the checkpoint's image-feature names; empty when names already match.
python/cli/src/openral_cli/autodetect.py
USB VID/PID enumeration and DDS topic discovery for openral detect.
class UsbDevice(NamedTuple)— A USB serial device on the host. (L45) fields:port, vid, pid, descriptionclass KnownDevice(NamedTuple)— A known USB adapter/controller from the VID/PID table. (L61) fields:chip, driver_hint, embodiment_tag, bh_robot_typeclass UsbMatch(NamedTuple)— A detected device matched against the table. (L79) fields:device, knownclass DdsTopic(NamedTuple)— A ROS 2 topic observed during DDS scan. (L91) fields:name, type_name_enumerate_linux_pyudev() -> list[UsbDevice](L217)_enumerate_macos_system_profiler() -> list[UsbDevice](L250)_enumerate_glob_fallback() -> list[UsbDevice]—/dev/tty*glob. (L295)enumerate_usb_devices() -> list[UsbDevice]— OS-routed enumerator. (L318)match_known_devices(devices) -> list[UsbMatch](L350)scan_dds_topics(timeout_s=5.0) -> list[DdsTopic]—ros2 topic list -t. (L377)infer_robot_from_topics(topics) -> str | None(L425)