Skip to content

CLI

Part of the OpenRAL public-symbol inventory. Hand-curated; (LNN) markers are refreshed by tools/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 trace sample_ratio from _SAMPLE_RATIO_BY_MODE (hardware → 0.1, others → None → ALWAYS_ON), calls configure_observability(service_name="ral", sample_ratio=...), and opens a cli.command root span via cli_command_span(ctx.invoked_subcommand, mode=…), registered on the click Context so 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.mode mapping 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 in openral doctor output. (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 missing ros2 binary is absent (non-fatal), not missing: Tier-0 ships no ROS 2 by design, so openral doctor must still exit 0 there. (L454)
  • _check_colcon() -> CheckResultabsent (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. Reads OPENRAL_REASONER_MODEL, resolves REASONER_MODELS, and reports the effective dialect / hosting / endpoint / key status; missing auth gets a follow-up row. An uncurated model fails until OPENRAL_REASONER_ENDPOINT is a named endpoint (or a URL plus OPENRAL_REASONER_DIALECT), then reports warn (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 named OPENRAL_REASONER_ENDPOINT through openral_core.REASONER_ENDPOINT_PRESETS first — 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 is info; managed with autostart disabled and every BYO-local endpoint are warn. (L696)
  • _cosmos_autostart_enabled() -> bool — mirrors the reasoner client's OPENRAL_COSMOS3_AUTOSTART parsing (falsy spellings 0/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 to probe_gpus. (L905)
  • detect(--output, --robot/--as, --report, --dds-timeout, --include, --no-write, --deployment, --yes)Always-interactive custom robot.yaml builder (replaces the old ral init); there is no --interactive/-i flag any more — probing + writing a manifest is unconditionally interactive, only --no-write short-circuits to non-interactive probe-only inspection (CI-safe: enrich_cameras=True reverse-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 raw DetectionReport JSON 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 → writes robot.yaml) — a headless/CI probe needs --no-write (optionally combined with --report), not --report by itself. --robot/--as forces 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_name asks for a custom rig name (default: canonical name), setting RobotDescription.name and — when --output is untouched — the default output path robots/<name>/robot.yaml; _run_camera_binding_wizard then walks every detected camera (V4L2 + RealSense + Orbbec) with a thumbnail grab and routes it into either the robot manifest's sensors: (canonical name reuse or a new robot sensor) or the DeployScene's workcell sensors: (w:<name>) — everything but the sensor list is inherited verbatim from the canonical manifest (joints, URDF/MJCF, safety, capabilities, compute); _maybe_customize_limits then gates an opt-in per-joint position/velocity/effort + safety-scalar override (N default inherits canonical verbatim); _relocate_file_assets rewrites file: URDF/SRDF refs repo-root-relative when the output dir differs from the canonical rig's dir. --deployment <path> also scaffolds a DeployScene (robot_id + the wizard's workcell sensors:; safety unset → manifest envelope; no rSkill pinned; a hal: binding seeded from the robot manifest's hal.parameters.defaults + lerobot calibration placeholders (id/calibration_dir), with the port overridden by the USB probe's detected device when one matched, so the scaffold is a self-contained deploy run target once the operator commits the calibration). No effect without --deployment.
  • _prompt_robot_name(default) -> strtyper.prompt for the custom rig name; blank input keeps default.
  • _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 on device_path, RealSense/Orbbec key on serial (empty device_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 canonical JointSpec/SafetySpec fields 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 — Rewrites assets.urdf.ref/mjcf/srdf file:<rel> refs to repo-root-relative when output_path is outside canonical_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 through DeployScene then writes the scaffold + review banner. sensor_specs here are always workcell (non-robot) cameras from the wizard. Overrides the seeded HAL port with the one probed in detection.usb.matches (when present) instead of the manifest's stale default.
  • _render_detection_summary(detection) — Print per-probe Rich table for openral detect.
  • connect(--robot, --port) — Open HAL, read state, disconnect. --robot accepts so100 / so101 (both drive the shared SO100FollowerHAL).
  • _connect_so_follower(label: str, port: str) -> None — Connect an SO-100/SO-101 follower arm via SO100FollowerHAL, read one state, disconnect.
  • calibrate_camera(--sensor, --topic, --chessboard-size, --square-size, --dry-run) — Run ROS 2 camera_calibration.
  • skill_install(HUB_ID, --revision, --force, --non-commercial, --yes) — Download rSkill, validate, register. An org-less HUB_ID (no /) fails fast with an OpenRAL/<name> suggestion + rskill search hint 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's rskill.yaml is fetched + validated; repos without a valid manifest are skipped (count surfaced), survivors filtered client-side by the facet flags and rendered as a paste-able repo_id table.
  • _load_hub_rskill_manifest(repo_id) -> RSkillManifest | None — Fetch + validate one Hub repo's rskill.yaml; None (skip) when absent or invalid.
  • _rskill_matches_filters(m, *, kind, role, embodiment, license_) -> bool — Whether a manifest passes every non-empty rskill search facet filter.
  • _render_rskill_search_results(rows, skipped, query) -> None — Print the rskill search table 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 via load_rskill_manifest and renders a per-section breakdown via check_single_rskill. Without an id, falls back to the legacy walk-all path (check_installed_rskills). --rskills-dir defaults to rskills/ 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 from rskills/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 --yes is off.
  • _resolve_family_and_patch(*, family, from_hf, yes) -> tuple[RSkillFamily | None, RSkillPatch | None] — Resolves --family / --from-hf into a family + manifest patch for scaffold_rskill. Prompts for family in interactive mode; bails non-zero with a clear message when --from-hf introspection fails or --family is unrecognized.
  • _display_license_banner(name, license_value, version, con) -> None
  • sensor_list(--vendor, --modality, --kind, --json) — List entries in sensor catalog. (L2553)
  • sensor_show(SENSOR_ID, --name, --parent-frame, --json) — Resolve catalog entry to a SensorSpec/SensorBundle. (L2644)
  • benchmark_report(--rskills-dir, --json) — Aggregate rskills/*/eval/*.json benchmark blocks into a rich-table or JSON dump. Validates every JSON against RSkillEvalResult. --rskills-dir defaults to rskills/. (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 bare list[BenchmarkScene] via openral_core.load_benchmark_suite + raise_on_invalid_suite, parse the rSkill reference from --rskill, dispatch to openral_sim.run_benchmark(scenes, vla, suite_id=<id>), and write a validated RSkillEvalResult JSON. --video (default on) records one world MP4 per episode into --video-dir (default <eval JSON dir>/videos/<suite_id>) through run_benchmark(video_dir=…); --no-video restores 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 it run_benchmark auto-filters the suite to the rSkill's evaluated_tasks (run only what it supports, skip + log the rest). --n-episodes overrides every BenchmarkScene.n_episodes in the suite for smoke runs (mirrors benchmark scene --n-episodes). Default output path is rskills/<rskill-dir>/eval/<suite_id>.json. With --update-manifest (default on), also writes avg_success_rate back into the manifest's benchmarks.<suite_id> field via update_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 of benchmark_run (scene-hierarchy refactor). --view/--no-view (default unset = headless) mirrors sim run --view for parity — opens a live mujoco.viewer per episode; threaded into run_benchmark_scene(view=…). --save-video DIR (with --video-size, default 1024) sets run_benchmark_scene(record_video=True) and writes a clean single-view world MP4 per episode (<task>_<rskill>_<success|fail>.mp4) + a videos.json manifest via openral_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 a BenchmarkScene YAML via load_scene_strict (DeployScene/SimScene rejected with a redirect), optionally overrides n_episodes for smoke runs, dispatches to openral_sim.run_benchmark_scene, writes rskills/<dir>/eval/scene_<scene_id>.json, and surgically updates the rSkill manifest's benchmarks.<scene_id> field. --dry-run resolves the rSkill (manifest only — no weights) and applies check_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_path but for single-scene JSONs; the scene_ 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 sharding ros2 launch openral_rskill_ros sim_e2e.launch.py (one generic launch — no per-robot launch files). --foxglove (default off) also spawns the read-only foxglove_bridge live-scene surface on ws://127.0.0.1:<foxglove-port> (view-only — cannot actuate; see packages/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 manual openral prompt or dashboard prompt arrives. Loads a DeployScene YAML (mirrors openral sim run --config); picks the HAL package/executable/node-name from _ROBOT_HAL_REGISTRY[robot_id]; asserts the registered HAL's supported_robot_names matches the manifest's name field (mismatch fails loud at resolution time). No envelope YAML is written or read: the launch's OpaqueFunction loads robot.yaml, calls openral_safety.envelope_loader.compute_intersection(robot, skill=None), and forwards each EnvelopeIntersection field as a ROS parameter on the kernel node (the C++ kernel grew a parameter-based loader alongside the legacy file path). --robot overrides the YAML's robot_id. --hal key=value (repeatable) overrides per-robot HAL defaults (JSON-parsed where possible). No --rskill flag: the reasoner picks the active rSkill dynamically from the in-tree rskills/ palette at on_configure. --memory-dir points at a deploy memory bundle directory; _memory_bundle_launch_args derives memory_md_path:=<dir>/MEMORY.md (always — the reasoner creates it on the first memory_write) plus spatial_memory_path:=<dir>/scene_graph.json and map_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 (nav2 map_server). The dir must exist (raises ROSConfigError otherwise); --memory-dir overrides the DeployScene.memory_dir field. --dry-run prints the resolved argv without writing the HAL params temp file. Defined in openral_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-imports openral_observability.dashboard.run_dashboard so openral --help stays sub-second. --inprocess takes a single shell-quoted string (shlex-tokenised) and spawns it as a child workload with OTEL_EXPORTER_OTLP_ENDPOINT + OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf pre-set so a one-keystroke demo doesn't need a second shell. Defaults: bind 127.0.0.1:4318 (OTLP/HTTP standard; was 8000 until issue #132), uvicorn at warning. 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 .mcap file or rosbag2 directory, join with OTel spans from --dashboard (http://host:port), and emit a chronological JSON timeline keyed by trace_id. --frame <repo_id>/<episode>/<frame> (with --dataset-root) pivots from a written LeRobotDataset frame: it resolves that frame's trace_id via openral_dataset.read_frame_trace and uses it as the join key (mutually exclusive with --trace). --out writes to a file; otherwise prints to stdout. Bag-only when --dashboard is 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 stored trace_id via read_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) — Spawn ros2 bag record with slim (default) or full profile presets; --dry-run prints the composed argv without forking.
  • profile session ACTION (start | stop | view) (--output, --name) — Drive an LTTng session for the realtime hot path; surfaces LttngSessionError cleanly when lttng is missing on PATH. Set OPENRAL_ROS2_TRACING=1 on the agent process to emit tracepoints; without the gate every tracepoint is a no-op.
  • _print_benchmark_run_plan(scenes, *, suite_id, vla_spec) -> Nonebenchmark run --dry-run's plan printer. Applies the same filter_scenes_for_skill / _manifest_for_filter pass run_benchmark applies, so the printed plan is the plan that would execute: exits 1 when the rSkill's evaluated_tasks cover no suite task (previously dry-ran clean, then raised ROSCapabilityMismatch on 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 at benchmarks/<id>.yaml) or a direct YAML path; raise typer.BadParameter listing catalogue entries on a typo.
  • _parse_rskill_cli_arg(raw) — Parse --rskill <ref> into a VLASpec. Accepts bare names (smolvla-libero), paths (rskills/smolvla-libero), or HF repo ids; validates via openral_rskill.loader._validate_skill_ref so VLASpec.weights_uri rejects explicit URI schemes. The adapter id is read from the manifest's model_family. Raises typer.BadParameter on 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-form results blocks (*_avg → numeric → status fallback). (L3526)
  • _path_completer(text: str, state: int) -> str | None — Stdlib readline-shaped Tab completer wired into _run_repl. Globs text* (with ~ expansion), adds trailing / to directory matches, and rewrites a leading $HOME back to ~ so a user who typed ~/foo keeps their literal tilde. Lets the REPL complete filesystem paths after sim run --config, --rskill rskills/, etc. Installed by _run_repl together with readline.set_completer_delims(" \t\n=;|&><") (shell-shaped delims so /, ., -, ~ are not word boundaries) and parse_and_bind("tab: complete") (or libedit's equivalent on macOS). Returns None past 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 rich Panel (Claude-Code style), content-sized (expand=False) and with the layout chosen to fit width: a white-bordered rounded box with OPENRAL v<version> inline (left-aligned) in the top border. Wide (>= _WIDE_MIN): two columns split by a MINIMAL-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 a Rule above 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_wordmark above the tagline + capability strip.
  • _kv_grid(rows: tuple[tuple[str, str], ...], key_style: str) -> Table — Borderless two-column key value grid (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-effort openral-cli package version for the banner title; suppresses PackageNotFoundError and falls back to "0.0.0".
  • _print_banner() -> None — Print render_banner(_cli_version(), width=console.width) to the REPL console at _run_repl startup, 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_TOKENHUGGINGFACE_HUB_TOKEN. Raises ROSConfigError with actionable hint when missing. (L55)
  • ensure_private(api: HfApi, repo_id: str, *, repo_type: str = "model") -> None — Re-fetch repo metadata and raise ROSConfigError if the repo is public. Critical safety gate; repo_type supports "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 / .warnings derived 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 a openral:rskill-readme-delegates-to marker; rejects double-hop and missing-target.
  • _validate_manifest_content(manifest) -> list[DocValidationIssue] — Description / provenance (paper_urlsource_repo) / name / weights_uri / source_repo checks 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_model comes from source_repo (NF4 repos self-host weights via weights_uri); base_model_relation is quantized when a quantization block is present, else finetune. (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 from body and 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 under deploy sim. Forwards --foxglove/--foxglove-port into resolve_launch_invocationenable_foxglove:=/foxglove_port:= on sim_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 manual openral prompt or 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 committed runtime: block (DeployRuntime), then to the documented auto (precedence CLI > scene > auto). Calls resolve_launch_invocation, runs assert_ros2_packages_discoverable on openral_rskill_ros + the resolved HAL package (catches an un-sourced overlay or stale build before ros2 launch returns 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=1 auto-installs, hard-fail only when the palette would be empty), then (when the reward monitor is active) _preflight_reward_vram_fit over _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 two tempfile.NamedTemporaryFiles (lifetime = subprocess), substitutes their paths into the argv template, and runs ros2 launch via _run_launch with PATH prepended to .venv/bin so spawned #!/usr/bin/env python3 node shebangs resolve to the venv interpreter (the only one that processes editable .pth files). _run_launch spawns 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_static or the GPU: (1) forward SIGINT/SIGTERM to the launch's group for ros2 launch's graceful shutdown; (2) _terminate_launch_group SIGKILLs the group after a grace; (3) _kill_orphan_openral_graph_processes sweeps by argv signature — the bulletproof backstop, since ros2 launch spawns nodes in their own process groups (and the rldx sidecar in its own session) that killpg can't reach directly. The same signature reaper runs at startup via _reap_orphans_with_log, matching _cmdline_is_openral_graph_process against _ORPHAN_GRAPH_NEEDLES (covering the static_transform_publisher / robot_state_publisher TF chain + the rldx-sidecar, closing the stale-z=0.4 /tf_static poisoning hole that made the rldx-rc365 arm reach 40 cm high; plus the reward_monitor_node / ros_image_detector_node / critic_producer_node graph nodes that leaked when graceful shutdown overran the grace window). Cleanup unlinks both temp files in finally.
  • assert_ros2_packages_discoverable(packages, *, prefix_lookup=_ros2_pkg_prefix) -> None — Raise ROSConfigError listing every pkg ros2 pkg prefix cannot resolve. Catches the most common openral deploy sim failure (operator sourced /opt/ros/jazzy/setup.bash but not the OpenRAL workspace overlay; or the local just ros2-build is stale and never installed the requested openral_hal_<X>). prefix_lookup is 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 at just ros2-build && source install/setup.bash, and disambiguates "is the package called rskill now?" — it is not.
  • _repo_root_from(start) -> Path (L480) — Resolve the repo root holding the robots/ + rskills/ manifest trees, in order: $OPENRAL_REPO_ROOT (explicit override, raises ROSConfigError when it is not a checkout) → walk up from start (source/editable install) → walk up from the cwd (wheel install run from inside a checkout). The cwd step is what makes deploy sim work off a published wheel at all: the manifest trees are repo data, not package data, so a site-packages __file__ has no robots/ ancestor and the start walk can never succeed there. Distinct from the packaging fix openral install ros got: robots/ and rskills/ are user-editable fixture trees a deploy reads, not fixed assets that could be vendored into the wheel. Sibling of the softer openral_sim.policies.robots._find_repo_root (returns None for 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 BEFORE ros2 launch. Backend-agnostic: reads scene.id from the DeployScene YAML and runs whatever SCENES.provision(scene_id) returns, so which scenes need preflighting is each backend's own provision= 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's on_configure — a callback tools/lifecycle_autostart.py bounds 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, and navigate_to_pose is 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 when config is None, when openral_sim is 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 at on_configure and raises the typed error with upstream stderr. Called after assert_ros2_packages_discoverable so a missing overlay fails in a second rather than after a multi-GB download.
  • _preflight_palette_deps(*, repo_root, robot_yaml, commercial_deployment=False) -> NoneAdvisory (not a gate) policy-extras preflight. Mirrors ReasonerNode._maybe_seed_palette_from_search_paths: globs <repo_root>/rskills/*/rskill.yaml, runs build_tool_palette against the robot's RobotCapabilities, then probes each capability-matching manifest via policy_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-process gr00t import set). The palette is robot-WIDE (a franka_panda config matches six model families), so a partially-installed venv is the common case and the reasoner already drops unimportable rSkills at on_configure — this mirrors that contract instead of blocking. When ≥1 matching skill is blocked: with OPENRAL_AUTO_INSTALL_DEPS=1 (the unattended-consent env var openral_sim._assets / _deps also honor) it runs just sync --all-packages --group … (union of manifest_install_groupsbehavior-groot for 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 instead typer.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 it typer.Exit(1)s with the just sync --all-packages --group … command (--all-packages is required so the workspace members survive the install — without it the next ros2 launch fails with No 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 at on_configure). Also the single pre-launch hook that knows what the reasoner may dispatch, so it calls _apply_palette_head_cam over the capability-matched set once the palette is known to be non-empty.
  • _apply_palette_head_cam(matched) -> bool — Sets OPENRAL_ROBOCASA_HEAD_CAM=1 when any capability-matched rSkill declares observation.images.head in sensors_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, so scenes/deploy/robocasa_navigate.yaml + the InternVLA-N1 VLN rSkill (the pairing the nav scene exists for) rendered no head frames 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, =0 included. Called from _preflight_palette_deps, so both deploy sim and deploy run inherit it; the launch env is os.environ.copy() (_prepare_launch_env), which carries it into the HAL process that renders. Defined in openral_cli.deploy_sim.
  • run_launch_invocation(invocation, *, run_preflight=True) -> int — Shared shelling path for deploy sim + deploy run. Under run_preflight it runs the SAME preflight sequence, in the same order, as the deploy sim command body: assert_ros2_packages_discoverable (overlay/stale-build check) → _reap_orphans_with_log_preflight_palette_deps → the _preflight_reward_vram_fit VLA↔reward pair check when invocation.enable_reward_monitor is set. The first two used to run on the sim path only, so a crashed deploy run left orphan graph processes holding GPU memory and /dev/shm/fastrtps_* lockfiles until someone happened to run deploy sim, and the next deploy run failed with a terse Failed init_port fastrtps_port7000 instead of reaping them. run_preflight=False remains a full bypass. Then writes the ephemeral HAL params YAML, exports OPENRAL_VENV_SITE + prepends the venv bin/PATH, and shells the resolved argv via _run_launch; returns the launch exit code. Defined in openral_cli.deploy_sim.
  • _detect_gpu_vram_gb(field) -> float — Torch-free nvidia-smi --query-gpu=<field> probe (MiB→GiB) for GPU 0, 0.0 on any failure (no nvidia-smi / no GPU / parse error). Deliberate mirror of openral_reasoner_ros.reasoner_node._detect_gpu_total_vram_gb (the CLI cannot import that ROS-package-local helper without pulling in rclpy). 0.0 makes the deploy preflight skip (budget unreadable → defer to the reasoner's runtime check). Defined in openral_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 in openral_cli.deploy_sim.
  • _capability_matched_manifests(repo_root, description, *, commercial_deployment=False) -> list[RSkillManifest] — Loads every rskills/*/rskill.yaml and returns those the reasoner palette would admit (build_tool_palette capability/role/license filter; unloadable manifests skipped). deploy sim does not preselect a VLA, so reward resolution + the VRAM preflight reason over this set (the "VLA known at launch" is the palette). Defined in openral_cli.deploy_sim.
  • _resolve_reward_monitor_manifest(*, repo_root, description, explicit_manifest) -> str — Reward-model resolution. An explicit --reward-monitor-manifest wins; else derives the reward model from the capability-matched VLA palette's reward_rskill_name (consensus → that kind:reward rSkill's in-tree manifest path), defaulting to robometer-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 in openral_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, runs openral_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_vla drops a non-fitting VLA anyway) and a HARD typer.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 when gpu_budget_gb <= 0 (budget unreadable), no reward model is active, or there is no VLA palette. Defined in openral_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 by deploy sim and deploy run; both load a DeployScene config, with real mode skipping sim scene-attach injections and forwarding hal_mode="real". First merges the scene's committed runtime: 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 forwards workcell_json:=... when the scene declares safety or extra_allowed_collision_pairs, so the launch applies tighten-only deploy safety and additive ACM before configuring the kernel. When the scene declares a hal: binding, its defaults (port + lerobot id/calibration_dir + calibrate_on_connect) are merged into hal_params above the robot-manifest defaults and below any --hal override (precedence --hal > scene hal > robot.yaml); a relative calibration_dir resolves 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 under deploy run: resolves the DeployScene and shells the same sim_e2e.launch.py graph with hal_mode:=real. --enable-reward-monitor (parity with deploy sim) brings up the Robometer reward monitor parallel to the VLA; the manifest auto-pairs from the VLA palette's reward_rskill_name with --reward-monitor-manifest as the operator override. Tri-state: unset falls through to the scene's runtime: block (DeployRuntime, shared resolver — a committed scene like so101_bench.yaml pins its whole posture with no flags), then off. Defined in openral_cli.main.

  • deploy_validate(--config, --robot, --hal) — Pre-run readiness check for openral deploy run; touches no hardware and shells no ros2 launch. Loads the DeployScene, resolves it via resolve_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 serial port is declared (+ device exists now → WARN if not); a serial HAL with calibrate_on_connect=false has an id+calibration_dir and the <calibration_dir>/<id>.json file exists (missing → ERROR; the "has no calibration registered" failure mode); and each scene sensor has a deploy_binding (+ any /dev/* device exists). ERROR (missing committed data) exits non-zero; WARN (device just not attached now) does not. Registered as openral deploy validate.
  • _parse_hal_overrides(raw: list[str] | None) -> dict[str, object] — Parse repeated --hal key=value flags. Values are JSON-decoded where possible (so --hal viewer_enabled=false parses 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 as hal_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's starting_pose and aborts the goal on a plan failure, vs. the best-effort snap. Empty default = legacy snap; forwarded as approach_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:pytorch selects 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_view lifecycle node per entry; forwarded comma-joined as object_detector_locators:=… only when non-empty). resolve_launch_invocation gains object_detector_onnx/object_detector_manifest/object_detector_query/object_detector_locators kwargs forwarded as object_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-onnx is given the default backend resolves to the open-vocab omdet-turbo-indoor manifest 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 to omdet-turbo-locator (when omdet deps import) and accept a repeatable --object-detector-locator <manifest|alias> (LocateAnything opt-in). openral deploy sim exposes --object-detector-manifest / --object-detector-query / --object-detector-locator. Reward-monitor: resolve_launch_invocation also gains enable_reward_monitor/reward_monitor_manifest/reward_monitor_task, forwarded as enable_reward_monitor:=… (+ the optional overrides only when set); openral deploy sim exposes --enable-reward-monitor/--no-enable-reward-monitor / --reward-monitor-manifest (a kind:reward YAML; weights_uri may be hf://org/repo or local:///abs/dir) / --reward-monitor-task. When on, the launch brings up reward_monitor_node parallel to the VLA and sets the reasoner's task_progress_available:=true. LaunchInvocation carries enable_reward_monitor: bool + the resolved reward_monitor_manifest: str — when the monitor is on and --reward-monitor-manifest is not pinned, resolve_launch_invocation derives the reward model from the capability-matched VLA palette's reward_rskill_name (the pairing the reasoner honours) via _resolve_reward_monitor_manifest, defaulting to robometer-4b when no VLA names one or they disagree (warned). The resolved path is forwarded as reward_monitor_manifest:=… (deploy run leaves the monitor off, so it is empty there). Scene VLM: resolve_launch_invocation also gains enable_scene_vlm/scene_vlm_manifest, forwarded as enable_scene_vlm:=… (+ scene_vlm_manifest:=… only when the leg is on AND a path is pinned — ros2 launch rejects an empty name:=); openral deploy sim exposes --enable-scene-vlm/--no-enable-scene-vlm. When on, the launch brings up openral_perception_ros/scene_vlm_node co-active with the graph (one cache entry per manifest RGB camera, so the LLM picks a viewpoint per query) and sets the reasoner's scene_query_available:=true, which is what actually offers the read-only query_scene tool. Before this the param had no setter anywhere in the repo, so the tool, its dispatch handler and the qwen35-4b-nf4 rSkill were all unreachable from deploy sim. Read-only. Critic producer: resolve_launch_invocation also gains enable_critic, forwarded as enable_critic:=…; openral deploy sim exposes --enable-critic/--no-enable-critic. When on, the launch brings up openral_reasoner_ros/critic_producer_node co-active with the graph — it watches the generic /openral/critic/score topic any reward model publishes and emits a Tier-C FailureTrigger on /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 via decompose_mission); forwarded as initial_task_prompt:=… on the argv when non-empty.
  • _omdet_runtime_available() -> bool — Probe (importlib.util.find_spec for transformers + timm) deciding whether resolve_launch_invocation's default object detector is the open-vocab omdet-turbo-indoor continuous backend or the in-tree RT-DETR COCO ONNX fallback. Patched in unit tests to exercise both branches deterministically. Defined in openral_cli.deploy_sim.
  • class _HalSpec — Frozen dataclass with package, executable, node_name, default_params, and opt-in flags supports_sim_env_yaml (scene-attach injection), manifest_driven (nodes built via make_lifecycle_main_from_manifest; the resolver forwards robot_yaml + hal_mode), and bare_twin_sim (issue #191 — a manifest arm that builds its OWN sim MJCF rather than scene-attaching: so100/so101 derive a bare MujocoArmHAL twin whose cameras are spliced in by the generic camera rig from sensors[].sim_placement (issue #88 — no scene composer), while openarm composes a tabletop MJCF from the DeployScene's composition forwarded as scene_composition_json (the scene owns its arena, not the robot manifest); suppresses the sim_env_yaml injection). Every robot is now manifest_driven — Phase 3 migrated the last two bespoke nodes (panda_mobile, openarm), so no openral_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 because sim_e2e.launch.py is generic.

python/cli/src/openral_cli/dataset.py

openral dataset Typer app + push subcommand.

  • dataset_app: typer.Typer — Public Typer group mounted under openral at name="dataset". (L43)
  • push_command(root, *, repo_id, yes, dry_run, token, commit_message) -> Noneopenral dataset push <root>. Reads meta/info.json, resolves the repo_id, runs the PII consent prompt (skippable via --yes or OPENRAL_DATASET_CONSENT=1), then HfApi.create_repo(private=True) → ensure_private → upload_folder. (L296)
  • from_bag_command(bag_path, *, robot, output, repo_id, license, fps) -> Noneopenral dataset from-bag <bag.mcap> --robot robots/<x>/robot.yaml --output <ds-root>. Calls Rosbag2ToLeRobotConverter.from_bag; produces a v3 dataset ready for openral dataset push. (L61)
  • _read_info_json(root: Path) -> dict[str, object] — Parse meta/info.json; raises ROSConfigError on missing / malformed file. (L187)
  • _camera_keys_from_info(info) -> list[str] — Extract observation.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 under openral at name="collision".
  • lower(robot, *, write, acm_only, geometry_only, emit_cumotion=None) -> Noneopenral collision lower --robot <yaml>. Prints a unified diff of the regenerated collision_geometry / allowed_collision_pairs block(s) and mutates the manifest only with --write (a regenerated ACM is a safety input — never silent; CLAUDE.md §3). --acm-only / --geometry-only are 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, --write writes the file.
  • _lower(robot_path, *, acm_only, geometry_only) -> tuple[RobotDescription, LoweredCollisionModel] — Shared loader: parse the manifest and lower via lower_robot_auto (the provenance dispatcher). Used by _lowered_text and the --emit-cumotion path.
  • check(robot, *, all_robots, acm_only, geometry_only) -> Noneopenral 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 a LoweredCollisionModel to (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 — Inject origin_xyz/origin_rpy/axis_xyz into 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) — Typed openral check --json payload: generated_at, n_robots/n_rskills/n_scenes, findings; .errors / .warnings / .ok properties.
  • check_description_graph(repo_root, *, resolve_remote_assets=False) -> GraphCheckReport — Parse every robots/*/robot.yaml, rskills/*/rskill.yaml, and scenes/{deploy,sim,benchmark}/*.yaml; resolve file:/ros2:// asset refs (rd:/gym_aloha:/openarm:/menagerie: skipped unless resolve_remote_assets); check every scene robot_id resolves to a robot dir, every rSkill's embodiment tags reach an in-repo robot (warning), and every sensor parent_frame is a declared frame (warning). Reuses RobotDescription.from_yaml / resolve_asset — no parallel validation logic.
  • check_command(--repo-root, --strict, --resolve-remote-assets, --json) — The openral check leaf command; exit 1 on any error (and on warnings under --strict). Registered in main.py via app.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 — Load upstream (rd:<robot_descriptions module> → xacro expanded via xacrodoc, or file:<path> → already-flat URDF), serialize to a flat URDF, apply rename (a (pattern, repl) pair or a sequence of them, re.sub in order; default per-robot from _RENAME / _RAW_RENAMES), and write <robot_id>.urdf with a provenance header after the XML declaration. For rd: upstreams the round-trip's cache-absolutized mesh paths are rewritten to portable rd:<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=True copies an already-flat upstream URDF's text verbatim (no yourdfpy round-trip) and applies the renames to the raw XML, preserving package:// / 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 a rskills/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) explicit patch → (4) CLI rename/license/embodiment_tags. (L173)
  • _apply_patch(raw, patch) -> None — Overlay patch keys onto the raw manifest dict; a None value removes the key (so e.g. ACT family clears min_vram_gb).
  • _rewrite_readme(readme_path, *, rskill_id, owner) -> None — Replace TEMPLATE_ORG / TEMPLATE_ID sentinels in README.md.
  • _validate_scaffold(scaffold_dir) -> None — Round-trip through RSkillManifest.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.

  • RSkillFamilyLiteral["act", "smolvla", "pi05", "xvla", "diffusion"]; mirrors the keys of openral_sim.registry.POLICIES minus 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] — Fetches config.json from a HF Hub repo, infers the policy family from type, and derives chunk_size / sensors / state_contract / image_preprocessing.aliases / weights_uri from input_features. (L168)
  • _fetch_hf_json(repo_id, filename) -> Anyhuggingface_hub.hf_hub_download + json.load; raises ValueError on network / parse error.
  • _sensors_from_input_features(input_features) -> list[dict] — One SensorRequirement-shaped dict per observation.images.* feature, with min_width / min_height pulled off the CHW shape.
  • _state_dim_from_input_features(input_features) -> int | None — Reads observation.state.shape[0].
  • _aliases_from_input_features(input_features) -> dict[str, str] — Pairs camera<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, description
  • class KnownDevice(NamedTuple) — A known USB adapter/controller from the VID/PID table. (L61) fields: chip, driver_hint, embodiment_tag, bh_robot_type
  • class UsbMatch(NamedTuple) — A detected device matched against the table. (L79) fields: device, known
  • class 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)