sima-cli 2.1.18__tar.gz → 2.1.20__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 (251) hide show
  1. {sima_cli-2.1.18/sima_cli.egg-info → sima_cli-2.1.20}/PKG-INFO +35 -2
  2. {sima_cli-2.1.18 → sima_cli-2.1.20}/README.md +32 -0
  3. {sima_cli-2.1.18 → sima_cli-2.1.20}/pyproject.toml +3 -2
  4. {sima_cli-2.1.18 → sima_cli-2.1.20}/requirements.txt +2 -1
  5. {sima_cli-2.1.18 → sima_cli-2.1.20}/setup.py +2 -1
  6. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/__version__.py +1 -1
  7. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/cli.py +52 -15
  8. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/cloudex/client.py +57 -12
  9. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/cloudex/installer.py +33 -1
  10. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/discover/discover.py +4 -0
  11. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/discover/linuxll.py +38 -3
  12. sima_cli-2.1.20/sima_cli/download/downloader.py +300 -0
  13. sima_cli-2.1.20/sima_cli/download/parallel.py +164 -0
  14. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/install/metadata_installer.py +16 -7
  15. sima_cli-2.1.20/sima_cli/mla/meminfo.py +274 -0
  16. sima_cli-2.1.20/sima_cli/models/client.py +916 -0
  17. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/models/commands.py +38 -13
  18. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/models/rendering.py +78 -205
  19. sima_cli-2.1.20/sima_cli/peripherals/__init__.py +5 -0
  20. sima_cli-2.1.20/sima_cli/peripherals/commands.py +208 -0
  21. sima_cli-2.1.20/sima_cli/peripherals/model.py +440 -0
  22. sima_cli-2.1.20/sima_cli/peripherals/sentinel.py +145 -0
  23. sima_cli-2.1.20/sima_cli/sdk/browser_ca.py +53 -0
  24. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/commands.py +118 -7
  25. sima_cli-2.1.20/sima_cli/sdk/container_registry.py +385 -0
  26. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/install.py +182 -20
  27. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/neat.py +175 -30
  28. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/network_doctor.py +104 -15
  29. sima_cli-2.1.20/sima_cli/sdk/preinstall.py +1759 -0
  30. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/utils.py +998 -76
  31. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/vscode_extensions.py +15 -13
  32. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/emmc.py +34 -2
  33. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/host.py +3 -4
  34. sima_cli-2.1.20/sima_cli/update/mirror_catalog.py +11 -0
  35. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/netboot.py +188 -65
  36. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/netboot_device.py +49 -244
  37. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/overlay.py +19 -7
  38. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/remote.py +14 -3
  39. sima_cli-2.1.20/sima_cli/update/reset_overlay.py +92 -0
  40. sima_cli-2.1.20/sima_cli/update/swap.py +81 -0
  41. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/swu.py +40 -13
  42. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/swu_artifacts.py +17 -6
  43. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/swu_certificate.py +1 -2
  44. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/swu_target.py +35 -0
  45. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/uboot_environment.py +11 -3
  46. sima_cli-2.1.20/sima_cli/utils/streaming.py +84 -0
  47. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/vulcan/artifacts.py +31 -18
  48. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/vulcan/commands.py +1 -1
  49. {sima_cli-2.1.18 → sima_cli-2.1.20/sima_cli.egg-info}/PKG-INFO +35 -2
  50. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli.egg-info/SOURCES.txt +34 -0
  51. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli.egg-info/requires.txt +6 -1
  52. sima_cli-2.1.20/tests/unit/peripherals_fixtures.py +136 -0
  53. sima_cli-2.1.20/tests/unit/test_artifact_parallel_integration.py +174 -0
  54. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_cloudex_client.py +138 -16
  55. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_cloudex_commands.py +24 -0
  56. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_cloudex_installer.py +75 -0
  57. sima_cli-2.1.20/tests/unit/test_code_ui_readiness.py +81 -0
  58. sima_cli-2.1.20/tests/unit/test_discovery_host_tools.py +103 -0
  59. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_download.py +1 -0
  60. sima_cli-2.1.20/tests/unit/test_download_retries.py +115 -0
  61. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_emmc.py +78 -12
  62. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_generate_cli_markdown_docs.py +16 -0
  63. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_host_update.py +11 -1
  64. sima_cli-2.1.20/tests/unit/test_install_force.py +39 -0
  65. sima_cli-2.1.20/tests/unit/test_install_script_terminal.py +153 -0
  66. sima_cli-2.1.20/tests/unit/test_mirror_catalog.py +61 -0
  67. sima_cli-2.1.20/tests/unit/test_mla_meminfo.py +159 -0
  68. sima_cli-2.1.20/tests/unit/test_model_deployments.py +794 -0
  69. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_models.py +401 -280
  70. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_netboot.py +5 -1
  71. sima_cli-2.1.20/tests/unit/test_netboot_autoflash.py +196 -0
  72. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_netboot_device.py +74 -212
  73. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_netboot_trootctl.py +4 -1
  74. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_overlay.py +41 -2
  75. sima_cli-2.1.20/tests/unit/test_parallel_downloads.py +131 -0
  76. sima_cli-2.1.20/tests/unit/test_peripherals_commands.py +313 -0
  77. sima_cli-2.1.20/tests/unit/test_peripherals_contract.py +105 -0
  78. sima_cli-2.1.20/tests/unit/test_peripherals_model.py +238 -0
  79. sima_cli-2.1.20/tests/unit/test_peripherals_sentinel.py +127 -0
  80. sima_cli-2.1.20/tests/unit/test_reset_overlay.py +159 -0
  81. sima_cli-2.1.20/tests/unit/test_sdk_agent_sessions.py +884 -0
  82. sima_cli-2.1.20/tests/unit/test_sdk_browser_ca.py +119 -0
  83. sima_cli-2.1.20/tests/unit/test_sdk_container_registry.py +544 -0
  84. sima_cli-2.1.20/tests/unit/test_sdk_host_key_trust.py +128 -0
  85. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_sdk_image_detection.py +858 -48
  86. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_sdk_model_compiler.py +24 -0
  87. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_sdk_network_doctor.py +145 -1
  88. sima_cli-2.1.20/tests/unit/test_sdk_preinstall.py +1325 -0
  89. sima_cli-2.1.20/tests/unit/test_selectable_resources.py +98 -0
  90. sima_cli-2.1.20/tests/unit/test_slot_swap.py +145 -0
  91. sima_cli-2.1.20/tests/unit/test_streaming_downloads.py +296 -0
  92. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_swu.py +77 -33
  93. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_swu_certificate.py +4 -3
  94. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_swu_mirror.py +16 -4
  95. sima_cli-2.1.20/tests/unit/test_swu_target.py +158 -0
  96. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_uboot_environment.py +43 -4
  97. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_update_staging.py +11 -4
  98. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_vscode_extension_pins.py +80 -24
  99. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_vulcan.py +3 -0
  100. sima_cli-2.1.18/sima_cli/download/downloader.py +0 -208
  101. sima_cli-2.1.18/sima_cli/mla/meminfo.py +0 -47
  102. sima_cli-2.1.18/sima_cli/models/client.py +0 -600
  103. sima_cli-2.1.18/sima_cli/sdk/preinstall.py +0 -715
  104. sima_cli-2.1.18/tests/unit/test_netboot_autoflash.py +0 -54
  105. sima_cli-2.1.18/tests/unit/test_sdk_preinstall.py +0 -432
  106. sima_cli-2.1.18/tests/unit/test_swu_target.py +0 -58
  107. {sima_cli-2.1.18 → sima_cli-2.1.20}/LICENSE +0 -0
  108. {sima_cli-2.1.18 → sima_cli-2.1.20}/MANIFEST.in +0 -0
  109. {sima_cli-2.1.18 → sima_cli-2.1.20}/setup.cfg +0 -0
  110. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/__init__.py +0 -0
  111. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/__main__.py +0 -0
  112. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/app_zoo/__init__.py +0 -0
  113. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/app_zoo/app.py +0 -0
  114. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/app_zoo/commands.py +0 -0
  115. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/auth/__init__.py +0 -0
  116. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/auth/auth0.py +0 -0
  117. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/auth/devportal.py +0 -0
  118. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/auth/login.py +0 -0
  119. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/cloudex/__init__.py +0 -0
  120. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/cloudex/commands.py +0 -0
  121. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/data/__init__.py +0 -0
  122. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/data/resources_internal.yaml +0 -0
  123. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/data/resources_public.yaml +0 -0
  124. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/deploy_only/__init__.py +0 -0
  125. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/deploy_only/device/__init__.py +0 -0
  126. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/deploy_only/device/commands.py +0 -0
  127. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/deploy_only/mpk/__init__.py +0 -0
  128. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/deploy_only/mpk/commands.py +0 -0
  129. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/discover/__init__.py +0 -0
  130. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/download/__init__.py +0 -0
  131. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/install/__init__.py +0 -0
  132. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/install/compatibility.py +0 -0
  133. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/install/github_assets.py +0 -0
  134. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/install/hostdriver.py +0 -0
  135. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/install/metadata_info.py +0 -0
  136. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/install/metadata_validator.py +0 -0
  137. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/install/optiview.py +0 -0
  138. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/install/package_builder.py +0 -0
  139. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/install/palette.py +0 -0
  140. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/install/registry.py +0 -0
  141. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/mla/__init__.py +0 -0
  142. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/model_zoo/__init__.py +0 -0
  143. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/model_zoo/model.py +0 -0
  144. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/models/__init__.py +0 -0
  145. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/network/__init__.py +0 -0
  146. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/network/network.py +0 -0
  147. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/playbooks/__init__.py +0 -0
  148. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/playbooks/commands.py +0 -0
  149. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/playbooks/manager.py +0 -0
  150. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/__init__.py +0 -0
  151. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/cmdexec.py +0 -0
  152. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/config.py +0 -0
  153. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/docker_staging.py +0 -0
  154. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/linux_devkit_network.py +0 -0
  155. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/linux_shared_network.py +0 -0
  156. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/model_compiler.py +0 -0
  157. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/requirements.json +0 -0
  158. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/script.py +0 -0
  159. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/stop.py +0 -0
  160. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/sdk/uninstall.py +0 -0
  161. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/serial/__init__.py +0 -0
  162. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/serial/serial.py +0 -0
  163. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/storage/__init__.py +0 -0
  164. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/storage/nvme.py +0 -0
  165. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/storage/sdcard.py +0 -0
  166. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/__init__.py +0 -0
  167. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/ab_state.py +0 -0
  168. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/bmaptool.py +0 -0
  169. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/bootimg.py +0 -0
  170. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/cleanlog.py +0 -0
  171. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/elxr.py +0 -0
  172. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/fast_upload.py +0 -0
  173. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/local.py +0 -0
  174. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/netboot_artifacts.py +0 -0
  175. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/query.py +0 -0
  176. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/rootfs.py +0 -0
  177. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/staging.py +0 -0
  178. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/update/updater.py +0 -0
  179. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/upgrade/__init__.py +0 -0
  180. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/upgrade/selfupdate.py +0 -0
  181. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/__init__.py +0 -0
  182. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/api_common.py +0 -0
  183. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/artifactory.py +0 -0
  184. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/common.py +0 -0
  185. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/config.py +0 -0
  186. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/config_loader.py +0 -0
  187. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/container_registries.py +0 -0
  188. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/deprecation.py +0 -0
  189. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/device_api.py +0 -0
  190. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/disk.py +0 -0
  191. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/docker.py +0 -0
  192. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/env.py +0 -0
  193. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/errors.py +0 -0
  194. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/mpk_api.py +0 -0
  195. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/net.py +0 -0
  196. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/network.py +0 -0
  197. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/pcie.py +0 -0
  198. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/pkg_update_check.py +0 -0
  199. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/serializers.py +0 -0
  200. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/services.py +0 -0
  201. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/utils/tag.py +0 -0
  202. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli/vulcan/__init__.py +0 -0
  203. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli.egg-info/dependency_links.txt +0 -0
  204. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli.egg-info/entry_points.txt +0 -0
  205. {sima_cli-2.1.18 → sima_cli-2.1.20}/sima_cli.egg-info/top_level.txt +0 -0
  206. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/__init__.py +0 -0
  207. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/e2e/__init__.py +0 -0
  208. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/__init__.py +0 -0
  209. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_ab_overlay.py +0 -0
  210. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_app_zoo.py +0 -0
  211. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_appzoo_commands.py +0 -0
  212. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_auth.py +0 -0
  213. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_auth0.py +0 -0
  214. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_bootimg_defaults.py +0 -0
  215. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_bootimg_recovery.py +0 -0
  216. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_cli.py +0 -0
  217. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_cli_stdio.py +0 -0
  218. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_cli_update.py +0 -0
  219. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_cli_update_rerun.py +0 -0
  220. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_container_registries.py +0 -0
  221. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_deprecation.py +0 -0
  222. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_device_discovery_cloudex.py +0 -0
  223. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_docker_utils.py +0 -0
  224. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_elxr_update.py +0 -0
  225. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_fast_upload.py +0 -0
  226. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_firmware.py +0 -0
  227. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_firmware_creation_order.py +0 -0
  228. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_firmware_layout.py +0 -0
  229. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_firmware_selector.py +0 -0
  230. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_install_stub.py +0 -0
  231. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_localized_docs_links.py +0 -0
  232. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_metadata_installer.py +0 -0
  233. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_metadata_validator.py +0 -0
  234. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_model_zoo.py +0 -0
  235. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_modelzoo_commands.py +0 -0
  236. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_netboot_images.py +0 -0
  237. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_netboot_ip_recovery.py +0 -0
  238. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_netboot_mirror.py +0 -0
  239. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_network_custom_static.py +0 -0
  240. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_network_safety.py +0 -0
  241. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_package_builder.py +0 -0
  242. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_palette_image_discovery.py +0 -0
  243. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_pcie.py +0 -0
  244. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_pkg_update_check.py +0 -0
  245. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_sdk_linux_shared_network.py +0 -0
  246. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_sdk_uninstall.py +0 -0
  247. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_selfupdate.py +0 -0
  248. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_skills_commands.py +0 -0
  249. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_skills_manager.py +0 -0
  250. {sima_cli-2.1.18 → sima_cli-2.1.20}/tests/unit/test_swu_errors.py +0 -0
  251. {sima_cli-2.1.18 → sima_cli-2.1.20}/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.18
