sima-cli 2.1.14__tar.gz → 2.1.16__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 (155) hide show
  1. {sima_cli-2.1.14/sima_cli.egg-info → sima_cli-2.1.16}/PKG-INFO +1 -1
  2. {sima_cli-2.1.14 → sima_cli-2.1.16}/pyproject.toml +1 -1
  3. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/__version__.py +1 -1
  4. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/cli.py +125 -11
  5. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/discover/discover.py +1 -1
  6. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/install/metadata_installer.py +56 -7
  7. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/model_zoo/model.py +1 -0
  8. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/playbooks/manager.py +9 -4
  9. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/commands.py +50 -4
  10. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/config.py +5 -0
  11. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/install.py +60 -17
  12. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/neat.py +24 -8
  13. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/utils.py +110 -23
  14. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/update/elxr.py +97 -33
  15. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/update/query.py +29 -4
  16. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/update/updater.py +47 -6
  17. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/container_registries.py +249 -0
  18. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/docker.py +1 -1
  19. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/pcie.py +126 -29
  20. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/vulcan/artifacts.py +55 -5
  21. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/vulcan/commands.py +50 -5
  22. {sima_cli-2.1.14 → sima_cli-2.1.16/sima_cli.egg-info}/PKG-INFO +1 -1
  23. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli.egg-info/SOURCES.txt +3 -0
  24. sima_cli-2.1.16/tests/unit/test_cli_update.py +76 -0
  25. sima_cli-2.1.16/tests/unit/test_cli_update_rerun.py +44 -0
  26. sima_cli-2.1.16/tests/unit/test_container_registries.py +220 -0
  27. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_docker_utils.py +23 -0
  28. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_elxr_update.py +110 -1
  29. sima_cli-2.1.16/tests/unit/test_firmware.py +141 -0
  30. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_metadata_installer.py +54 -0
  31. sima_cli-2.1.16/tests/unit/test_model_zoo.py +48 -0
  32. sima_cli-2.1.16/tests/unit/test_modelzoo_commands.py +11 -0
  33. sima_cli-2.1.16/tests/unit/test_pcie.py +171 -0
  34. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_sdk_image_detection.py +287 -31
  35. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_skills_manager.py +30 -0
  36. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_vulcan.py +226 -13
  37. sima_cli-2.1.14/tests/unit/test_cli_update.py +0 -25
  38. sima_cli-2.1.14/tests/unit/test_cli_update_rerun.py +0 -20
  39. sima_cli-2.1.14/tests/unit/test_firmware.py +0 -60
  40. sima_cli-2.1.14/tests/unit/test_model_zoo.py +0 -0
  41. {sima_cli-2.1.14 → sima_cli-2.1.16}/LICENSE +0 -0
  42. {sima_cli-2.1.14 → sima_cli-2.1.16}/MANIFEST.in +0 -0
  43. {sima_cli-2.1.14 → sima_cli-2.1.16}/README.md +0 -0
  44. {sima_cli-2.1.14 → sima_cli-2.1.16}/requirements.txt +0 -0
  45. {sima_cli-2.1.14 → sima_cli-2.1.16}/setup.cfg +0 -0
  46. {sima_cli-2.1.14 → sima_cli-2.1.16}/setup.py +0 -0
  47. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/__init__.py +0 -0
  48. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/__main__.py +0 -0
  49. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/app_zoo/__init__.py +0 -0
  50. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/app_zoo/app.py +0 -0
  51. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/app_zoo/commands.py +0 -0
  52. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/auth/__init__.py +0 -0
  53. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/auth/auth0.py +0 -0
  54. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/auth/devportal.py +0 -0
  55. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/auth/login.py +0 -0
  56. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/data/__init__.py +0 -0
  57. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/data/resources_internal.yaml +0 -0
  58. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/data/resources_public.yaml +0 -0
  59. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/deploy_only/__init__.py +0 -0
  60. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/deploy_only/device/__init__.py +0 -0
  61. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/deploy_only/device/commands.py +0 -0
  62. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/deploy_only/mpk/__init__.py +0 -0
  63. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/deploy_only/mpk/commands.py +0 -0
  64. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/discover/__init__.py +0 -0
  65. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/discover/linuxll.py +0 -0
  66. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/download/__init__.py +0 -0
  67. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/download/downloader.py +0 -0
  68. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/install/__init__.py +0 -0
  69. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/install/compatibility.py +0 -0
  70. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/install/github_assets.py +0 -0
  71. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/install/hostdriver.py +0 -0
  72. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/install/metadata_info.py +0 -0
  73. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/install/metadata_validator.py +0 -0
  74. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/install/optiview.py +0 -0
  75. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/install/package_builder.py +0 -0
  76. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/install/palette.py +0 -0
  77. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/install/registry.py +0 -0
  78. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/mla/__init__.py +0 -0
  79. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/mla/meminfo.py +0 -0
  80. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/model_zoo/__init__.py +0 -0
  81. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/network/__init__.py +0 -0
  82. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/network/network.py +0 -0
  83. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/playbooks/__init__.py +0 -0
  84. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/playbooks/commands.py +0 -0
  85. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/__init__.py +0 -0
  86. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/cmdexec.py +0 -0
  87. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/linux_devkit_network.py +0 -0
  88. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/linux_shared_network.py +0 -0
  89. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/network_doctor.py +0 -0
  90. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/preinstall.py +0 -0
  91. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/requirements.json +0 -0
  92. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/script.py +0 -0
  93. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/stop.py +0 -0
  94. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/sdk/uninstall.py +0 -0
  95. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/serial/__init__.py +0 -0
  96. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/serial/serial.py +0 -0
  97. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/storage/__init__.py +0 -0
  98. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/storage/nvme.py +0 -0
  99. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/storage/sdcard.py +0 -0
  100. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/update/__init__.py +0 -0
  101. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/update/bmaptool.py +0 -0
  102. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/update/bootimg.py +0 -0
  103. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/update/cleanlog.py +0 -0
  104. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/update/local.py +0 -0
  105. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/update/netboot.py +0 -0
  106. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/update/remote.py +0 -0
  107. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/upgrade/__init__.py +0 -0
  108. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/upgrade/selfupdate.py +0 -0
  109. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/__init__.py +0 -0
  110. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/api_common.py +0 -0
  111. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/artifactory.py +0 -0
  112. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/common.py +0 -0
  113. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/config.py +0 -0
  114. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/config_loader.py +0 -0
  115. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/deprecation.py +0 -0
  116. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/device_api.py +0 -0
  117. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/disk.py +0 -0
  118. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/env.py +0 -0
  119. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/errors.py +0 -0
  120. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/mpk_api.py +0 -0
  121. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/net.py +0 -0
  122. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/network.py +0 -0
  123. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/pkg_update_check.py +0 -0
  124. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/serializers.py +0 -0
  125. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/services.py +0 -0
  126. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/utils/tag.py +0 -0
  127. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli/vulcan/__init__.py +0 -0
  128. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli.egg-info/dependency_links.txt +0 -0
  129. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli.egg-info/entry_points.txt +0 -0
  130. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli.egg-info/requires.txt +0 -0
  131. {sima_cli-2.1.14 → sima_cli-2.1.16}/sima_cli.egg-info/top_level.txt +0 -0
  132. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/__init__.py +0 -0
  133. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/e2e/__init__.py +0 -0
  134. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/__init__.py +0 -0
  135. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_app_zoo.py +0 -0
  136. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_appzoo_commands.py +0 -0
  137. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_auth.py +0 -0
  138. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_auth0.py +0 -0
  139. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_cli.py +0 -0
  140. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_cli_stdio.py +0 -0
  141. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_deprecation.py +0 -0
  142. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_download.py +0 -0
  143. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_generate_cli_markdown_docs.py +0 -0
  144. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_install_stub.py +0 -0
  145. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_metadata_validator.py +0 -0
  146. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_netboot.py +0 -0
  147. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_package_builder.py +0 -0
  148. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_pkg_update_check.py +0 -0
  149. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_sdk_linux_shared_network.py +0 -0
  150. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_sdk_network_doctor.py +0 -0
  151. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_sdk_preinstall.py +0 -0
  152. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_sdk_uninstall.py +0 -0
  153. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_selfupdate.py +0 -0
  154. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_skills_commands.py +0 -0
  155. {sima_cli-2.1.14 → sima_cli-2.1.16}/tests/unit/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sima-cli
