pow-cli 0.1.1__tar.gz → 0.2.0rc2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. pow_cli-0.2.0rc2/CHANGELOG.md +170 -0
  2. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/PKG-INFO +1 -1
  3. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/cli/asset.py +0 -1
  4. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/cli/init.py +44 -28
  5. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/cli/lint.py +0 -2
  6. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/cli/main.py +14 -3
  7. pow_cli-0.2.0rc2/pow_cli/cli/ros.py +155 -0
  8. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/cli/run.py +0 -2
  9. pow_cli-0.2.0rc2/pow_cli/cli/sim.py +67 -0
  10. pow_cli-0.2.0rc2/pow_cli/common/prompt.py +136 -0
  11. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/core/models/pow_config.py +104 -4
  12. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/core/ros_manager.py +142 -128
  13. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/core/runner.py +113 -13
  14. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/data/pow.template.toml +3 -6
  15. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/docker/Dockerfile.simros_jazzy +99 -13
  16. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pyproject.toml +6 -1
  17. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/tests/test_config.py +108 -5
  18. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/tests/test_init_cmd.py +30 -7
  19. pow_cli-0.2.0rc2/tests/test_prompt.py +198 -0
  20. pow_cli-0.2.0rc2/tests/test_ros_cmd.py +154 -0
  21. pow_cli-0.2.0rc2/tests/test_ros_manager.py +268 -0
  22. pow_cli-0.2.0rc2/tests/test_runner.py +299 -0
  23. pow_cli-0.2.0rc2/tests/test_sim_cmd.py +71 -0
  24. pow_cli-0.2.0rc2/tests/test_version.py +11 -0
  25. pow_cli-0.1.1/CHANGELOG.md +0 -126
  26. pow_cli-0.1.1/pow_cli/cli/ros.py +0 -42
  27. pow_cli-0.1.1/pow_cli/docker/Dockerfile.simros_humble +0 -200
  28. pow_cli-0.1.1/tests/test_runner.py +0 -88
  29. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/.gitignore +0 -0
  30. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/LICENSE +0 -0
  31. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/NOTICE +0 -0
  32. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/README.md +0 -0
  33. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/__init__.py +0 -0
  34. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/cli/__init__.py +0 -0
  35. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/cli/check.py +0 -0
  36. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/cli/python.py +0 -0
  37. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/common/__init__.py +0 -0
  38. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/common/utils.py +0 -0
  39. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/core/__init__.py +0 -0
  40. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/core/asset_manager.py +0 -0
  41. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/core/initializer.py +0 -0
  42. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/core/linter.py +0 -0
  43. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/core/models/asset_profile.py +0 -0
  44. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/core/models/system_config.py +0 -0
  45. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/data/asset-registry/isaacsim_assets_5_1_0.toml +0 -0
  46. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/data/asset-registry/omniverse_assets.toml +0 -0
  47. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/data/example/asset-profile.example.toml +0 -0
  48. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/data/gitignore.template +0 -0
  49. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/data/system.template.toml +0 -0
  50. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/pow_cli/main.py +0 -0
  51. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/scripts/check_assets_urls.py +0 -0
  52. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/tests/test_check_cmd.py +0 -0
  53. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/tests/test_initializer.py +0 -0
  54. {pow_cli-0.1.1 → pow_cli-0.2.0rc2}/tests/test_verify_isaacsim.py +0 -0