3
+ Version: 2.1.20
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
@@ -27,7 +27,8 @@ Requires-Dist: rich==14.0.0
27
27
  Requires-Dist: InquirerPy==0.3.4
28
28
  Requires-Dist: tftpy==0.8.6
29
29
  Requires-Dist: psutil==7.0.0
30
- Requires-Dist: huggingface-hub<1.0,>=0.34.0
30
+ Requires-Dist: huggingface-hub<3.0,>=2.2.0; python_version >= "3.10"
31
+ Requires-Dist: huggingface-hub<2.0,>=0.34.0; python_version < "3.10"
31
32
  Requires-Dist: tabulate==0.9.0
32
33
  Dynamic: author
33
34
  Dynamic: license-file
@@ -148,6 +149,20 @@ sima-cli --internal login
148
149
  SIMA_CLI_INTERNAL=1 sima-cli install -v 2.1.2 tools/model-compiler/amd64
149
150
  ```
150
151
 
152
+ Full-system SWU updates prefer RAM-backed `/tmp` when available memory can
153
+ hold both the bundle and its extraction, plus a reserve of at least 512 MiB
154
+ or 10% of total RAM (whichever is larger). Otherwise they stage in `/data`,
155
+ with NVMe as a further fallback if `/data` lacks space. RAM staging is temporary;
156
+ interrupted updates retained there do not survive a reboot.
157
+
158
+ Public-mirror downloads, `models download`, and `neat artifacts` split files
159
+ of at least 64 MiB into four byte ranges when supported by the server. Models
160
+ and Neat artifacts share a limit of four active download connections across
161
+ files and ranges, including single-connection fallbacks. Ranges must match the
162
+ expected size and an object validator or the expected SHA-256 before the file is published. Models retain signed-URL refresh and
163
+ cancellation handling. Existing public-mirror partial downloads continue
164
+ sequentially; servers without range support use a single connection.
165
+
151
166
  ## Common Workflows
152
167
 
153
168
  Set up SDK containers:
@@ -157,6 +172,14 @@ sima-cli sdk setup
157
172
  sima-cli sdk neat
158
173
  ```