3
- Version: 2.1.14
3
+ Version: 2.1.16
4
4
  Summary: CLI tool for SiMa Developer Portal to download models, firmware, and apps.
5
5
  Home-page: https://community.sima.ai/
6
6
  Author: SiMa.ai
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sima-cli"
7
- version = "2.1.14"
7
+ version = "2.1.16"
8
8
  description = "CLI tool for SiMa Developer Portal to download models, firmware, and apps."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1,2 +1,2 @@
1
1
  # sima_cli/__version__.py
2
- __version__ = "2.1.14"
2
+ __version__ = "2.1.16"
@@ -2,6 +2,9 @@ import os
2
2
  import shutil
3
3
  import sys
4
4
  import click
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.text import Text
5
8
  from sima_cli.utils.env import get_environment_type
6
9
  from sima_cli.update.updater import perform_update
7
10
  from sima_cli.model_zoo.model import list_models, download_model, describe_model
@@ -12,7 +15,11 @@ from sima_cli.__version__ import __version__
12
15
  from sima_cli.utils.config import CONFIG_PATH
13
16
  from sima_cli.install.optiview import install_optiview
14
17
  from sima_cli.install.hostdriver import install_hostdriver
15
- from sima_cli.install.metadata_installer import install_from_metadata, metadata_resolver
18
+ from sima_cli.install.metadata_installer import (
19
+ MetadataAccessForbidden,
20
+ install_from_metadata,
21
+ metadata_resolver,
22
+ )
16
23
  from sima_cli.serial.serial import connect_serial
