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). (L389)_RUN_MODE_BY_SUBCOMMAND: dict[str, str]— Subcommand →openral.run.modemapping consumed by_root. (L370)_SAMPLE_RATIO_BY_MODE: dict[str, float]—openral.run.mode→ trace sample ratio. Hardware: 0.1 (2026-05-17 amendment). (L383)class CheckResult(NamedTuple)— One row inopenral doctoroutput. (L420) fields:check, status, details_check_python() -> CheckResult(L437)_check_platform() -> CheckResult(L442)_check_openral_core() -> CheckResult(L446)_check_ros2() -> list[CheckResult]— ROS 2 binary, distro, RMW. A missingros2binary isabsent(non-fatal), notmissing: Tier-0 ships no ROS 2 by design, soopenral doctormust still exit 0 there. (L454)_check_colcon() -> CheckResult—absent(non-fatal) when unavailable, for the same Tier-0 reason as_check_ros2. (L502)_check_gpu() -> list[CheckResult]— One row per GPU. (L511)_check_usb() -> list[CheckResult]— Candidate robot USB serial devices. (L619)_check_just() -> CheckResult(L637)_check_reasoner_llm() -> list[CheckResult]— Model-first doctor dispatcher. ReadsOPENRAL_REASONER_MODEL, resolvesREASONER_MODELS, and reports the effective dialect / hosting / endpoint / key status; missing auth gets a follow-up row. An uncurated model fails untilOPENRAL_REASONER_ENDPOINTis a named endpoint (or a URL plusOPENRAL_REASONER_DIALECT), then reportswarn(untested). The provider-first contract was removed in 0.3.0 and is no longer read. Never prints the API key value. (L850)_check_reasoner_model(model_key) -> list[CheckResult]— Resolve one curated/uncurated model into doctor rows; delegates loopback checks to_reasoner_endpoint_probe_row. Resolves a namedOPENRAL_REASONER_ENDPOINTthroughopenral_core.REASONER_ENDPOINT_PRESETSfirst — the SAME table the factory uses (the old hand-mirrored local copy drifted twice: 2fe732a, 131a489), still without importing the optional reasoner package. (L733)_reasoner_endpoint_probe_row(label, base_url, *, managed, autostart) -> CheckResult— Generic loopback probe. Down managed-local + autostart isinfo; managed with autostart disabled and every BYO-local endpoint arewarn. (L696)_cosmos_autostart_enabled() -> bool— mirrors the reasoner client'sOPENRAL_COSMOS3_AUTOSTARTparsing (falsy spellings0/false/no/off); kept local so doctor never imports the optionally-installed reasoner package. (L652)_is_local_base_url(url) -> bool— True when host resolves to a loopback name. (L663)_probe_tcp(host, port, *, timeout_s=0.2) -> bool— Fast non-blocking TCP probe used to diagnose the Ollama daemon. (L669)_gather_checks() -> list[CheckResult](L872)doctor(--json)— Diagnose host: Python, OS, ROS 2, GPU, USB. Delegates GPU enumeration toprobe_gpus. (L905)detect(--output, --robot/--as, --report, --dds-timeout, --include, --no-write, --deployment, --yes)— Always-interactive customrobot.yamlbuilder (replaces the oldral init); there is no--interactive/-iflag any more — probing + writing a manifest is unconditionally interactive, only--no-writeshort-circuits to non-interactive probe-only inspection (CI-safe:enrich_cameras=Truereverse-looks-up detected cameras from the sensor catalog instead of prompting, then returns before any prompt).--report <path>is an orthogonal flag that dumps the rawDetectionReportJSON to disk and works with or without--no-write; used alone (without--no-write) it still falls through into the full interactive builder (name prompt → camera wizard → joint/safety gate → writesrobot.yaml) — a headless/CI probe needs--no-write(optionally combined with--report), not--reportby itself.--robot/--asforces the canonical base manifest (e.g.so100, since a bare Feetech plug-in defaults to the SO-101) over USB/DDS inference — the canonical manifest is a template, never the output. Flow:_prompt_robot_nameasks for a custom rig name (default: canonical name), settingRobotDescription.nameand — when--outputis untouched — the default output pathrobots/<name>/robot.yaml;_run_camera_binding_wizardthen walks every detected camera (V4L2 + RealSense + Orbbec) with a thumbnail grab and routes it into either the robot manifest'ssensors:(canonical name reuse or a new robot sensor) or the DeployScene's workcellsensors:(w:<name>) — everything but the sensor list is inherited verbatim from the canonical manifest (joints, URDF/MJCF, safety, capabilities, compute);_maybe_customize_limitsthen gates an opt-in per-joint position/velocity/effort + safety-scalar override (Ndefault inherits canonical verbatim);_relocate_file_assetsrewritesfile:URDF/SRDF refs repo-root-relative when the output dir differs from the canonical rig's dir.--deployment <path>also scaffolds aDeployScene(robot_id + the wizard's workcellsensors:; safety unset → manifest envelope; no rSkill pinned; ahal:binding seeded from the robot manifest'shal.parameters.defaults+ lerobot calibration placeholders (id/calibration_dir), with theportoverridden by the USB probe's detected device when one matched, so the scaffold is a self-containeddeploy runtarget once the operator commits the calibration). No effect without--deployment._prompt_robot_name(default) -> str—typer.promptfor the custom rig name; blank input keepsdefault._run_camera_binding_wizard(canonical, detection) -> tuple[list[SensorSpec], list[SensorSpec]]—(robot_sensors, workcell_sensors); the routing prompt loop above over_iter_wizard_cameras(V4L2 + RealSense + Orbbec). Enter skips a device, dropping any canonical sensor never bound._iter_wizard_cameras(detection) -> list[tuple[str, str, str]]—(device_path, label, serial)per bindable camera; V4L2 keys ondevice_path, RealSense/Orbbec key onserial(emptydevice_path)._grab_camera_thumbnail(device_path, out_dir) -> Path | None— Best-effort one-frame JPEG grab (opencv) so the operator can see which physical camera a/dev/video*node is._maybe_customize_limits(description) -> RobotDescription— Opt-in gate"Customize joint limits & safety envelope?"(default No); declining inherits the canonicalJointSpec/SafetySpecfields verbatim, accepting walks each present (non-None) joint field and the four safety scalars with Enter-to-keep-default prompts._relocate_file_assets(description, *, canonical_dir, output_path) -> RobotDescription— Rewritesassets.urdf.ref/mjcf/srdffile:<rel>refs to repo-root-relative whenoutput_pathis outsidecanonical_dir, so the custom manifest's URDF/SRDF still resolve after relocation._write_deploy_scene_scaffold(path, description, sensor_specs, *, detection, assume_yes) -> None— Validates throughDeployScenethen writes the scaffold + review banner.sensor_specshere are always workcell (non-robot) cameras from the wizard. Overrides the seeded HALportwith the one probed indetection.usb.matches(when present) instead of the manifest's stale default._render_detection_summary(detection)— Print per-probe Rich table foropenral detect.connect(--robot, --port)— Open HAL, read state, disconnect.--robotacceptsso100/so101(both drive the sharedSO100FollowerHAL)._connect_so_follower(label: str, port: str) -> None— Connect an SO-100/SO-101 follower arm viaSO100FollowerHAL, read one state, disconnect.calibrate_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. 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). (L2257)_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. (L2553)sensor_show(SENSOR_ID, --name, --parent-frame, --json)— Resolve catalog entry to aSensorSpec/SensorBundle. (L2644)benchmark_report(--rskills-dir, --json)— Aggregaterskills/*/eval/*.jsonbenchmark blocks into a rich-table or JSON dump. Validates every JSON againstRSkillEvalResult.--rskills-dirdefaults torskills/. (L3436)benchmark_run(--suite, --rskill, --out, --device, --save-dir, --benchmarks-dir, --task, --n-episodes, --dry-run, --update-manifest/--no-update-manifest, --video/--no-video, --video-dir, --dashboard, --dashboard-port)— Resolve--suite(built-in id or path) to a barelist[BenchmarkScene]viaopenral_core.load_benchmark_suite+raise_on_invalid_suite, parse the rSkill reference from--rskill, dispatch toopenral_sim.run_benchmark(scenes, vla, suite_id=<id>), and write a validatedRSkillEvalResultJSON.--video(default on) records one world MP4 per episode into--video-dir(default<eval JSON dir>/videos/<suite_id>) throughrun_benchmark(video_dir=…);--no-videorestores allocation-light runs.--task <id>runs a single explicit task from the suite (e.g.libero_spatial/3,maniskill3/PushCube-v1), erroring if it is not in the suite; without itrun_benchmarkauto-filters the suite to the rSkill'sevaluated_tasks(run only what it supports, skip + log the rest).--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. (L2737)benchmark_scene(--config, --rskill, --out, --device, --save-dir, --save-video, --video-size, --n-episodes, --view/--no-view, --dry-run, --update-manifest/--no-update-manifest, --dashboard, --dashboard-port)— Single-scene sibling ofbenchmark_run(scene-hierarchy refactor).--view/--no-view(default unset = headless) mirrorssim run --viewfor parity — opens a livemujoco.viewerper episode; threaded intorun_benchmark_scene(view=…).--save-video DIR(with--video-size, default 1024) setsrun_benchmark_scene(record_video=True)and writes a clean single-view world MP4 per episode (<task>_<rskill>_<success|fail>.mp4) + avideos.jsonmanifest viaopenral_sim._website_video.write_world_videos— for website hero clips; the task slug prevents benchmark scenes sharing a backend from overwriting each other; pair with--n-episodes 1. 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-runresolves the rSkill (manifest only — no weights) and appliescheck_benchmark_task_compatibility, exiting 1 on a task-mismatched pairing; built-in mock policies (placeholder/zero/random) have no manifest and keep the as-typed echo. (L3111)_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. (L3419)deploy sim(--config, --robot, --dashboard-port, --foxglove/--no-foxglove, --foxglove-port, --reset-to-pose-service, --hal, --memory-dir, --initial-task, --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) also spawns the read-onlyfoxglove_bridgelive-scene surface onws://127.0.0.1:<foxglove-port>(view-only — cannot actuate; seepackages/openral_foxglove_bringup).--initial-task(optional string) is the single operator goal delivered to the reasoner at startup; when omitted no startup prompt is set and the reasoner idles until a manualopenral promptor dashboard prompt arrives. 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 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.--memory-dirpoints at a deploy memory bundle directory;_memory_bundle_launch_argsderivesmemory_md_path:=<dir>/MEMORY.md(always — the reasoner creates it on the firstmemory_write) plusspatial_memory_path:=<dir>/scene_graph.jsonandmap_path:=<dir>/map.yaml(each only when the file is present), so a deploy boots with the self-maintained memory + the 3D scene graph (recall_object) + the 2D occupancy grid (nav2map_server). The dir must exist (raisesROSConfigErrorotherwise);--memory-diroverrides theDeployScene.memory_dirfield.--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(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)— 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)— Spawnros2 bag recordwithslim(default) orfullprofile presets;--dry-runprints the composed argv without forking.profile session ACTION (start | stop | view) (--output, --name)— 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._print_benchmark_run_plan(scenes, *, suite_id, vla_spec) -> None—benchmark run --dry-run's plan printer. Applies the samefilter_scenes_for_skill/_manifest_for_filterpassrun_benchmarkapplies, so the printed plan is the plan that would execute: exits 1 when the rSkill'sevaluated_taskscover no suite task (previously dry-ran clean, then raisedROSCapabilityMismatchon the real run), and prints a skip note for partial coverage._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.behavior_serve(--rskill, --task, --instruction, --host, --port, --device, --state-dim, --action-dim)— Serve one rSkill through the official BEHAVIOR Challenge WebSocket policy protocol. Defaults to the R1Pro observation/action contract; OmniGibson remains out-of-process and owns task loading, metrics, and videos._summarize_results(results: dict[str, object]) -> str— Headline-line picker for free-formresultsblocks (*_avg→ numeric → status fallback). (L3526)_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
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/_rskill_readme.py
Derive an HF model-card README from an rSkill manifest (manifest = single source of truth, CLAUDE.md §1.3).
The manifest-derived model-card front-matter (license / license_name, library_name, pipeline_tag, tags, base_model + base_model_relation, datasets, inference) is emitted uniformly so every published repo — private OR public — carries a consistent, discoverable card. tools/rskill_publisher.py builds it at publish time (README excluded from the folder upload, then rebuilt); the human-written prose body is preserved verbatim. Hand-curated extras already on the in-tree README's front-matter (extra tags, a curated datasets) are unioned in ("best of both"); derived fields win on conflict.
build_rskill_frontmatter(manifest: RSkillManifest) -> dict[str, Any]— Pure, deterministic derivation of the model-card front-matter dict (no network).base_modelcomes fromsource_repo(NF4 repos self-host weights viaweights_uri);base_model_relationisquantizedwhen a quantization block is present, elsefinetune. (L~86)render_frontmatter(fm: dict[str, Any]) -> str— Render the dict as a----fenced YAML block, stable field order.build_rskill_readme(manifest: RSkillManifest, body: str) -> str— Full README = merged front-matter + prose body; strips any existing front-matter frombodyand merges its curated extras. Idempotent. (L~150)
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, --initial-task, --dry-run)— Typer callback registered underdeploy sim. Forwards--foxglove/--foxglove-portintoresolve_launch_invocation→enable_foxglove:=/foxglove_port:=onsim_e2e.launch.py.--initial-task(optional) passes the operator goal to the reasoner at startup; when omitted, no startup prompt is set and the reasoner idles until a manualopenral promptor dashboard prompt arrives. The deploy-posture flags (--enable-slam/nav2/octomap/octomap-kernel-check,--object-detector+ manifest/onnx/query/locators,--enable-reward-monitor+ manifest/task,--enable-critic,--spatial-memory-ingest,--approach-skill-id) are tri-state: unset falls through to the scene's committedruntime:block (DeployRuntime), then to the documented auto (precedence CLI > scene > auto). 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), then (when the reward monitor is active)_preflight_reward_vram_fitover_detect_gpu_free_vram_gb()to fail fast before ROS when no VLA can co-reside with the reward model, 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(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; plus thereward_monitor_node/ros_image_detector_node/critic_producer_nodegraph nodes that leaked when graceful shutdown overran the grace window). 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._repo_root_from(start) -> Path(L480) — Resolve the repo root holding therobots/+rskills/manifest trees, in order:$OPENRAL_REPO_ROOT(explicit override, raisesROSConfigErrorwhen it is not a checkout) → walk up fromstart(source/editable install) → walk up from the cwd (wheel install run from inside a checkout). The cwd step is what makesdeploy simwork off a published wheel at all: the manifest trees are repo data, not package data, so a site-packages__file__has norobots/ancestor and thestartwalk can never succeed there. Distinct from the packaging fixopenral install rosgot:robots/andrskills/are user-editable fixture trees a deploy reads, not fixed assets that could be vendored into the wheel. Sibling of the softeropenral_sim.policies.robots._find_repo_root(returnsNonefor the same wheel case rather than raising, because it runs at import time)._preflight_scene_assets(config) -> None(L2101) — Provision the scene's sim backend BEFOREros2 launch. Backend-agnostic: readsscene.idfrom theDeploySceneYAML and runs whateverSCENES.provision(scene_id)returns, so which scenes need preflighting is each backend's ownprovision=declaration rather than a table here (RoboCasa's ~11 GB fork + asset pull, the Isaac / RoboTwin sidecar venvs, the RLBench / BEHAVIOR / VLABench externally-provisioned refusals). Without it that work runs inside the HAL'son_configure— a callbacktools/lifecycle_autostart.pybounds at 300 s, with the nav2 palette re-seed helper waiting only 120 s alongside it; on a fresh machine both time out, the HAL never reaches ACTIVE, andnavigate_to_poseis silently dropped from the reasoner palette. Running it here also puts each backend's license banner +typer.confirm()on a reachable TTY, and turns a "run ./setup.sh" refusal into a legible pre-launch message instead of a lifecycle timeout. No-op whenconfigis None, whenopenral_simis absent, or for scenes with no hook (LIBERO / MetaWorld / ManiSkill3 / the native MuJoCo scenes — pip installs only). Idempotent (every hook short-circuits on an install probe or readiness sentinel) and advisory — a failure warns and continues, since the backend retries aton_configureand raises the typed error with upstream stderr. Called afterassert_ros2_packages_discoverableso a missing overlay fails in a second rather than after a multi-GB download._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 viapolicy_deps.can_import_policy_manifest— manifest-keyed, so sidecar variants probe their actual wire deps (the BEHAVIOR organizer GR00T rSkill probes zmq+msgpack, not the in-processgr00timport set). 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 ofmanifest_install_groups—behavior-grootfor the BEHAVIOR sidecar rSkill, else the family 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). Also the single pre-launch hook that knows what the reasoner may dispatch, so it calls_apply_palette_head_camover the capability-matched set once the palette is known to be non-empty._apply_palette_head_cam(matched) -> bool— SetsOPENRAL_ROBOCASA_HEAD_CAM=1when any capability-matched rSkill declaresobservation.images.headinsensors_required; returns whether it did. The RoboCasa backend synthesises that forward nav camera (openral_sim.backends.robocasa.render_head_view) behind the env gate so manipulation runs skip the second offscreen render — but nothing set it, soscenes/deploy/robocasa_navigate.yaml+ the InternVLA-N1 VLN rSkill (the pairing the nav scene exists for) rendered noheadframes at all unless the operator knew to export it (issue #91). Derived from the palette rather than per-scene config, so any future nav skill/scene works with no bookkeeping. Reads off the capability-matched set, not the post-drop dispatchable one — a nav skill blocked on missing extras costs one wasted render per step, cheaper than one that boots blind. An operator-set value always wins,=0included. Called from_preflight_palette_deps, so bothdeploy simanddeploy runinherit it; the launch env isos.environ.copy()(_prepare_launch_env), which carries it into the HAL process that renders. Defined inopenral_cli.deploy_sim.run_launch_invocation(invocation, *, run_preflight=True) -> int— Shared shelling path fordeploy sim+deploy run. Underrun_preflightit runs the SAME preflight sequence, in the same order, as thedeploy simcommand body:assert_ros2_packages_discoverable(overlay/stale-build check) →_reap_orphans_with_log→_preflight_palette_deps→ the_preflight_reward_vram_fitVLA↔reward pair check wheninvocation.enable_reward_monitoris set. The first two used to run on the sim path only, so a crasheddeploy runleft orphan graph processes holding GPU memory and/dev/shm/fastrtps_*lockfiles until someone happened to rundeploy sim, and the nextdeploy runfailed with a terseFailed init_port fastrtps_port7000instead of reaping them.run_preflight=Falseremains a full bypass. Then 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._detect_gpu_vram_gb(field) -> float— Torch-freenvidia-smi --query-gpu=<field>probe (MiB→GiB) for GPU 0,0.0on any failure (no nvidia-smi / no GPU / parse error). Deliberate mirror ofopenral_reasoner_ros.reasoner_node._detect_gpu_total_vram_gb(the CLI cannot import that ROS-package-local helper without pulling in rclpy).0.0makes the deploy preflight skip (budget unreadable → defer to the reasoner's runtime check). Defined inopenral_cli.deploy_sim._detect_gpu_free_vram_gb() -> float—_detect_gpu_vram_gb("memory.free"). The launch preflight runs before any OpenRAL model is loaded, so free VRAM is the honest budget the VLA+reward pair must fit; budgeting against total would greenlight a pair that OOMs the moment both load (a desktop compositor or a sibling worktree can hold GBs the pair never sees). Defined inopenral_cli.deploy_sim._capability_matched_manifests(repo_root, description, *, commercial_deployment=False) -> list[RSkillManifest]— Loads everyrskills/*/rskill.yamland returns those the reasoner palette would admit (build_tool_palettecapability/role/license filter; unloadable manifests skipped).deploy simdoes not preselect a VLA, so reward resolution + the VRAM preflight reason over this set (the "VLA known at launch" is the palette). Defined inopenral_cli.deploy_sim._resolve_reward_monitor_manifest(*, repo_root, description, explicit_manifest) -> str— Reward-model resolution. An explicit--reward-monitor-manifestwins; else derives the reward model from the capability-matched VLA palette'sreward_rskill_name(consensus → thatkind:rewardrSkill's in-tree manifest path), defaulting torobometer-4b(_DEFAULT_REWARD_RSKILL_DIR) when no VLA names one, the named model is not in-tree, or the palette VLAs disagree (the latter two warned). Defined inopenral_cli.deploy_sim._preflight_reward_vram_fit(*, repo_root, description, reward_manifest_path, gpu_budget_gb, commercial_deployment=False) -> None— Pre-LAUNCH VLA↔reward VRAM gate (deploy sim/run). For each capability-matched VLA, runsopenral_core.assert_vla_reward_fits(vla, reward, gpu_budget_gb), bucketing into fit / OOM (ROSGPUMemoryError) / undeclared-min_vram_gb(ROSConfigError). Mirrors_preflight_palette_deps' contract: advisory per-VLA (the reasoner's runtime_refuse_unfittable_vladrops a non-fitting VLA anyway) and a HARDtyper.Exit(1)only when NO matched VLA can co-reside with the reward model (the deploy could dispatch nothing) — failing fast before ROS instead of a mid-run OOM / blind VLA. Skipped whengpu_budget_gb <= 0(budget unreadable), no reward model is active, or there is no VLA palette. 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, initial_task_prompt=None) -> LaunchInvocation— Pure resolver shared bydeploy simanddeploy run; both load aDeploySceneconfig, with real mode skipping sim scene-attach injections and forwardinghal_mode="real". First merges the scene's committedruntime:block (DeployRuntime) field-by-field under the CLI flags (precedence CLI > scene > auto; relative manifest/onnx paths that exist next to the scene YAML resolve against its dir), so a committed workcell pins its whole deploy posture with zero flags. The resolver forwardsworkcell_json:=...when the scene declaressafetyorextra_allowed_collision_pairs, so the launch applies tighten-only deploy safety and additive ACM before configuring the kernel. When the scene declares ahal:binding, itsdefaults(port + lerobotid/calibration_dir+calibrate_on_connect) are merged intohal_paramsabove the robot-manifest defaults and below any--haloverride (precedence--hal> scenehal>robot.yaml); a relativecalibration_dirresolves against the scene file's dir. -
deploy_run(--config, --robot, --hal, --dashboard/--no-dashboard, --dashboard-port, --enable-reward-monitor/--no-enable-reward-monitor, --reward-monitor-manifest, --dry-run)— Typer callback registered underdeploy run: resolves theDeploySceneand shells the samesim_e2e.launch.pygraph withhal_mode:=real.--enable-reward-monitor(parity withdeploy sim) brings up the Robometer reward monitor parallel to the VLA; the manifest auto-pairs from the VLA palette'sreward_rskill_namewith--reward-monitor-manifestas the operator override. Tri-state: unset falls through to the scene'sruntime:block (DeployRuntime, shared resolver — a committed scene likeso101_bench.yamlpins its whole posture with no flags), then off. Defined inopenral_cli.main. deploy_validate(--config, --robot, --hal)— Pre-run readiness check foropenral deploy run; touches no hardware and shells noros2 launch. Loads theDeployScene, resolves it viaresolve_launch_invocation(hal_mode="real")(so a sim-only robot / name mismatch / unknown HAL / missing manifest fails here), then checks the runtime-required inputs a real run needs before the launch — the exact gaps that otherwise fail late at HAL configure / sensor leg: a serialportis declared (+ device exists now → WARN if not); a serial HAL withcalibrate_on_connect=falsehas anid+calibration_dirand the<calibration_dir>/<id>.jsonfile exists (missing → ERROR; the "has no calibration registered" failure mode); and each scene sensor has adeploy_binding(+ any/dev/*device exists). ERROR (missing committed data) exits non-zero; WARN (device just not attached now) does not. Registered asopenral deploy validate._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("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(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(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(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: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.LaunchInvocationcarriesenable_reward_monitor: bool+ the resolvedreward_monitor_manifest: str— when the monitor is on and--reward-monitor-manifestis not pinned,resolve_launch_invocationderives the reward model from the capability-matched VLA palette'sreward_rskill_name(the pairing the reasoner honours) via_resolve_reward_monitor_manifest, defaulting torobometer-4bwhen no VLA names one or they disagree (warned). The resolved path is forwarded asreward_monitor_manifest:=…(deployrunleaves the monitor off, so it is empty there). Scene VLM:resolve_launch_invocationalso gainsenable_scene_vlm/scene_vlm_manifest, forwarded asenable_scene_vlm:=…(+scene_vlm_manifest:=…only when the leg is on AND a path is pinned —ros2 launchrejects an emptyname:=);openral deploy simexposes--enable-scene-vlm/--no-enable-scene-vlm. When on, the launch brings upopenral_perception_ros/scene_vlm_nodeco-active with the graph (one cache entry per manifest RGB camera, so the LLM picks a viewpoint per query) and sets the reasoner'sscene_query_available:=true, which is what actually offers the read-onlyquery_scenetool. Before this the param had no setter anywhere in the repo, so the tool, its dispatch handler and theqwen35-4b-nf4rSkill were all unreachable fromdeploy sim. Read-only. Critic producer:resolve_launch_invocationalso gainsenable_critic, forwarded asenable_critic:=…;openral deploy simexposes--enable-critic/--no-enable-critic. When on, the launch brings upopenral_reasoner_ros/critic_producer_nodeco-active with the graph — it watches the generic/openral/critic/scoretopic any reward model publishes and emits a Tier-CFailureTriggeron/openral/failure/critic(which the reasoner already maps to a forced Tier-C tick) when a critic stalls. Advisory-only. Startup prompt:initial_task_prompt: str(empty = no startup prompt) — set from--initial-task(the single operator goal the reasoner decomposes viadecompose_mission); forwarded asinitial_task_prompt:=…on the argv when non-empty._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(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 whose cameras are spliced in by the generic camera rig fromsensors[].sim_placement(issue #88 — no scene composer), while openarm composes a tabletop MJCF from the DeployScene'scompositionforwarded asscene_composition_json(the scene owns its arena, not the robot manifest); 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
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
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, emit_cumotion=None) -> 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.--emit-cumotion <path>additionally renders a cuRobo robot-config from the same lowered geometry (cumotion_config.render_cumotion_config) — dry-run prints it,--writewrites the file._lower(robot_path, *, acm_only, geometry_only) -> tuple[RobotDescription, LoweredCollisionModel]— Shared loader: parse the manifest and lower vialower_robot_auto(the provenance dispatcher). Used by_lowered_textand the--emit-cumotionpath.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/check.py
openral check: static, host-independent validation of the declarative robot/skill/scene set. Imports only openral_core (no hardware probe); complements the host-specific openral rskill check. Manifest JSON-Schema emission lives in tools/schema_export.py (CI-gated), not here.
class CheckFinding(BaseModel)— One problem:rule(robot_parse/rskill_parse/scene_parse/asset_ref/scene_robot_id/embodiment_reach/frames),severity(error/warning),target,message.class GraphCheckReport(BaseModel)— Typedopenral check --jsonpayload:generated_at,n_robots/n_rskills/n_scenes,findings;.errors/.warnings/.okproperties.check_description_graph(repo_root, *, resolve_remote_assets=False) -> GraphCheckReport— Parse everyrobots/*/robot.yaml,rskills/*/rskill.yaml, andscenes/{deploy,sim,benchmark}/*.yaml; resolvefile:/ros2://asset refs (rd:/gym_aloha:/openarm:/menagerie:skipped unlessresolve_remote_assets); check every scenerobot_idresolves to a robot dir, every rSkill's embodiment tags reach an in-repo robot (warning), and every sensorparent_frameis a declared frame (warning). ReusesRobotDescription.from_yaml/resolve_asset— no parallel validation logic.check_command(--repo-root, --strict, --resolve-remote-assets, --json)— Theopenral checkleaf command; exit 1 on any error (and on warnings under--strict). Registered inmain.pyviaapp.command("check").
python/cli/src/openral_cli/robot.py
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 a provenance header after the XML declaration. Forrd:upstreams the round-trip's cache-absolutized mesh paths are rewritten to portablerd:<module>:<path-relative-to-repository>refs (_portable_mesh_refs) — committed absolute paths resolve on exactly one machine and once broke fleet collision lowering in CI.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-int8,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](L228)_enumerate_macos_system_profiler() -> list[UsbDevice](L264)_enumerate_glob_fallback() -> list[UsbDevice]—/dev/tty*glob. (L309)enumerate_usb_devices() -> list[UsbDevice]— OS-routed enumerator. (L332)match_known_devices(devices) -> list[UsbMatch](L364)scan_dds_topics(timeout_s=5.0) -> list[DdsTopic]—ros2 topic list -t. (L391)infer_robot_from_topics(topics) -> str | None(L439)