159
174
 
175
+ When pairing with `sdk setup --devkit`, a new DevKit host key is automatically
176
+ recorded in the SDK user's `~/.ssh/known_hosts`, including during unattended
177
+ setup or after SDK container recreation. Subsequent pairing preserves saved
178
+ keys and rejects a changed key at the same address and SSH port. After a device
179
+ reimage or key rotation, verify the replacement fingerprint before explicitly
180
+ updating that entry. For environments requiring verified trust before the first
181
+ connection, provision verified host keys in the SDK user's `known_hosts`.
182
+
160
183
  Install Model Compiler from package metadata:
161
184
 
162
185
  ```bash
@@ -238,3 +261,13 @@ For command-specific prerequisites, see the generated [command reference](docs/s
238
261
  ## Support
239
262
 
240
263
  For issues and feature requests, use the sima-cli GitHub repository or contact the SiMa.ai development team.
264
+
265
+ ### Remote browser certificate help
266
+
267
+ SDK setup stages a verified public mkcert CA certificate alongside the SDK HTTPS
268
+ certificate. SDK images with browser certificate help can offer that file for
269
+ download on the sign-in page, with instructions for the browser computer.
270
+ Only the public certificate is staged; the mkcert private key stays on the host.
271
+ If the issuing CA cannot be verified, including a plain self-signed fallback,
272
+ no CA download is published. Re-running setup refreshes the staged file even
273
+ when the existing HTTPS certificate does not need to be reissued.
@@ -113,6 +113,20 @@ sima-cli --internal login
113
113
  SIMA_CLI_INTERNAL=1 sima-cli install -v 2.1.2 tools/model-compiler/amd64
114
114
  ```
115
115
 