@@ -0,0 +1,170 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `pow-cli` package will be documented in this file.
4
+
5
+ ## [0.2.0-rc.2] - 2026-07-28
6
+
7
+ ### Added
8
+
9
+ - **`pow sim`** — run Isaac Sim from any directory, with no project required: no `pyproject.toml` check and `pow.toml` is never read. Launches the default version (`5.1.0`, override with `-v/--version`) and forwards raw arguments straight to `isaac-sim.sh` (`pow sim -- --no-window`). The ROS 2 bridge environment is loaded by default (`--ros jazzy`); pass `--no-ros` to launch with the inherited environment instead
10
+ - **`pow ros build`** — build the custom image from `ros_dockerfile` without re-running `pow init`; builds the `pow_simros_jazzy` base image first if it is missing, and supports `--no-cache`. `pow ros` is now a command group (bare `pow ros [args...]` usage is unchanged; `pow ros launch` is the explicit form)
11
+ - **`ros_bridge` config in `pow.toml`** — choose which Isaac Sim internal ROS 2 bridge libs to load (`jazzy` or `humble`, default `jazzy`), i.e. which `exts/isaacsim.ros2.bridge/<distro>/lib` is added to `LD_LIBRARY_PATH` when `enable_ros = true`. Overridable per profile. Previously the bridge was inferred from the host Ubuntu version with no way to override it; an unsupported value is now rejected with a clear error
12
+ - **Tab completion for the `pow init` ROS workspace path** — step 6's "Path to clone IsaacSim-ros_workspaces" prompt now completes filesystem paths on <kbd>Tab</kbd> like a shell: unique directories complete inline (no stray trailing space, so the next segment can be typed straight away), ambiguous prefixes list candidates, and `~` is kept in the stored path. Trailing separators and whitespace are trimmed before the value is written to `pow.toml`
13
+
14
+ ### Changed
15
+
16
+ - **Breaking (vs 0.2.0-rc.1):** the `ros_container_name` config key was renamed to `ros_docker_image`. The container name is no longer configured directly — it is derived from the image name (`/` and `:` replaced with `_`), so default setups now get a container named `pow_simros_jazzy` instead of `pow_simros`
17
+ - **Breaking (vs 0.2.0-rc.1):** ROS 2 Humble support was removed — the ROS workspace and docker integration now support Jazzy only (`Dockerfile.simros_humble` deleted). Isaac Sim itself still runs on Ubuntu 22.04 and 24.04; Jazzy workspaces build on both
18
+ - **`pow init` no longer builds the Isaac Sim ROS workspace.** Step 6 only clones `IsaacSim-ros_workspaces`; the ROS 2 bridge comes from Isaac Sim's own prebuilt libs (selected by `ros_bridge`), so init is much faster and no longer runs a Docker build for the workspace. It reports the bridge distro in use instead of a build result
19
+ - `pow init` no longer creates the `scripts/`, `.assets/`, and `standalone/` project folders (only `exts/`, `.modules/`, `usda/`). A manually created `scripts/` folder is still mounted into the ROS container when present
20
+ - The `pow_simros_jazzy` image now resolves workspace dependencies with `rosdep install` at build time instead of building the workspace, and source-only packages (`topic_based_ros2_control`) are staged in `/opt/pow/extra_src` and copied into `/jazzy_ws/src` at container start, so the runtime volume mount no longer shadows them
21
+
22
+ ### Fixed
23
+
24
+ - **`rosdep install` failed inside the Jazzy container** — three causes, all fixed (requires rebuilding the image): the Ubuntu 24.04 base image's stock `ubuntu` user took UID 1000 and collided with the mapped host user, so `HOME` pointed at `/home/ubuntu` while the host `~/.ros` was mounted at `/home/hostuser/.ros`; the per-user rosdep cache was empty because `rosdep update` had run as root at build time, giving "your rosdep installation has not been initialized"; and `ros-jazzy-ros-testing` was missing from the image
25
+ - **`cpu_performance_mode` asked for the sudo password on every launch** — `pow run` / `pow python` now read the current CPU governor from `/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor` first and run no `sudo` at all when it is already `performance`, so the password is normally needed once instead of every launch. When sudo credentials are still cached the governor is set without the "requires sudo" notice, and hosts without `cpupower` or without cpufreq support now report and skip rather than prompting for a password to no purpose. The interactive prompt itself is unchanged when the governor really has to be switched
26
+ - **`ros2` tab completion inside the `pow_simros` container** — the entrypoint sourced ROS before exec'ing bash, which carried environment variables but not bash completion functions. Interactive shells now re-source `/opt/ros/jazzy/setup.bash` and the workspace overlay via `/etc/bash.bashrc`, so `ros2` / `colcon` autocomplete works without manually sourcing `setup.bash` (requires rebuilding the image)
27
+
28
+ ---
29
+
30
+ ## [0.2.0-rc.1] - 2026-06-14
31
+
32
+ ### Added
33
+
34
+ - **Custom ROS Dockerfile (`pow init`)**
35
+ - New `ros_dockerfile` config in `pow.toml` — point to a project-local Dockerfile that layers on top of the bundled `pow_simros_<distro>` base image
36
+ - New `ros_container_name` config in `pow.toml` — set a custom container/image name (defaults to `pow_simros`)
37
+ - `pow init` now builds the custom image automatically after the base image when `ros_dockerfile` is set
38
+ - `pow ros` launches the custom image when configured, otherwise falls back to the base image
39
+ - **Version flag (`pow --version` / `pow -v`)** — quickly check the installed `pow-cli` version from the command line
40
+
41
+ ### Changed
42
+
43
+ - Updated CLI description to *"Manage Isaac Sim projects and simplify the development workflow"*
44
+ - Renamed status messages from *"Isaac ROS workspace"* to *"Isaac Sim ROS workspace"* for clarity
45
+ - Container name is now read from `ros_container_name` config instead of being hard-coded
46
+
47
+ ---
48
+
49
+ ## [0.1.1] - 2026-06-14
50
+
51
+ ### Fixed
52
+
53
+ - **ROS Docker container: `colcon build` fails for new packages** — The container
54
+ entrypoint now detects stale build artifacts (left from a host-path build) and
55
+ cleans `build/`, `install/`, and `log/` automatically before rebuilding.
56
+
57
+ ---
58
+
59
+ ## [0.1.0] - 2026-06-05
60
+
61
+ First stable release of `pow-cli`, promoted from `0.1.0-rc.1` with documentation improvements.
62
+
63
+
64
+ ### Fixed
65
+
66
+ - Installation guide referenced incorrect `pow-cli` version
67
+
68
+ ### Changed
69
+
70
+ - Improved README wording, tagline, and Profiles section
71
+
72
+ ---
73
+
74
+ ## [0.1.0-rc.1] - 2026-05-11
75
+
76
+ ### Added
77
+
78
+ - **Asset Management (`pow asset`)**
79
+ - `pow asset list` — list available Isaac Sim & Omniverse assets
80
+ - `pow asset add` — download and register assets into the local asset folder (only support `isaacsim_5_1_0` for now)
81
+ - `pow asset set / unset` — set/unset target local asset folder
82
+ - `pow asset info` — display current local asset folder information
83
+ - **Linter (`pow lint`)**
84
+ - New `pow lint` and `pow lint --fix` commands for `.usda` files
85
+ - Detects absolute/relative local asset paths and rewrites them with correct aliases or asset urls (`user-home`, `pow-assets`)
86
+ - Rule for validating `simros_ws` relative paths
87
+ - **ROS Integration (`pow ros`)**
88
+ - `pow ros` command to build, launch, and attach to ROS 2 Docker containers
89
+ - Verbose mode (`--verbose`) for runtime diagnostics
90
+ - Separate Dockerfiles for ROS Humble and Jazzy distributions
91
+ - Project `scripts/` directory mounted into the container
92
+ - PyTorch with CUDA support available inside the container
93
+ - `isaacsim_ros_ws` working directory configurable in `pow.toml`
94
+ - **Runner Improvements**
95
+ - (experimental) `pow run --open <file>` option to open a USD stage on launch
96
+ - Non-existent `ext_folders` entries are now silently skipped instead of auto-created
97
+ - **Other**
98
+ - `pow python` command with `--profile` flag for running standalone app under specified version of Isaac Sim's Python
99
+ - `user-home` aliases automatically configured during `pow init`
100
+ - `usda/` folder added to default project structure
101
+ - `extends` support in `pow.toml` for profile-based configuration
102
+
103
+ ### Fixed
104
+
105
+ - ROS Jazzy container build failure on Ubuntu 24.04
106
+ - CUDA version pinned to 12.1 for deterministic ROS builds
107
+ - `.ros` / `.ros2` mount and permission issues in the SimROS container
108
+ - SimROS entrypoint no longer warns when host-user directory already exists
109
+ - `pow run` no longer calls `open_stage` when no path is provided
110
+ - `pow asset unset` no longer accidentally removes `user-home` alias
111
+ - Duplicate and deprecated keys in generated `.vscode/settings.json`
112
+ - `pow init` now respects existing `isaacsim_ros_ws` value in existing `pow.toml`
113
+ - `pow ros` correctly attaches to an already-running container
114
+
115
+ ### Changed
116
+
117
+ - Rewrite and refactor all core functionality of pow-cli
118
+ - Move commands under group `pow sim` to root `pow` command instead
119
+ - Remove pow-foxglove from repository
120
+ - ROS-related logic extracted into dedicated `ros_manager.py` module
121
+ - CLI and core layers refactored for clearer separation of concerns
122
+
123
+ ## [0.1.0a3] - 2026-01-27
124
+
125
+ ### Fixed
126
+
127
+ - Fixed incorrect Ubuntu base Docker image version in `pow sim init` ROS workspace setup. Now correctly uses Ubuntu 22.04 for ROS Humble and Ubuntu 24.04 for ROS Jazzy (previously hardcoded to 22.04 for both).
128
+
129
+ ## [0.1.0a2] - 2025-12-25
130
+
131
+ ### Fixed
132
+
133
+ - `pow sim init` now allows overwriting existing VS Code settings to resolve Pylance `reportMissingImports` errors for Isaac Sim packages.
134
+ - Fixed an issue where the `ros_enable` flag did not correctly disable ROS workspace sourcing when set to `false` in an existing `pow.toml`.
135
+
136
+ ## [0.1.0a1] - 2025-12-23
137
+
138
+ ### Added
139
+
140
+ - Initial alpha release of Isaac Powerpack CLI (`pow`)
141
+ - **Core CLI Structure**
142
+ - Main entry point with Click-based command group architecture
143
+ - Hierarchical command organization under `pow sim` namespace
144
+
145
+ - **Simulation Commands (`pow sim`)**
146
+ - `pow sim run` - Run Isaac Sim applications with automatic environment setup
147
+ - Auto-discovery of project root via `pow.toml` configuration
148
+ - ROS 2 workspace sourcing support
149
+ - Isaac Sim setup file sourcing
150
+ - Configurable app path and extension loading
151
+ - `pow sim init` - Initialize Isaac Sim development environment
152
+ - VS Code settings generation for Isaac Sim development
153
+ - Asset browser cache fix utility
154
+ - Project configuration scaffolding
155
+ - `pow sim check` - Run Isaac Sim compatibility checker
156
+ - Validates system compatibility with Isaac Sim requirements
157
+ - `pow sim info` - Display Isaac Sim configuration information
158
+ - Show local assets path configuration (`-l, --local-assets` flag)
159
+
160
+ - **Resource Management (`pow sim add`)**
161
+ - `pow sim add local-assets` - Configure local Isaac Sim assets
162
+ - Updates `isaacsim.exp.base.kit` with local asset paths
163
+ - Configures asset browser and content browser folders
164
+ - Supports versioned asset directories
165
+
166
+ ### Dependencies
167
+
168
+ - `click>=8.1.7` - Command line interface framework
169
+ - `toml>=0.10.2` - TOML configuration file parsing
170
+ - Python 3.10+ required
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pow-cli
3
- Version: 0.1.1
3
+ Version: 0.2.0rc2
4
4
  Summary: Command line interface for Isaac Powerpack