17
24
  from sima_cli.storage.nvme import nvme_format, nvme_remount
18
25
  from sima_cli.storage.sdcard import sdcard_format
@@ -71,6 +78,19 @@ def _should_rerun_after_update(argv):
71
78
  return command_name not in (None, "selfupdate", "version")
72
79
 
73
80
 
81
+ def _allows_external_prerelease_fallback(argv):
82
+ """Return whether this invocation explicitly allows update mirror fallback."""
83
+ args = argv[1:]
84
+ return "update" in args and any(arg in ("-f", "--force") for arg in args)
85
+
86
+
87
+ def _update_auto_confirm_requested(argv):
88
+ """Return whether an update invocation requests non-interactive confirmation."""
89
+ return _command_name_from_argv(argv) == "update" and any(
90
+ arg in ("-y", "--yes") for arg in argv[1:]
91
+ )
92
+
93
+
74
94
  def _rerun_current_command() -> None:
75
95
  env = os.environ.copy()
76
96
  env["SIMA_CLI_CHECK_FOR_UPDATE"] = "0"
@@ -82,9 +102,10 @@ def _rerun_current_command() -> None:
82
102
  # Entry point for the CLI tool using Click's command group decorator
83
103
  @click.group(context_settings=dict(help_option_names=["-h", "--help", "-?"], max_content_width=120))
84
104
  @click.option('-i', '--internal', is_flag=True, help="Use internal Artifactory resources, Authorized Sima employees only")
105
+ @click.option('-y', '--yes', is_flag=True, help="Assume yes for confirmation prompts.")
85
106
  @click.version_option(version=f"{__version__}", message="SiMa CLI version: %(version)s")
86
107
  @click.pass_context
87
- def main(ctx, internal):
108
+ def main(ctx, internal, yes):
88
109
  """
89
110
  sima-cli – SiMa Developer Portal CLI Tool
90
111
 
@@ -92,8 +113,19 @@ def main(ctx, internal):
92
113
  --internal Use internal Artifactory resources (can also be set via env variable SIMA_CLI_INTERNAL=1)
93
114
  """
94
115
  _configure_stdio_errors()
95
- if check_for_update('sima-cli') and _should_rerun_after_update(sys.argv):
96
- _rerun_current_command()
116
+ auto_accept_update = yes or _update_auto_confirm_requested(sys.argv)
117
+ previous_auto_accept = os.environ.get("SIMA_CLI_AUTO_ACCEPT_UPDATE")
118
+ if auto_accept_update:
119
+ os.environ["SIMA_CLI_AUTO_ACCEPT_UPDATE"] = "1"
120
+ try:
121
+ if check_for_update('sima-cli') and _should_rerun_after_update(sys.argv):
122
+ _rerun_current_command()
123
+ finally:
124
+ if auto_accept_update:
125
+ if previous_auto_accept is None:
126
+ os.environ.pop("SIMA_CLI_AUTO_ACCEPT_UPDATE", None)
127
+ else:
128
+ os.environ["SIMA_CLI_AUTO_ACCEPT_UPDATE"] = previous_auto_accept
97
129
  ctx.ensure_object(dict)
98
130
 
99
131
  os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
@@ -107,13 +139,25 @@ def main(ctx, internal):
107
139
  click.echo("Refer to the confluence page to find out how to configure internal resource map.")
108
140
  exit(0)
109
141
 
110
- if internal and not check_artifactory_reachability():
142
+ internal_reachable = True
143
+ if internal:
144
+ internal_reachable = check_artifactory_reachability()
145
+
146
+ if internal and not internal_reachable and not _allows_external_prerelease_fallback(sys.argv):
111
147
  click.secho("❌ You have specified -i or --internal argument to access internal resources, but you can't connect to Artifactory.", fg='red')
112
148
  click.secho("Please make sure you are connected to VPN or are on the corporate network.", fg='red')