116
+ Full-system SWU updates prefer RAM-backed `/tmp` when available memory can
117
+ hold both the bundle and its extraction, plus a reserve of at least 512 MiB
118
+ or 10% of total RAM (whichever is larger). Otherwise they stage in `/data`,
119
+ with NVMe as a further fallback if `/data` lacks space. RAM staging is temporary;
120
+ interrupted updates retained there do not survive a reboot.
121
+
122
+ Public-mirror downloads, `models download`, and `neat artifacts` split files
123
+ of at least 64 MiB into four byte ranges when supported by the server. Models
124
+ and Neat artifacts share a limit of four active download connections across
125
+ files and ranges, including single-connection fallbacks. Ranges must match the
126
+ expected size and an object validator or the expected SHA-256 before the file is published. Models retain signed-URL refresh and
127
+ cancellation handling. Existing public-mirror partial downloads continue
128
+ sequentially; servers without range support use a single connection.
129
+
116
130
  ## Common Workflows
117
131
 
118
132
  Set up SDK containers:
@@ -122,6 +136,14 @@ sima-cli sdk setup
122
136
  sima-cli sdk neat
123
137
  ```
124
138
 
139
+ When pairing with `sdk setup --devkit`, a new DevKit host key is automatically
140
+ recorded in the SDK user's `~/.ssh/known_hosts`, including during unattended
141
+ setup or after SDK container recreation. Subsequent pairing preserves saved
142
+ keys and rejects a changed key at the same address and SSH port. After a device
143
+ reimage or key rotation, verify the replacement fingerprint before explicitly
144
+ updating that entry. For environments requiring verified trust before the first
145
+ connection, provision verified host keys in the SDK user's `known_hosts`.
146
+
125
147
  Install Model Compiler from package metadata:
126
148
 
127
149
  ```bash