5
5
  Author: Titiwat Munintravong, Isaac Powerpack Contributors
6
6
  License-Expression: Apache-2.0
@@ -307,7 +307,6 @@ def list_cmd():
307
307
  console.print()
308
308
  try:
309
309
  from ..core.asset_manager import REGISTRIES
310
- from itertools import groupby
311
310
 
312
311
  target_groups = set()
313
312
  for r in REGISTRIES:
@@ -6,11 +6,10 @@ from pathlib import Path
6
6
  import click
7
7
  from rich.panel import Panel
8
8
  from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
9
- from rich.prompt import Confirm, Prompt
10
- from rich.text import Text
9
+ from rich.prompt import Confirm
11
10
 
11
+ from ..common.prompt import ask_path
12
12
  from ..common.utils import console
13
- from ..core.models.pow_config import PowConfig
14
13
  from ..core.initializer import Initializer
15
14
  from ..core.ros_manager import RosManager
16
15
 
@@ -63,7 +62,7 @@ def _step1_check_config(global_dir_name: str) -> bool:
63
62
  """Print config header and verify pyproject.toml exists. Return False to abort."""
64
63
  if not Path("pyproject.toml").exists():
65
64
  console.print(
66
- "\n[bold red][1/8] ❌ Error:[/bold red] pyproject.toml not found. "
65
+ "\n[bold red][1/10] ❌ Error:[/bold red] pyproject.toml not found. "
67
66
  "Please run this command in a valid project directory."
68
67
  )