113
149
  exit(0)
150
+
151
+ if internal and not internal_reachable:
152
+ click.secho(
153
+ "⚠️ Internal resources are unreachable. --force allows this update to use the external pre-release mirror.",
154
+ fg="yellow",
155
+ )
114
156
 
115
157
 
116
158
  ctx.obj["internal"] = internal
159
+ ctx.obj["internal_reachable"] = internal_reachable
160
+ ctx.obj["yes"] = yes
117
161
 
118
162
  env_type, env_subtype = get_environment_type()
119
163
 
@@ -230,21 +274,29 @@ def download(ctx, url, dest):
230
274
  @click.option(
231
275
  "-y", "--yes",
232
276
  is_flag=True,
233
- help="Skip confirmation after firmware file is downloaded."
277
+ help="Assume yes for update confirmation prompts."
234
278
  )
235
279
  @click.option(
236
280
  "-p", "--passwd",
237
281
  default="edgeai",
238
282
  show_default=True,
239
- help="Optional SSH password for remote board (default is 'edgeai')."
283
+ help="Password for remote board SSH or local ELXR sudo authentication."
240
284
  )
241
285
  @click.option(
242
- "-f", "--flavor",
286
+ "--flavor",
243
287
  type=click.Choice(["headless", "full", "auto"], case_sensitive=False),
244
288
  default="auto",
245
289
  show_default=True,
246
290
  help="Firmware flavor: 'full' image supports NVMe and GUI on Modalix DevKit. This option is deprecated for 2.0 and above"
247
291
  )
292
+ @click.option(
293
+ "-f", "--force",
294
+ is_flag=True,
295
+ help=(
296
+ "If the internal mirror is unreachable, fall back to the external pre-release mirror "
297
+ "without signature verification; without --internal, select that mirror directly (ELXR only)."
298
+ )
299
+ )
248
300
  @click.option(
249
301
  "-t", "--troot_only",
250
302
  is_flag=True,
@@ -258,7 +310,7 @@ def download(ctx, url, dest):
258
310
  help="For ELXR updates only, validate the update path and print the simaai-ota command without running it."
259
311
  )
260
312
  @click.pass_context