@@ -203,3 +225,13 @@ For command-specific prerequisites, see the generated [command reference](docs/s
203
225
  ## Support
204
226
 
205
227
  For issues and feature requests, use the sima-cli GitHub repository or contact the SiMa.ai development team.
228
+
229
+ ### Remote browser certificate help
230
+
231
+ SDK setup stages a verified public mkcert CA certificate alongside the SDK HTTPS
232
+ certificate. SDK images with browser certificate help can offer that file for
233
+ download on the sign-in page, with instructions for the browser computer.
234
+ Only the public certificate is staged; the mkcert private key stays on the host.
235
+ If the issuing CA cannot be verified, including a plain self-signed fallback,
236
+ no CA download is published. Re-running setup refreshes the staged file even
237
+ when the existing HTTPS certificate does not need to be reissued.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sima-cli"
7
- version = "2.1.18"
7
+ version = "2.1.20"
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"
@@ -33,7 +33,8 @@ dependencies = [
33
33
  "InquirerPy==0.3.4",
34
34
  "tftpy==0.8.6",
35
35
  "psutil==7.0.0",
36
- "huggingface-hub>=0.34.0,<1.0",
36
+ "huggingface-hub>=2.2.0,<3.0; python_version >= '3.10'",
37
+ "huggingface-hub>=0.34.0,<2.0; python_version < '3.10'",
37
38
  "tabulate==0.9.0"
38
39
  ]
39
40
 
@@ -8,5 +8,6 @@ rich==14.0.0
8
8
  InquirerPy==0.3.4
9
9
  tftpy==0.8.6
10
10
  psutil==7.0.0
11
- huggingface-hub>=0.34.0,<1.0
11
+ huggingface-hub>=2.2.0,<3.0; python_version >= "3.10"
12
+ huggingface-hub>=0.34.0,<2.0; python_version < "3.10"
12
13
  tabulate==0.9.0
@@ -23,7 +23,8 @@ setup(
23
23
  "InquirerPy>=0.3.4,<0.4",
24
24
  "tftpy>=0.8.6,<0.9",
25
25
  "psutil>=7.0.0,<8.0",
26
- "huggingface-hub>=0.34.0,<1.0",
26
+ "huggingface-hub>=2.2.0,<3.0; python_version >= '3.10'",
27
+ "huggingface-hub>=0.34.0,<2.0; python_version < '3.10'",
27
28
  "rich>=13.7,<14.0"
28
29
  ],
29
30
  entry_points={
@@ -1,2 +1,2 @@
1
1
  # sima_cli/__version__.py
2
- __version__ = "2.1.18"
2
+ __version__ = "2.1.20"
@@ -14,7 +14,7 @@ from sima_cli.update.swu import handle_update
14
14
  from sima_cli.model_zoo.model import list_models, download_model, describe_model
15
15
  from sima_cli.app_zoo.app import list_apps, download_app, describe_app
16
16
  from sima_cli.utils.config_loader import internal_resource_exists
17
- from sima_cli.mla.meminfo import monitor_simaai_mem_chart
17
+ from sima_cli.mla.meminfo import MemoryTelemetryError, monitor_simaai_mem_chart
18
18
  from sima_cli.__version__ import __version__
19
19
  from sima_cli.utils.config import CONFIG_PATH
20
20
  from sima_cli.install.optiview import install_optiview
@@ -42,6 +42,7 @@ from sima_cli.upgrade.selfupdate import register_selfupdate_command
42
42
  from sima_cli.playbooks import register_playbook_commands
43
43
  from sima_cli.vulcan import register_vulcan_commands
44
44
  from sima_cli.models import register_models_commands
45
+ from sima_cli.peripherals import register_peripherals_commands
45
46
  from sima_cli.cloudex import register_cloudex_commands
46
47
  from sima_cli.vulcan.commands import (
47
48
  ENV_METAVAR,
@@ -105,6 +106,13 @@ def _rerun_current_command() -> None:
105
106
  os.execvpe(sys.executable, cmd, env)
106
107
 
107
108
 
109
+ # Command groups whose stdout is a machine-readable document: root diagnostics go to stderr for them.
110
+ QUIET_STDOUT_SUBCOMMANDS = ("models", "peripherals")
111
+ # Command groups that only read the DevKit they run on: no PyPI update check and no Artifactory
112
+ # gate before them, so they work offline and never prompt or exit early.
113
+ LOCAL_ONLY_SUBCOMMANDS = ("peripherals",)
114
+
115
+
108
116
  def _initialize_main_context(ctx, internal, yes):
109
117
  _configure_stdio_errors()
110
118
  auto_accept_update = yes or _update_auto_confirm_requested(sys.argv)
@@ -112,7 +120,8 @@ def _initialize_main_context(ctx, internal, yes):
112
120
  if auto_accept_update:
113
121
  os.environ["SIMA_CLI_AUTO_ACCEPT_UPDATE"] = "1"
114
122
  try:
115
- if check_for_update('sima-cli') and _should_rerun_after_update(sys.argv):
123
+ if (ctx.invoked_subcommand not in LOCAL_ONLY_SUBCOMMANDS
124
+ and check_for_update('sima-cli') and _should_rerun_after_update(sys.argv)):
116
125
  _rerun_current_command()
117
126
  finally:
118
127
  if auto_accept_update:
@@ -128,14 +137,15 @@ def _initialize_main_context(ctx, internal, yes):
128
137
  if not internal:
129
138
  internal = os.getenv("SIMA_CLI_INTERNAL", "0") in ("1", "true", "yes")
130
139
 
131
- defer_artifactory_access = ctx.invoked_subcommand == "update" or ctx.meta.get("daily_netboot", False)
140
+ local_only = ctx.invoked_subcommand in LOCAL_ONLY_SUBCOMMANDS
141
+ defer_artifactory_access = local_only or ctx.invoked_subcommand == "update" or ctx.meta.get("daily_netboot", False)
132
142
  if internal and not internal_resource_exists() and not defer_artifactory_access:
133
143
  click.echo("❌ You have specified -i or --internal argument to access internal resources, but you do not have an internal resource map configured.")
134
144
  click.echo("Refer to the confluence page to find out how to configure internal resource map.")
135
145
  exit(0)
136
146
 
137
147
  internal_reachable = True
138
- if internal:
148
+ if internal and not local_only:
139
149
  internal_reachable = check_artifactory_reachability()
140
150
 
141
151
  if internal and not internal_reachable and not defer_artifactory_access and not _allows_external_prerelease_fallback(sys.argv):
@@ -170,7 +180,7 @@ class InspectionAwareGroup(click.Group):
170
180
  _command_name_from_argv(['sima-cli'] + options) == 'bootimg'
171
181
  and any(arg in ('--netboot', '-n', '--autoflash', '-a') for arg in options)
172
182
  )
173
- if _command_name_from_argv(['sima-cli'] + options) == 'update' and '--inspect' in options:
183
+ if _command_name_from_argv(['sima-cli'] + options) == 'update' and any(arg in ('--inspect', '--swap', '--reset-overlay') for arg in options):
174
184
  ctx.meta['update_inspect'] = True
175
185
  return super().parse_args(ctx, args)
176
186
 
@@ -198,11 +208,11 @@ def main(ctx, internal, yes):
198
208
  ctx.ensure_object(dict)
199
209
  ctx.obj.update(internal=internal, yes=yes)
200
210
  return
201
- # Model Registry JSON must remain parseable on stdout. Route root-level
202
- # update and environment diagnostics to stderr for this command group.
211
+ # Model Registry and peripherals JSON must remain parseable on stdout. Route root-level
212
+ # update and environment diagnostics to stderr for these command groups.
203
213
  output_context = (
204
214
  redirect_stdout(sys.stderr)
205
- if ctx.invoked_subcommand == "models"
215
+ if ctx.invoked_subcommand in QUIET_STDOUT_SUBCOMMANDS
206
216
  else nullcontext()
207
217
  )
208
218
  with output_context:
@@ -216,6 +226,7 @@ register_sdk_commands(main)
216
226
  register_playbook_commands(main)
217
227
  register_vulcan_commands(main)
218
228
  register_models_commands(main)
229
+ register_peripherals_commands(main)
219
230
  register_cloudex_commands(main)
220
231
 
221
232
 
@@ -352,14 +363,16 @@ def download(ctx, url, dest):
352
363
  "--dryrun",
353
364
  is_flag=True,
354
365
  default=False,
355
- help="For eLxr updates, validate the update path and show the command without installing."
366
+ help="For eLxr, preview an update, slot swap, or overlay reset without making changes."
356
367
  )
357
368
  @click.option("--inspect", "inspect_state", is_flag=True, help="Show eLxr 3.0+ A/B slots and overlay customizations without updating (local or --ip).")
369
+ @click.option("--reset-overlay", is_flag=True, help="Discard local overlay software and settings without updating firmware; reboot to use the clean system (eLxr 3.0+).")
370
+ @click.option("--swap", is_flag=True, help="Select the available inactive eLxr 3.0+ slot for next boot; use --reboot to restart now.")
358
371
  @click.option("--verbose", is_flag=True, help="Show detailed overlay file categories during eLxr 3.0+ inspection or update.")
359
372
  @click.option("--signing-cert", metavar="URL_OR_FILE", help="SWUpdate PEM verification certificate: HTTP(S) URL or local file. Defaults to the certificate for the selected channel when configured (eLxr 3.0+).")
360
- @click.option("--reboot", is_flag=True, help="Reboot after successful eLxr 3.0+ installation; verify remote boot health.")
373
+ @click.option("--reboot", is_flag=True, help="Reboot after successful eLxr 3.0+ installation, slot selection, or overlay reset.")
361
374
  @click.pass_context
362
- def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force, troot_only, dryrun, inspect_state, verbose, reboot, signing_cert):
375
+ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force, troot_only, dryrun, inspect_state, verbose, reboot, signing_cert, swap, reset_overlay):
363
376
  """
364
377
  Update a SiMa DevKit, remote device, or Linux PCIe host.
365
378
 
@@ -370,9 +383,23 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
370
383
 
371
384
  eLxr 3.0+ uses signed full-system SWU bundles. Use --inspect for A/B
372
385
  state, --ip for remote updates, and --reboot to reboot after installation.
386
+
387
+ Use --reset-overlay to discard local software and settings without updating
388
+ firmware or switching slots. Save needed files first; the saved inventory
389
+ contains metadata only. Reboot promptly, or add --reboot. Use --dryrun to preview.
390
+
391
+ Use --swap to select the valid inactive slot without downloading an update.
392
+ The current and target versions are shown before confirmation; --yes skips
393
+ confirmation. Reboot separately to apply, or add --reboot to restart now.
394
+ Shared data and overlay customizations persist and can override files in
395
+ the selected version. Blank, invalid, pending, and unknown states are refused.
396
+
397
+ Examples: sima-cli update --swap; sima-cli update --ip 192.168.6.5 --swap --reboot
398
+
373
399
  The verification certificate is downloaded for the selected channel when configured; use
374
400
  --signing-cert URL_OR_FILE for an image signed with your own certificate.
375
- Developer-portal version lookup for 3.0 is not available yet.
401
+ For official eLxr 3.0+ releases, specify -v 3.0.0 (or -v 3.0) to
402
+ download the SWU from docs.sima.ai using your developer-portal login.
376
403
 
377
404
  Internal mode warns that pre-release software may be unstable and is used
378
405
  at your own risk. On eLxr 3.0+, unavailable Artifactory access falls back
@@ -479,6 +506,8 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
479
506
  dryrun
480
507
  or troot_only
481
508
  or inspect_state
509
+ or swap
510
+ or reset_overlay
482
511
  or verbose
483
512
  or reboot
484
513
  or force
@@ -496,6 +525,10 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
496
525
  return
497
526
 
498
527
  is_elxr = is_devkit_running_elxr()
528
+ if reset_overlay and (inspect_state or swap or version_or_url or force or troot_only or signing_cert is not None or flavor != "auto"):
529
+ raise click.UsageError("--reset-overlay cannot be combined with --inspect, --swap, or installation options.")
530
+ if swap and (inspect_state or version_or_url or force or troot_only or signing_cert is not None or flavor != "auto"):
531
+ raise click.UsageError("--swap cannot be combined with --inspect or installation options.")
499
532
  if inspect_state and (version_or_url or dryrun or force or troot_only or reboot or signing_cert is not None or flavor != 'auto'):
500
533
  raise click.UsageError("--inspect cannot be combined with installation options.")
501
534
  try:
@@ -503,7 +536,7 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
503
536
  internal=ctx.obj.get("internal", False),
504
537
  auto_confirm=yes or ctx.obj.get("yes", False), dryrun=dryrun,
505
538
  signing_cert=signing_cert, reboot=reboot, inspect=inspect_state, force=force,
506
- troot_only=troot_only, flavor=flavor, local_elxr=is_elxr, verbose=verbose):
539
+ troot_only=troot_only, flavor=flavor, local_elxr=is_elxr, verbose=verbose, swap=swap, reset_overlay=reset_overlay):
507
540
  return
508
541
  except (click.ClickException, click.Abort):
509
542
  raise
@@ -632,8 +665,10 @@ def mla(ctx):
632
665
  @click.pass_context
633
666
  def show_mla_memory_usage(ctx):
634
667
  """Show MLA Memory usage overtime."""
635
- monitor_simaai_mem_chart()
636
- pass
668
+ try:
669
+ monitor_simaai_mem_chart()
670
+ except MemoryTelemetryError as exc:
671
+ raise click.ClickException(str(exc)) from exc
637
672
 
638
673
 
639
674
  # ----------------------
@@ -789,6 +824,8 @@ def bootimg_cmd(ctx, version, boardtype, netboot, images, delete_cache, devkit_i
789
824
  else:
790
825
  write_image(version, boardtype, fwtype, internal, flavor='headless', recovery=recovery)
791
826
  click.echo("✅ Boot image successfully written.")
827
+ except click.ClickException:
828
+ raise
792
829
  except Exception as e:
793
830
  click.echo(f"❌ Failed to write boot image: {e}", err=True)
794
831
  ctx.exit(1)
@@ -7,6 +7,7 @@ import json
7
7
  import os
8
8
  import shutil
9
9
  import subprocess
10
+ import sys
10
11
  import time
11
12
  import uuid
12
13
  from pathlib import Path
@@ -17,6 +18,8 @@ import requests
17
18
 
18
19
  DEFAULT_ROOT = Path.home() / ".sima-cli" / "cloudex"
19
20
  FORWARDER_NAME = "kerrigan-p2p-forwarder"
21
+ MACOS_TUNNEL_PATH = "/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:/usr/local/bin"
22
+ MACOS_WIREGUARD_INSTALL = "Install WireGuard CLI support with: brew install wireguard-tools"
20
23
  CONNECTION_SETUP_TIMEOUT_SECONDS = 60
21
24
  DIAGNOSTIC_LOG_LINES = 50
22
25
  SAFE_FORWARDER_STAGES = {
@@ -68,8 +71,8 @@ def decode_key(value):
68
71
  raise CloudExError("--key contains an invalid value")
69
72
  if len(profile["allocation_secret"]) < 32:
70
73
  raise CloudExError("--key contains an invalid allocation secret")
71
- if profile["environment"] != "vulcan-staging":
72
- raise CloudExError("CloudEx currently supports only vulcan-staging allocation keys")
74
+ if profile["environment"] not in {"vulcan-staging", "vulcan-production"}:
75
+ raise CloudExError("CloudEx supports only vulcan-staging or vulcan-production allocation keys")
73
76
  return profile
74
77
 
75
78
 
@@ -393,6 +396,9 @@ def _forwarder_failure(log_path):
393
396
  "start wireguard-go": "the WireGuard interface could not start",
394
397
  "invalid session config": "the native forwarder rejected the session configuration",
395
398
  }
399
+ if sys.platform == "darwin":
400
+ for needle in ("wireguard is unavailable", "start wireguard-go"):
401
+ mappings[needle] += ". " + MACOS_WIREGUARD_INSTALL
396
402
  for needle, message in mappings.items():
397
403
  if needle in detail:
398
404
  return message + suffix
@@ -552,22 +558,61 @@ def _reconcile_existing_connection(profile, store, progress):
552
558
  return None
553
559
 
554
560
 
561
+ def _forwarder_sudo_prefix(progress=None):
562
+ """Install missing macOS tools as the user, then expose them through sudo."""
563
+ prefix = ["sudo", "-n", "--"]
564
+ if sys.platform != "darwin":
565
+ return prefix
566
+ missing = [name for name in ("wg", "wireguard-go")
567
+ if shutil.which(name, path=MACOS_TUNNEL_PATH) is None]
568
+ if missing:
569
+ brew = shutil.which("brew", path=MACOS_TUNNEL_PATH)
570
+ if brew is None:
571
+ raise CloudExError(
572
+ "CloudEx requires {} on macOS. Install Homebrew, then retry CloudEx; "
573
+ "missing WireGuard dependencies will be installed automatically.".format(
574
+ ", ".join(missing)
575
+ )
576
+ )
577
+ packages = ["wireguard-tools" if name == "wg" else name for name in missing]
578
+ if progress:
579
+ progress.update("Installing missing WireGuard dependencies with Homebrew")
580
+ environment = dict(os.environ, PATH=MACOS_TUNNEL_PATH)
581
+ try:
582
+ result = subprocess.run(
583
+ [brew, "install", *packages],
584
+ env=environment, stdin=subprocess.DEVNULL,
585
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=600,
586
+ )
587
+ except (OSError, subprocess.TimeoutExpired) as exc:
588
+ raise CloudExError(
589
+ "Could not install WireGuard dependencies with Homebrew. " + MACOS_WIREGUARD_INSTALL
590
+ ) from exc
591
+ if result.returncode:
592
+ detail = (result.stderr or result.stdout or "Homebrew failed").strip()
593
+ raise CloudExError("WireGuard dependency installation failed: " + detail[-1024:])
594
+ missing = [name for name in ("wg", "wireguard-go")
595
+ if shutil.which(name, path=MACOS_TUNNEL_PATH) is None]
596
+ if missing:
597
+ raise CloudExError(
598
+ "Homebrew completed but CloudEx still cannot find: {}. {}".format(
599
+ ", ".join(missing), MACOS_WIREGUARD_INSTALL
600
+ )
601
+ )
602
+ return prefix + ["/usr/bin/env", "PATH=" + MACOS_TUNNEL_PATH]
603
+
604
+
555
605
  def connect(profile, store, progress, attempts=3, transport="auto"):
556
606
  """Create a session and return its durable local record."""
557
- try:
558
- forwarder = find_forwarder()
559
- except CloudExError:
560
- # Import lazily: the installer reuses this module's privilege and error
561
- # handling, while ordinary CloudEx commands should remain quick to load.
562
- from .installer import DEFAULT_BRANCH, install_forwarder
607
+ from .installer import ensure_forwarder
563
608
 
564
- progress.update("CloudEx forwarder is missing; installing from {}".format(DEFAULT_BRANCH))
565
- forwarder = install_forwarder(DEFAULT_BRANCH, progress=progress)["path"]
566
609
  authorize_admin()
567
610
  api = CloudExAPI.from_profile(profile)
568
611
  existing = _reconcile_existing_connection(profile, store, progress)
569
612
  if existing:
570
613
  return existing
614
+ sudo_prefix = _forwarder_sudo_prefix(progress=progress)
615
+ forwarder = ensure_forwarder(progress=progress)
571
616
  choices = (["p2p"] * attempts + ["routed"] if transport == "auto"
572
617
  else ["routed"] if transport == "turn" else ["p2p"] * attempts)
573
618
  last_error = None
@@ -616,8 +661,8 @@ def connect(profile, store, progress, attempts=3, transport="auto"):
616
661
  config_path.chmod(0o600)
617
662
  with log_path.open("x", encoding="utf-8") as log:
618
663
  process = subprocess.Popen(
619
- [
620
- "sudo", "-n", "--", str(forwarder), "session", "--role", "user",
664
+ sudo_prefix + [
665
+ str(forwarder), "session", "--role", "user",
621
666
  "--config", str(config_path), "--report", str(report_path),
622
667
  ],
623
668
  stdin=subprocess.DEVNULL,
@@ -1,6 +1,7 @@
1
1
  """Install the native CloudEx forwarder from Kerrigan's Vulcan artifacts."""