69
68
  return False
@@ -260,7 +259,7 @@ def _step6_ros_integration(
260
259
  ws_path = forced_ws
261
260
  console.print(f" Using existing workspace path from pow.toml: [bold green]{ws_path}[/bold green]")
262
261
  else:
263
- ws_path = Prompt.ask(
262
+ ws_path = ask_path(
264
263
  " Path to clone IsaacSim-ros_workspaces",
265
264
  default=default_ws,
266
265
  )
@@ -277,28 +276,19 @@ def _step6_ros_integration(
277
276
  console.print(f" [dim]Workspace path:[/dim] {display_path}")
278
277
 
279
278
  ros_cloned = False
280
- ros_already_built = False
281
279
 
282
280
  def ros_status_callback(state):
283
- nonlocal ros_cloned, ros_already_built
281
+ nonlocal ros_cloned
284
282
  if state == "cloning":
285
283
  ros_cloned = True
286
- status.update("[bold green]Cloning Isaac ROS workspace...")
284
+ status.update("[bold green]Cloning Isaac Sim ROS workspace...")
287
285
  elif state == "existed":
288
- status.update("[bold yellow]Isaac ROS workspace already exists. Checking build...")
289
- elif state == "built":
290
- ros_already_built = True
291
- status.update("[bold yellow]Docker build already complete.")
292
- elif state == "building":
293
- status.update("[bold green]Docker build: Isaac ROS workspace...")
294
- elif state.startswith("building:"):
295
- line = state[len("building:"):]
296
- status.update(f"[bold green]Docker build:[/bold green] [dim]{line[:80]}[/dim]")
286
+ status.update("[bold yellow]Isaac Sim ROS workspace already exists.")
297
287
 
298
288
  # Resolve the tilde path for actual filesystem operations
299
289
  resolved_ws = Path(ws_path).expanduser()
300
290
 
301
- ros_build_failed = False
291
+ ros_setup_failed = False
302
292
 
303
293
  with console.status("Preparing ROS workspace...") as status:
304
294
  try:
@@ -307,11 +297,11 @@ def _step6_ros_integration(
307
297
  ws_path=resolved_ws,
308
298
  )
309
299
  except Exception as e:
310
- ros_build_failed = True
300
+ ros_setup_failed = True
311
301
  console.print(f" [bold red]❌ ROS Setup Error:[/bold red] {e}")
312
302
 
313
- if ros_build_failed:
314
- console.print(" [yellow]⊖[/yellow] Skipping remaining steps due to ROS build error.")
303
+ if ros_setup_failed:
304
+ console.print(" [yellow]⊖[/yellow] Skipping remaining steps due to ROS setup error.")
315
305
  raise SystemExit(1)
316
306
 
317
307
  if ros_cloned:
@@ -319,11 +309,12 @@ def _step6_ros_integration(
319
309
  else:
320
310
  console.print(f" [yellow]✔[/yellow] IsaacSim-ros_workspaces already available in [dim]{display_path}[/dim]")
321
311
 
322
- distro_label = f"ROS [bold]{ros_res['ros_distro']}[/bold] (Ubuntu {ros_res['ubuntu_version']})"
323
- if ros_already_built:
324
- console.print(f" [yellow]✔[/yellow] Docker build already complete for {distro_label}.")
325
- else:
326
- console.print(f" [green]✔[/green] Docker build complete for {distro_label}.")
312
+ bridge_distro = ros_mgr.config.ros_bridge
313
+ console.print(
314
+ f" [green]✔[/green] ROS bridge: [bold]{bridge_distro}[/bold] "
315
+ f"(ros_bridge in pow.toml, host Ubuntu {ros_res['ubuntu_version']}) "
316
+ f"via Isaac Sim internal libs."
317
+ )
327
318
 
328
319
  # Build pow_simros Docker image
329
320
  simros_already_built = False
@@ -341,7 +332,7 @@ def _step6_ros_integration(
341
332
 
342
333
  with console.status("Building pow_simros image...") as simros_status:
343
334
  try:
344
- simros_res = ros_mgr.build_simros_image(
335
+ ros_mgr.build_simros_image(
345
336
  status_callback=simros_status_callback,
346
337
  ws_path=resolved_ws,
347
338
  )
@@ -356,13 +347,38 @@ def _step6_ros_integration(
356
347
  else:
357
348
  console.print(f" [green]✔[/green] Docker image {simros_label} built successfully.")
358
349
 
350
+ # Build custom ROS image layered on top of pow_simros_<distro>
351
+ custom_dockerfile = ros_mgr.config.ros_dockerfile
352
+ if custom_dockerfile:
353
+ custom_image = ros_mgr.config.ros_docker_image
354
+
355
+ def custom_status_callback(state):
356
+ if state == "custom_building":
357
+ custom_status.update(f"[bold green]Building custom image '{custom_image}'...")
358
+ elif state.startswith("custom_building:"):
359
+ line = state[len("custom_building:"):]
360
+ custom_status.update(f"[bold green]custom build:[/bold green] [dim]{line[:80]}[/dim]")
361
+
362
+ with console.status(f"Building custom image '{custom_image}'...") as custom_status:
363
+ try:
364
+ ros_mgr.build_custom_ros_image(status_callback=custom_status_callback)
365
+ except Exception as e:
366
+ console.print(f" [bold red]❌ Custom ROS Build Error:[/bold red] {e}")
367
+ console.print(" [yellow]⊖[/yellow] Skipping remaining steps due to build error.")
368
+ raise SystemExit(1)
369
+
370
+ console.print(
371
+ f" [green]✔[/green] Custom Docker image [bold]{custom_image}[/bold] "
372
+ f"built from [dim]{custom_dockerfile}[/dim]."
373
+ )
374
+
359
375
  return True, display_path
360
376
 
361
377
 
362
378
  def _step7_project_structure(initializer: Initializer):
363
379
  """Create local project folders and .gitignore."""
364
380
  console.print("[bold blue][7/10] 🏗️ Project Structure:[/bold blue] Creating local folders...")
365
- local_folders = ["exts", "scripts", ".modules", ".assets", "standalone", "usda"]
381
+ local_folders = ["exts", ".modules", "usda"]
366
382
 
367
383
  with Progress(
368
384
  SpinnerColumn(),
@@ -5,8 +5,6 @@ from pathlib import Path
5
5
  import click
6
6
  from rich.markup import escape
7
7
  from rich.panel import Panel
8
- from rich.table import Table
9
- from rich.text import Text
10
8
 
11
9
  from ..common.utils import console
12
10
  from ..core.linter import AliasConfig, lint_file, fix_file, scan_directory
@@ -7,14 +7,24 @@ from .init import init_cmd
7
7
  from .check import check_cmd
8
8
  from .lint import lint_group
9
9
  from .run import run_cmd
10
- from .ros import ros_cmd
10
+ from .sim import sim_cmd
11
+ from .ros import ros_group
11
12
  from .python import python_cmd
12
13
  from .asset import asset_group
13
14
 
14
15
 
15
16
  @click.group()
17
+ @click.version_option(
18
+ None,
19
+ "-v",
20
+ "--version",
21
+ package_name="pow-cli",
22
+ prog_name="pow",
23
+ message="%(prog)s %(version)s",
24
+ help="Show the pow CLI version.",
25
+ )
16
26
  def pow_group():
17
- """Isaac Powerpack CLI for Isaac ROS and Isaac Sim development."""
27
+ """Manage Isaac Sim projects and simplify the development workflow."""
18
28
  pass
19
29
 
20
30
 
@@ -23,6 +33,7 @@ pow_group.add_command(init_cmd)
23
33
  pow_group.add_command(check_cmd)
24
34
  pow_group.add_command(lint_group)
25
35
  pow_group.add_command(run_cmd)
26
- pow_group.add_command(ros_cmd)
36
+ pow_group.add_command(sim_cmd)
37
+ pow_group.add_command(ros_group)
27
38
  pow_group.add_command(python_cmd)
28
39
  pow_group.add_command(asset_group)
@@ -0,0 +1,155 @@
1
+ """Ros command implementation."""
2
+
3
+ import click
4
+ from rich.panel import Panel
5
+ from ..common.utils import console
6
+ from ..core.ros_manager import RosManager
7
+
8
+
9
+ def _launch_container(extra_args, verbose: bool):
10
+ """Launch or attach to the ROS container, rendering errors as panels."""
11
+ try:
12
+ RosManager.run_simros_container(extra_args=extra_args, verbose=verbose)
13
+ except click.ClickException:
14
+ raise
15
+ except Exception as e:
16
+ console.print(
17
+ Panel(
18
+ f"[bold red]✘[/bold red] {e}",
19
+ title="[bold red]ROS Error[/bold red]",
20
+ border_style="red",
21
+ )
22
+ )
23
+ raise SystemExit(1)
24
+
25
+
26
+ class RosGroup(click.Group):
27
+ """Group that falls through to `launch` for unrecognized arguments."""
28
+
29
+ def resolve_command(self, ctx, args):
30
+ if args and self.get_command(ctx, args[0]) is not None:
31
+ return super().resolve_command(ctx, args)
32
+ launch = self.get_command(ctx, "launch")
33
+ return launch.name, launch, args
34
+
35
+
36
+ @click.group(
37
+ name="ros",
38
+ cls=RosGroup,
39
+ invoke_without_command=True,
40
+ context_settings={"ignore_unknown_options": True},
41
+ )
42
+ @click.pass_context
43
+ def ros_group(ctx: click.Context):
44
+ """Launch or manage the ROS docker container.
45
+
46
+ Without a subcommand, launches an interactive session in the container
47
+ running the image from `ros_docker_image` in pow.toml (or attaches to it
48
+ if it is already running). Unrecognized arguments are forwarded as the
49
+ container command (default: /bin/bash), e.g. `pow ros ros2 topic list`.
50
+
51
+ \b
52
+ Subcommands:
53
+ launch Explicitly launch the container (same as bare `pow ros`).
54
+ build Build the custom image from `ros_dockerfile`.
55
+
56
+ \b
57
+ Requires:
58
+ - ROS integration enabled in pow.toml (enable_ros = true).
59
+ - The pow_simros Docker image built via `pow init`.
60
+ """
61
+ if ctx.invoked_subcommand is None:
62
+ _launch_container(None, False)
63
+
64
+
65
+ @ros_group.command(
66
+ name="launch",
67
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
68
+ )
69
+ @click.option("--verbose", "-v", is_flag=True, default=False, help="Show detailed feedback during container launch.")
70
+ @click.pass_context
71
+ def launch_cmd(ctx: click.Context, verbose: bool):
72
+ """Launch the ROS container, forwarding extra args as the container command."""
73
+ _launch_container(ctx.args or None, verbose)
74
+
75
+
76
+ @ros_group.command(name="build")
77
+ @click.option("--no-cache", is_flag=True, default=False, help="Build without using the Docker layer cache.")
78
+ def build_cmd(no_cache: bool):
79
+ """Build the custom ROS image from `ros_dockerfile` in pow.toml."""
80
+ ros_mgr = RosManager()
81
+ config = ros_mgr.config
82
+
83
+ if config.project_root is None:
84
+ raise click.ClickException("Not initialized. Run `pow init` first.")
85
+
86
+ if not config.ros_dockerfile:
87
+ console.print(
88
+ "[yellow]No `ros_dockerfile` set in pow.toml; nothing to build.[/yellow]\n"
89
+ f"The bundled [bold]pow_simros_{config.ros_distro}[/bold] image is built by `pow init`."
90
+ )
91
+ return
92
+
93
+ base_image = f"pow_simros_{config.ros_distro}"
94
+ if not RosManager.image_exists(base_image):
95
+ distro_ws = config.ros_ws_path / f"{config.ros_distro}_ws"
96
+ if not distro_ws.exists():
97
+ raise click.ClickException(
98
+ f"Base image '{base_image}' not found and the ROS workspace "
99
+ f"'{distro_ws}' does not exist.\n"
100
+ "Run `pow init` first to set up the ROS workspace."
101
+ )
102
+
103
+ console.print(f"[yellow]Base image '{base_image}' not found — building it first.[/yellow]")
104
+
105
+ def simros_status_callback(state):
106
+ if state == "simros_building":
107
+ base_status.update(f"[bold green]Building base image '{base_image}'...")
108
+ elif state.startswith("simros_building:"):
109
+ line = state[len("simros_building:"):]
110
+ base_status.update(f"[bold green]{base_image} build:[/bold green] [dim]{line[:80]}[/dim]")
111
+
112
+ with console.status(f"Building base image '{base_image}'...") as base_status:
113
+ try:
114
+ ros_mgr.build_simros_image(status_callback=simros_status_callback)
115
+ except Exception as e:
116
+ console.print(
117
+ Panel(
118
+ f"[bold red]✘[/bold red] {e}",
119
+ title="[bold red]ROS Build Error[/bold red]",
120
+ border_style="red",
121
+ )
122
+ )
123
+ raise SystemExit(1)
124
+
125
+ console.print(f"[green]✔[/green] Base Docker image [bold]{base_image}[/bold] built successfully.")
126
+
127
+ custom_image = config.ros_docker_image
128
+
129
+ def custom_status_callback(state):
130
+ if state == "custom_building":
131
+ custom_status.update(f"[bold green]Building custom image '{custom_image}'...")
132
+ elif state.startswith("custom_building:"):
133
+ line = state[len("custom_building:"):]
134
+ custom_status.update(f"[bold green]custom build:[/bold green] [dim]{line[:80]}[/dim]")
135
+
136
+ with console.status(f"Building custom image '{custom_image}'...") as custom_status:
137
+ try:
138
+ ros_mgr.build_custom_ros_image(
139
+ status_callback=custom_status_callback,
140
+ no_cache=no_cache,
141
+ )
142
+ except Exception as e:
143
+ console.print(
144
+ Panel(
145
+ f"[bold red]✘[/bold red] {e}",
146
+ title="[bold red]ROS Build Error[/bold red]",
147
+ border_style="red",
148
+ )
149
+ )
150
+ raise SystemExit(1)
151
+
152
+ console.print(
153
+ f"[green]✔[/green] Custom Docker image [bold]{custom_image}[/bold] "
154
+ f"built from [dim]{config.ros_dockerfile}[/dim]."
155
+ )
@@ -1,8 +1,6 @@
1
1
  """Run command implementation."""
2
2
 
3
3
  import click
4
- from rich.panel import Panel
5
- from ..common.utils import console
6
4
  from ..core.runner import Runner
7
5
 
8
6
  @click.command(
@@ -0,0 +1,67 @@
1
+ """Sim command implementation."""
2
+
3
+ import click
4
+ from rich.panel import Panel
5
+ from ..common.utils import console
6
+ from ..core.models.pow_config import PowConfig
7
+ from ..core.runner import Runner
8
+
9
+
10
+ @click.command(
11
+ name="sim",
12
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
13
+ )
14
+ @click.option(
15
+ "-v",
16
+ "--version",
17
+ "sim_version",
18
+ default=PowConfig.ISAACSIM_VERSION,
19
+ show_default=True,
20
+ help="Isaac Sim version to run.",
21
+ )
22
+ @click.option(
23
+ "--ros",
24
+ "ros_bridge",
25
+ type=click.Choice(PowConfig.SUPPORTED_ROS_BRIDGES),
26
+ default=PowConfig.ROS_BRIDGE,
27
+ show_default=True,
28
+ help="ROS 2 bridge distro to load into the environment.",
29
+ )
30
+ @click.option(
31
+ "--no-ros",
32
+ is_flag=True,
33
+ default=False,
34
+ help="Launch without the ROS 2 bridge environment.",
35
+ )
36
+ @click.pass_context
37
+ def sim_cmd(ctx: click.Context, sim_version: str, ros_bridge: str, no_ros: bool):
38
+ """Run Isaac Sim from any directory, ignoring pow.toml.
39
+
40
+ \b
41
+ Unlike `pow run` this needs no project: no pyproject.toml is required and
42
+ pow.toml is never read. Every unrecognized argument is forwarded verbatim
43
+ to .pow/isaacsim/<version>/isaac-sim.sh.
44
+
45
+ \b
46
+ Examples:
47
+ pow sim
48
+ pow sim --no-ros
49
+ pow sim -- --no-window --/renderer/enabled=gpu
50
+ """
51
+ try:
52
+ Runner.run_sim(
53
+ version=sim_version,
54
+ ros_bridge=None if no_ros else ros_bridge,
55
+ extra_args=ctx.args,
56
+ )
57
+ except click.ClickException:
58
+ raise
59
+ except Exception as e:
60
+ console.print(
61
+ Panel(
62
+ f"[bold red]✘[/bold red] {e}",
63
+ title="[bold red]Sim Error[/bold red]",
64
+ border_style="red",
65
+ )
66
+ )
67
+ raise SystemExit(1)