261
- def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, troot_only, dryrun):
313
+ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force, troot_only, dryrun):
262
314
  """
263
315
  Update the software on a SiMa DevKit or remote SiMa device.
264
316
 
@@ -316,6 +368,18 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, troot_o
316
368
 
317
369
  sima-cli update -v 1.7.0 -y
318
370
 
371
+ # Update ELXR to the latest official release without prompts
372
+
373
+ sima-cli update -y
374
+
375
+ # Update ELXR from the internal mirror without prompts
376
+
377
+ sima-cli -i update -y
378
+
379
+ # Update ELXR from the public pre-release mirror without prompts
380
+
381
+ sima-cli -y update -f -y
382
+
319
383
  # Validate ELXR update path without running simaai-ota
320
384
 
321
385
  sima-cli update --dryrun
@@ -345,15 +409,20 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, troot_o
345
409
 
346
410
  # Extract context and run update logic
347
411
  internal = ctx.obj.get("internal", False)
412
+ auto_confirm = yes or ctx.obj.get("yes", False)
413
+ force_external_fallback = force and (
414
+ not internal or not ctx.obj.get("internal_reachable", True)
415
+ )
348
416
  perform_update(
349
417
  version_or_url,
350
418
  ip,
351
419
  internal,
352
420
  passwd=passwd,
353
- auto_confirm=yes,
421
+ auto_confirm=auto_confirm,
354
422
  flavor=flavor,
355
423
  troot_only=troot_only,
356
424
  dryrun=dryrun,
425
+ force_external_fallback=force_external_fallback,
357
426
  )
358
427
 
359
428
  # ----------------------
@@ -407,7 +476,7 @@ def get_model(ctx, model_name):
407
476
  @click.argument('model_name')
408
477
  @click.pass_context
409
478
  def get_model(ctx, model_name):
410
- """Download a specific model."""
479
+ """Provide information about a specific model."""
411
480
  ver = ctx.obj.get("ver")
412
481
  internal = ctx.obj.get("internal", False)
413
482
  boardtype = ctx.obj.get('boardtype')
@@ -544,6 +613,37 @@ def bootimg_cmd(ctx, version, boardtype, netboot, devkit_ip, autoflash, fwtype,
544
613
  SDK_DEPENDENT_COMPONENTS = {"palette", "hostdriver"}
545
614
  SDK_INDEPENDENT_COMPONENTS = {"optiview"}
546
615
  ALL_COMPONENTS = SDK_DEPENDENT_COMPONENTS | SDK_INDEPENDENT_COMPONENTS
616
+ NEAT_INSTALL_REPOSITORIES = {
617
+ "apps",
618
+ "core",
619
+ "insight",
620
+ "internals",
621
+ "llima",
622
+ "models",
623
+ "sdk",
624
+ "sentinel",
625
+ "sima-cli",
626
+ }
627
+
628
+
629
+ def _is_neat_install_target(component: str) -> bool:
630
+ repository = component.lower().split("/", 1)[0].split("@", 1)[0].split(":", 1)[0]
631
+ return repository in NEAT_INSTALL_REPOSITORIES
632
+
633
+
634
+ def _show_neat_install_fallback(component: str) -> None:
635
+ Console().print(
636
+ Panel(
637
+ Text.from_markup(
638
+ "The standard install source returned HTTP 403 for this Neat component.\n\n"
639
+ f"Switching to [bold]sima-cli neat install {component}[/bold].\n\n"
640
+ "For Neat organization components, use [bold]sima-cli neat install[/bold] in the future."
641
+ ),
642
+ title="Switching to Neat Install",
643
+ border_style="yellow",
644
+ expand=False,
645
+ )
646
+ )
547
647
 
548
648
  @main.command(name="install")
549
649
  @click.argument("component", required=False)
@@ -728,6 +828,20 @@ def install_cmd(
728
828
  command_name="sima-cli install",
729
829
  ):
730
830
  click.echo("✅ Installation complete.")
831
+ except MetadataAccessForbidden:
832
+ if not _is_neat_install_target(component):
833
+ raise
834
+ _show_neat_install_fallback(component)
835
+ install_vulcan_package(
836
+ target=component,
837
+ environment="production",
838
+ package_type=tag,
839
+ install_dir=install_dir,
840
+ force=force,
841
+ json_output=json_output,
842
+ command_name="sima-cli neat install",
843
+ )
844
+ return None
731
845
  except click.ClickException:
732
846
  raise
733
847
  except Exception as e:
@@ -236,7 +236,7 @@ def discover_and_render_pcie_devices():
236
236
  )
237
237
 
238
238
  console.print(pcie_table)
239
- maybe_run_pcie_throughput_test()
239
+ maybe_run_pcie_throughput_test(pcie_devices)
240
240
 
241
241
  # ─────────────────────────────────────────────────────────────
242
242
  # Unified Discovery Orchestration
@@ -49,6 +49,26 @@ class InstallationPreflightError(click.ClickException):
49
49
  )
50
50
 
51
51
 
52
+ class MetadataAccessForbidden(click.ClickException):
53
+ """Raised when a metadata request is rejected with HTTP 403."""
54
+
55
+
56
+ def _is_http_forbidden_error(exc: BaseException) -> bool:
57
+ current = exc
58
+ seen = set()
59
+ while current is not None and id(current) not in seen:
60
+ seen.add(id(current))
61
+ response = getattr(current, "response", None)
62
+ if getattr(response, "status_code", None) == 403:
63
+ return True
64
+ if getattr(current, "code", None) == 403:
65
+ return True
66
+ if re.search(r"\b403\b", str(current), flags=re.IGNORECASE):
67
+ return True
68
+ current = current.__cause__ or current.__context__
69
+ return False
70
+
71
+
52
72
  def _ensure_install_dir_writable(install_dir: str, command_name: str = "sima-cli install") -> None:
53
73
  target = Path(install_dir or ".").expanduser()
54
74
  display_path = Path.cwd() if str(target) == "." else target
@@ -767,7 +787,12 @@ def selectable_resource_handler(metadata):
767
787
  return metadata
768
788
 
769
789
 
770
- def _download_and_validate_metadata(metadata_url, internal=False, force=False):
790
+ def _download_and_validate_metadata(
791
+ metadata_url,
792
+ internal=False,
793
+ force=False,
794
+ check_compatibility=True,
795
+ ):
771
796
  """
772
797
  Downloads (if remote), validates, and parses metadata from a given URL or local file path.
773
798
 
@@ -775,6 +800,7 @@ def _download_and_validate_metadata(metadata_url, internal=False, force=False):
775
800
  metadata_url (str): URL or local path to a metadata.json file
776
801
  internal (bool): Whether to use internal mirrors or logic in downloader
777
802
  force (bool): whether to ignore compatibility check result
803
+ check_compatibility (bool): whether to enforce platform compatibility
778
804
 
779
805
  Returns:
780
806
  tuple: (parsed metadata dict, folder containing the metadata file)