2
2
 
3
3
  import hashlib
4
+ import os
4
5
  import platform
5
6
  import re
6
7
  import subprocess
@@ -99,7 +100,30 @@ def _download_resource(client, base_url, resource, expected_sha256, destination)
99
100
  return path
100
101
 
101
102
 
102
- def install_forwarder(branch=DEFAULT_BRANCH, progress=None, client=None):
103
+ def ensure_forwarder(progress=None):
104
+ """Resolve the current published binary before starting a new tunnel."""
105
+ try:
106
+ installed = find_forwarder()
107
+ except CloudExError:
108
+ installed = None
109
+ else:
110
+ if os.environ.get("SIMA_CLOUDEX_FORWARDER"):
111
+ # Preserve an existing controlled binary, but allow the normal
112
+ # installer to bootstrap a missing controlled installation.
113
+ return installed
114
+ return install_forwarder(
115
+ DEFAULT_BRANCH, progress=progress, current_path=installed
116
+ )["path"]
117
+
118
+
119
+ def _matches_binary(path, checksum):
120
+ try:
121
+ return hashlib.sha256(Path(path).read_bytes()).hexdigest() == checksum.lower()
122
+ except OSError:
123
+ return False
124
+
125
+
126
+ def install_forwarder(branch=DEFAULT_BRANCH, progress=None, client=None, current_path=None):
103
127
  """Install or update the forwarder and return artifact/install details."""