@@ -811,7 +837,7 @@ def _download_and_validate_metadata(metadata_url, internal=False, force=False):
811
837
  with open(metadata_path, "r", encoding="utf-8") as f:
812
838
  metadata = json.load(f)
813
839
  validate_metadata(metadata)
814
- if _is_platform_compatible(metadata, force) or force:
840
+ if not check_compatibility or force or _is_platform_compatible(metadata, force):
815
841
  click.echo("✅ Metadata validated successfully.")
816
842
  metadata = selectable_resource_handler(metadata)
817
843
  return metadata, os.path.dirname(metadata_path)
@@ -823,16 +849,22 @@ def _download_and_validate_metadata(metadata_url, internal=False, force=False):
823
849
  metadata = json.load(f)
824
850
 
825
851
  validate_metadata(metadata)
826
- if _is_platform_compatible(metadata, force=force) or force:
852
+ if not check_compatibility or force or _is_platform_compatible(metadata, force=force):
827
853
  metadata = selectable_resource_handler(metadata)
828
854
  click.echo("✅ Metadata validated successfully.")
829
855
  return metadata, os.path.dirname(os.path.abspath(metadata_path))
830
856
 
857
+ return None, None
858
+
831
859
  except MetadataValidationError as e:
832
860
  click.echo(f"❌ Metadata validation failed: {e}")
833
861
  raise click.Abort()
834
862
 
835
863
  except Exception as e:
864
+ if _is_http_forbidden_error(e):
865
+ raise MetadataAccessForbidden(
866
+ f"Access forbidden while retrieving metadata from {metadata_url}"
867
+ ) from e
836
868
  click.echo(f"❌ Failed to retrieve or parse metadata from {metadata_url}: {e}")
837
869
  raise click.Abort()
838
870
 
@@ -1178,6 +1210,12 @@ def _get_palette_sdk_version(release_file: Path = Path("/etc/sdk-release")) -> s
1178
1210
  except OSError:
1179
1211
  return ""
1180
1212
 
1213
+ platform_base = re.search(
1214
+ r"^Platform Base\s*=\s*(\S+)", content, flags=re.MULTILINE
1215
+ )
1216
+ if platform_base:
1217
+ return platform_base.group(1).strip()
1218
+
1181
1219
  match = re.search(r"^SDK Version\s*=\s*(\S+)", content, flags=re.MULTILINE)
1182
1220
  if not match:
1183
1221
  return ""
@@ -1513,6 +1551,7 @@ def install_from_metadata(
1513
1551
  install_dir: str = '.',
1514
1552
  force: bool = False,
1515
1553
  command_name: str = "sima-cli install",
1554
+ download_only: bool = False,
1516
1555
  ):
1517
1556
  _ensure_install_dir_writable(install_dir, command_name=command_name)
1518
1557
 
@@ -1526,15 +1565,23 @@ def install_from_metadata(
1526
1565
  if force:
1527
1566
  click.secho('⚠️ --force option was provided, skipping available space and compatibility check, package may not work properly', fg='yellow')
1528
1567
 
1529
- metadata, _ = _download_and_validate_metadata(metadata_url, internal, force=force)
1530
- registry.create_entry(metadata.get('name'), metadata.get('version'), metadata, '')
1531
-
1568
+ metadata, _ = _download_and_validate_metadata(
1569
+ metadata_url,
1570
+ internal,
1571
+ force=force,
1572
+ check_compatibility=not download_only,
1573
+ )
1532
1574
  if metadata:
1533
1575
  print_metadata_summary(metadata=metadata)
1576
+ if not download_only:
1577
+ registry.create_entry(metadata.get('name'), metadata.get('version'), metadata, '')
1534
1578
 
1535
1579
  if _check_whether_disk_is_big_enough(metadata, force) or force:
1536
- if _is_platform_compatible(metadata, force) or force:
1580
+ if download_only or force or _is_platform_compatible(metadata, force):
1537
1581
  local_paths = _download_assets(metadata, metadata_url, install_dir, internal, tag=tag)
1582
+ if download_only:
1583
+ click.echo(f"✅ Download complete. {len(local_paths)} resource(s) saved to: {install_dir}")
1584
+ return local_paths
1538
1585
  if len(local_paths) > 0:
1539
1586
  _mark_install_script_executable(metadata, install_dir)
1540
1587
  _combine_multipart_files(install_dir, local_paths=local_paths)
@@ -1542,6 +1589,8 @@ def install_from_metadata(
1542
1589
  _mark_install_script_executable(metadata, install_dir)
1543
1590
  _run_installation_script(metadata=metadata, extract_path=install_dir)
1544
1591
 
1592
+ except MetadataAccessForbidden:
1593
+ raise
1545
1594
  except Exception as e:
1546
1595
  click.echo(f"❌ Failed to install from metadata URL {metadata_url}: {e}")
1547
1596
  exit(1)
@@ -564,6 +564,7 @@ def _list_available_models_internal(version: str, boardtype: str):
564
564
 
565
565
  if action == "Download model":
566
566
  _download_model_internal(version, boardtype, selected_model)
567
+ break # return to model selection after the download attempt
567
568
  elif action == "Back":
568
569
  break # back to model list
569
570
  else: # Exit
@@ -364,9 +364,11 @@ class SkillManager:
364
364
  return target
365
365
 
366
366
  def _download_repo_archive(self, source: SourceRef, url: str, workdir: Path) -> Path:
367
- parsed = urlparse(url)
368
- filename = Path(parsed.path).name or "skills.tar.gz"
369
- target = workdir / filename
367
+ # SCM archive endpoints commonly end in a ref (for example ``develop``
368
+ # or a commit SHA), so the URL path does not carry an archive suffix.
369
+ # Use a known filename instead of asking archive extraction to infer the
370
+ # format from that ref.
371
+ target = workdir / "skills.tar.gz"
370
372
 
371
373
  token = self._normalize_auth_token(os.getenv("GITHUB_TOKEN", "")) if source.scheme == "gh" else ""
372
374
  bb_user = os.getenv("BITBUCKET_USERNAME", "").strip()
@@ -418,7 +420,10 @@ class SkillManager:
418
420
  repo = source.repo_name
419
421
  ref = source.ref
420
422
  if source.scheme == "gh":
421
- return f"https://codeload.github.com/{owner}/{repo}/tar.gz/refs/heads/{ref}"
423
+ # GitHub's generic codeload endpoint resolves branches, tags, and
424
+ # commit SHAs. refs/heads only works for branches and returns 404
425
+ # for the immutable commit refs used by published packages.
426
+ return f"https://codeload.github.com/{owner}/{repo}/tar.gz/{quote(ref, safe='')}"
422
427
  if source.scheme == "bb":
423
428
  return f"https://bitbucket.org/{owner}/{repo}/get/{ref}.tar.gz"
424
429
  raise SkillError(f"Unsupported repo source: {source.scheme}")
@@ -13,6 +13,7 @@ Usage:
13
13
  sima-cli sdk model : go to model container
14
14
  sima-cli sdk yocto : go to Yocto container
15
15
  sima-cli sdk neat : go to Neat SDK container
16
+ sima-cli sdk ros2 : go to ROS 2 SDK container
16
17
  sima-cli sdk elxr : go to elxr container
17
18
  """
18
19
 
@@ -42,6 +43,7 @@ from sima_cli.sdk.network_doctor import (
42
43
  print_network_doctor_report,
43
44
  repair_linux_devkit_network,
44
45
  )
46
+ from sima_cli.sdk.neat import DEFAULT_INSIGHT_VIDEO_CHANNELS, MAX_INSIGHT_VIDEO_CHANNELS
45
47
  from sima_cli.sdk.linux_shared_network import (
46
48
  NM_SHARED_DISPATCHER_PATH,
47
49
  rollback_linux_shared_devkit_network,
@@ -67,7 +69,7 @@ def sdk(ctx, version_filter):
67
69
 
68
70
  This group provides access to the full SDK 2.0 toolchain, including
69
71
  setup, container orchestration, tool-specific shells (MPK, model,
70
- Yocto, Neat, eLxr), and hybrid `.sima` script execution. These commands are
72
+ Yocto, Neat, ROS 2, eLxr), and hybrid `.sima` script execution. These commands are
71
73
  intended for SDK 2.0+ users only.
72
74
 
73
75
  \\c Host platforms only.
@@ -281,6 +283,13 @@ def launch_sdk_tool(tool: str, cmd, ctx, recover_unavailable: bool = False):
281
283
  is_flag=True,
282
284
  help="Start Neat SDK without Insight UI/video/WebRTC port mappings.",
283
285
  )
286
+ @click.option(
287
+ "--insight-video-channels",
288
+ type=click.IntRange(1, MAX_INSIGHT_VIDEO_CHANNELS),
289
+ default=DEFAULT_INSIGHT_VIDEO_CHANNELS,
290
+ show_default=True,
291
+ help="Number of Insight video channels to configure (four exposed ports per channel).",
292
+ )
284
293
  @click.option(
285
294
  "--no-model-compiler",
286
295
  "--no-model-sdk",
@@ -304,8 +313,15 @@ def launch_sdk_tool(tool: str, cmd, ctx, recover_unavailable: bool = False):
304
313
  is_flag=True,
305
314
  help="Allow setup to install a persistent NetworkManager shared-network repair profile without prompting.",
306
315
  )
316
+ @click.option(
317
+ "--image",
318
+ "image_selectors",
319
+ multiple=True,
320
+ default=(),
321
+ help="Start only the SDK image matching this repository:tag or tag (e.g. 'ghcr.io/sima-neat/sdk:latest' or 'latest'). Repeatable; skips the selection prompt.",
322
+ )
307
323
  @click.pass_context
308
- def setup(ctx, yes, noninteractive, devkit, no_insight, no_model_sdk, minimal, workspace, persistent_network_profile):
324
+ def setup(ctx, yes, noninteractive, devkit, no_insight, insight_video_channels, no_model_sdk, minimal, workspace, persistent_network_profile, image_selectors):
309
325
  """Initialize SDK environment and select components to start."""
310
326
  devkit_ip = _resolve_devkit_ip(devkit)
311
327
  try:
@@ -314,10 +330,12 @@ def setup(ctx, yes, noninteractive, devkit, no_insight, no_model_sdk, minimal, w
314
330
  yes_to_all=yes,
315
331
  devkit_ip=devkit_ip,
316
332
  no_insight=no_insight,
333
+ insight_video_channels=insight_video_channels,
317
334
  no_model_sdk=no_model_sdk,
318
335
  minimal=minimal,
319
336
  workspace=workspace,
320
337
  persistent_network_profile=persistent_network_profile,
338
+ image_selectors=image_selectors,
321
339
  )
322
340
  except subprocess.CalledProcessError as e:
323
341
  raise click.ClickException(f"SDK setup failed while running: {' '.join(e.cmd)}") from e
@@ -335,11 +353,18 @@ def setup(ctx, yes, noninteractive, devkit, no_insight, no_model_sdk, minimal, w
335
353
  is_flag=True,
336
354
  help="Skip confirmation before starting the container."
337
355
  )
356
+ @click.option(
357
+ "--image",
358
+ "image_selectors",
359
+ multiple=True,
360
+ default=(),
361
+ help="Start only the SDK image matching this repository:tag or tag (e.g. 'ghcr.io/sima-neat/sdk:latest' or 'latest'). Repeatable; skips the selection prompt.",
362
+ )
338
363
  @click.pass_context
339
- def start(ctx, yes, noninteractive):
364
+ def start(ctx, yes, noninteractive, image_selectors):
340
365
  """Select and start one or more SDK containers."""
341
366
  try:
342
- setup_and_start(noninteractive=noninteractive,start_only=True, yes_to_all=yes)
367
+ setup_and_start(noninteractive=noninteractive, start_only=True, yes_to_all=yes, image_selectors=image_selectors)
343
368
  except subprocess.CalledProcessError as e:
344
369
  raise click.ClickException(f"SDK start failed while running: {' '.join(e.cmd)}") from e
345
370
  except RuntimeError as e:
@@ -636,6 +661,27 @@ def neat(ctx, cmd):
636
661
  launch_sdk_tool("neat", cmd, ctx, recover_unavailable=True)
637
662
 
638
663
 
664
+ @sdk.command(
665
+ name="ros2",
666
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True}
667
+ )
668
+ @click.argument("cmd", nargs=-1, type=click.UNPROCESSED)
669
+ @click.pass_context
670
+ def ros2(ctx, cmd):
671
+ """Launch the ROS 2 SDK workspace environment.
672
+
673
+ Use ``sima-cli sdk -v VERSION ros2`` to select among multiple installed
674
+ versions. CMD is executed in the container; without CMD an interactive
675
+ login shell is opened as the mapped host user.
676
+
677
+ \b
678
+ Examples:
679
+ sima-cli sdk ros2
680
+ sima-cli sdk -v latest ros2 ros2 --help
681
+ """
682
+ launch_sdk_tool("ros2", cmd, ctx)
683
+
684
+
639
685
  @sdk.command(
640
686
  name="elxr",
641
687
  context_settings={"ignore_unknown_options": True, "allow_extra_args": True}
@@ -27,6 +27,11 @@ IMAGE_CONFIG = {
27
27
  "port_mapping_required": False,
28
28
  "var-log-folders": ["sima", "supervisor"]
29
29
  },
30
+ "ros2": {
31
+ "display": "ROS 2 SDK",
32
+ "privileged": False,
33
+ "port_mapping_required": False
34
+ },
30
35
  "modelsdk": {
31
36
  "display": "ModelSDK",
32
37
  "privileged": False,