104
128
  branch = _validate_branch(branch)
105
129
  variant = _platform_variant()
@@ -119,6 +143,12 @@ def install_forwarder(branch=DEFAULT_BRANCH, progress=None, client=None):
119
143
  metadata, resources, script_resource = _checked_metadata(
120
144
  client, result.metadata_url, variant
121
145
  )
146
+ expected_sha256 = metadata["resources-checksum"][resources[0]]
147
+ if current_path is not None and _matches_binary(current_path, expected_sha256):
148
+ if progress:
149
+ progress.update("CloudEx forwarder is up to date")
150
+ return {"path": current_path, "branch": branch,
151
+ "version": metadata["version"], "artifact": result.resolved_spec}
122
152
  base_url = result.metadata_url.rsplit("/", 1)[0]
123
153
  with tempfile.TemporaryDirectory(prefix="sima-cloudex-forwarder-") as temporary:
124
154
  root = Path(temporary)
@@ -156,6 +186,8 @@ def install_forwarder(branch=DEFAULT_BRANCH, progress=None, client=None):
156
186
  raise CloudExError("CloudEx forwarder installation failed: {}".format(detail[:1024]))
157
187
 
158
188
  installed = find_forwarder()
189
+ if current_path is not None and not _matches_binary(installed, expected_sha256):
190
+ raise CloudExError("CloudEx forwarder update did not replace the active binary; check PATH")
159
191
  return {
160
192
  "path": installed,
161
193
  "branch": branch,
@@ -5,6 +5,7 @@ import subprocess
5
5
  import json
6
6
  import time
7
7
  import re
8
+ import shutil
8
9
  import psutil
9
10
  from typing import List, Dict
10
11
  from rich.console import Console
@@ -51,6 +52,9 @@ def get_sima_devices_from_arp():
51
52
  try:
52
53
  output = subprocess.check_output(["ip", "neigh", "show"], text=True)
53
54
  except FileNotFoundError:
55
+ if not shutil.which("arp"):
56
+ console.print("[yellow]ARP lookup unavailable: install iproute2 (ip) or net-tools (arp); continuing with multicast discovery.[/yellow]")
57
+ return []
54
58
  output = subprocess.check_output(["arp", "-n"], text=True)
55
59
  pattern = re.compile(
56
60
  r"(?P<ip>\d+\.\d+\.\d+\.\d+).*?(?P<mac>(?:[0-9a-f]{1,2}:){5}[0-9a-f]{1,2})",
@@ -11,9 +11,41 @@ import re
11
11
  import time
12
12
  import shlex
13
13
  import subprocess
14
+ import shutil
15
+ import sys
16
+
17
+ import click
14
18
  from sima_cli.utils.env import get_environment_type
15
19
  from typing import Optional, Dict
16
20
 
21
+ def ensure_discovery_tools():
22
+ """Provision required Linux discovery tools only after an interactive opt-in."""
23
+ if shutil.which('ip'):
24
+ return
25
+ guidance = (
26
+ "Device discovery needs 'ip' (package iproute2). "
27
+ "On Ubuntu/Debian run: sudo apt-get update && sudo apt-get install -y iproute2 ethtool. "
28
+ "For netboot, pass --devkit-ip <IPv4> to bypass auto-discovery."
29
+ )
30
+ if not shutil.which('apt-get') or not sys.stdin.isatty():
31
+ raise click.ClickException(guidance)
32
+ packages = ['iproute2']
33
+ if not shutil.which('ethtool'):
34
+ packages.append('ethtool')
35
+ if not click.confirm('Device discovery needs host packages. Install ' + ' '.join(packages) + '?', default=False):
36
+ raise click.ClickException(guidance)
37
+ prefix = [] if os.geteuid() == 0 else ['sudo']
38
+ if prefix and not shutil.which('sudo'):
39
+ raise click.ClickException(guidance + ' sudo is unavailable; ask an administrator to install them.')
40
+ try:
41
+ subprocess.run(prefix + ['apt-get', 'update'], check=True)
42
+ subprocess.run(prefix + ['apt-get', 'install', '-y', '--no-install-recommends'] + packages, check=True)
43
+ except (OSError, subprocess.CalledProcessError) as error:
44
+ raise click.ClickException(f'Device discovery package installation failed: {error}. {guidance}') from error
45
+ if not shutil.which('ip'):
46
+ raise click.ClickException(guidance)
47
+
48
+
17
49
  def _run(cmd: str, check=False, capture=True) -> subprocess.CompletedProcess:
18
50
  return subprocess.run(
19
51
  shlex.split(cmd),
@@ -24,7 +56,7 @@ def _run(cmd: str, check=False, capture=True) -> subprocess.CompletedProcess:
24
56
  )
25
57
 
26
58
  def _has_nmcli() -> bool:
27
- return _run("which nmcli").returncode == 0
59
+ return shutil.which("nmcli") is not None
28
60
 
29
61
 
30
62
  def _iface_has_ipv4(iface: str) -> bool:
@@ -41,7 +73,7 @@ def _get_mac(iface: str) -> str:
41
73
  def _is_usb_ethernet(iface: str) -> bool:
42
74
  """Return True if this interface is a USB Ethernet device."""
43
75
  dev_path = f"/sys/class/net/{iface}/device"
44
- if not os.path.exists(dev_path):
76
+ if not os.path.exists(dev_path) or not shutil.which("udevadm"):
45
77
  return False
46
78
 
47
79
  cp = _run(f"udevadm info -q property -p {dev_path}", capture=True)
@@ -63,7 +95,7 @@ def _usb_attrs(iface: str) -> Dict[str, str]:
63
95
  Returns possibly empty dict.
64
96
  """
65
97
  dev_path = f"/sys/class/net/{iface}/device"
66
- if not os.path.exists(dev_path):
98
+ if not os.path.exists(dev_path) or not shutil.which("udevadm"):
67
99
  return {}
68
100
 
69
101
  cp = _run(f"udevadm info -q property -p {dev_path}", capture=True)
@@ -100,6 +132,8 @@ def _usb_attrs(iface: str) -> Dict[str, str]:
100
132
 
101
133
 
102
134
  def _driver_info(iface: str) -> Dict[str, str]:
135
+ if not shutil.which("ethtool"):
136
+ return {"driver": "", "bus-info": "", "firmware": ""}
103
137
  cp = _run(f"ethtool -i {iface}", capture=True)
104
138
  info = {}
105
139
  for line in cp.stdout.splitlines():
@@ -227,6 +261,7 @@ def suggest_and_switch_to_linklocal():
227
261
  envtype, env_subtype = get_environment_type()
228
262
 
229
263
  if envtype == 'host' and env_subtype == 'linux':
264
+ ensure_discovery_tools()
230
265
  cp = _run("ip -o link", capture=True)
231
266
  ifaces = []
232
267
  for line in cp.stdout.splitlines():