sima-cli 2.1.18__tar.gz → 2.1.19__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 (239) hide show
  1. {sima_cli-2.1.18/sima_cli.egg-info → sima_cli-2.1.19}/PKG-INFO +35 -2
  2. {sima_cli-2.1.18 → sima_cli-2.1.19}/README.md +32 -0
  3. {sima_cli-2.1.18 → sima_cli-2.1.19}/pyproject.toml +3 -2
  4. {sima_cli-2.1.18 → sima_cli-2.1.19}/requirements.txt +2 -1
  5. {sima_cli-2.1.18 → sima_cli-2.1.19}/setup.py +2 -1
  6. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/__version__.py +1 -1
  7. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/cli.py +40 -14
  8. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/cloudex/client.py +2 -8
  9. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/cloudex/installer.py +33 -1
  10. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/download/downloader.py +90 -33
  11. sima_cli-2.1.19/sima_cli/download/parallel.py +164 -0
  12. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/metadata_installer.py +5 -2
  13. sima_cli-2.1.19/sima_cli/mla/meminfo.py +274 -0
  14. sima_cli-2.1.19/sima_cli/models/client.py +891 -0
  15. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/models/commands.py +14 -2
  16. sima_cli-2.1.19/sima_cli/peripherals/__init__.py +5 -0
  17. sima_cli-2.1.19/sima_cli/peripherals/commands.py +208 -0
  18. sima_cli-2.1.19/sima_cli/peripherals/model.py +426 -0
  19. sima_cli-2.1.19/sima_cli/peripherals/sentinel.py +145 -0
  20. sima_cli-2.1.19/sima_cli/sdk/browser_ca.py +53 -0
  21. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/commands.py +85 -5
  22. sima_cli-2.1.19/sima_cli/sdk/container_registry.py +385 -0
  23. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/install.py +134 -17
  24. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/neat.py +119 -21
  25. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/network_doctor.py +104 -15
  26. sima_cli-2.1.19/sima_cli/sdk/preinstall.py +1759 -0
  27. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/utils.py +452 -63
  28. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/vscode_extensions.py +15 -13
  29. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/emmc.py +34 -2
  30. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/netboot.py +112 -52
  31. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/netboot_device.py +49 -244
  32. sima_cli-2.1.19/sima_cli/update/swap.py +81 -0
  33. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/swu.py +20 -4
  34. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/uboot_environment.py +11 -3
  35. sima_cli-2.1.19/sima_cli/utils/streaming.py +84 -0
  36. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/vulcan/artifacts.py +31 -18
  37. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/vulcan/commands.py +1 -1
  38. {sima_cli-2.1.18 → sima_cli-2.1.19/sima_cli.egg-info}/PKG-INFO +35 -2
  39. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli.egg-info/SOURCES.txt +25 -0
  40. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli.egg-info/requires.txt +6 -1
  41. sima_cli-2.1.19/tests/unit/peripherals_fixtures.py +131 -0
  42. sima_cli-2.1.19/tests/unit/test_artifact_parallel_integration.py +174 -0
  43. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cloudex_client.py +6 -6
  44. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cloudex_installer.py +75 -0
  45. sima_cli-2.1.19/tests/unit/test_code_ui_readiness.py +81 -0
  46. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_download.py +1 -0
  47. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_emmc.py +78 -12
  48. sima_cli-2.1.19/tests/unit/test_install_force.py +39 -0
  49. sima_cli-2.1.19/tests/unit/test_mla_meminfo.py +159 -0
  50. sima_cli-2.1.19/tests/unit/test_model_deployments.py +794 -0
  51. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_models.py +138 -77
  52. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot.py +5 -1
  53. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot_autoflash.py +35 -4
  54. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot_device.py +74 -212
  55. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot_trootctl.py +4 -1
  56. sima_cli-2.1.19/tests/unit/test_parallel_downloads.py +131 -0
  57. sima_cli-2.1.19/tests/unit/test_peripherals_commands.py +293 -0
  58. sima_cli-2.1.19/tests/unit/test_peripherals_contract.py +96 -0
  59. sima_cli-2.1.19/tests/unit/test_peripherals_model.py +219 -0
  60. sima_cli-2.1.19/tests/unit/test_peripherals_sentinel.py +127 -0
  61. sima_cli-2.1.19/tests/unit/test_sdk_browser_ca.py +119 -0
  62. sima_cli-2.1.19/tests/unit/test_sdk_container_registry.py +544 -0
  63. sima_cli-2.1.19/tests/unit/test_sdk_host_key_trust.py +128 -0
  64. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_sdk_image_detection.py +491 -11
  65. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_sdk_network_doctor.py +145 -1
  66. sima_cli-2.1.19/tests/unit/test_sdk_preinstall.py +1325 -0
  67. sima_cli-2.1.19/tests/unit/test_slot_swap.py +145 -0
  68. sima_cli-2.1.19/tests/unit/test_streaming_downloads.py +296 -0
  69. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_swu.py +22 -26
  70. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_swu_mirror.py +3 -2
  71. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_uboot_environment.py +43 -4
  72. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_update_staging.py +11 -4
  73. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_vscode_extension_pins.py +79 -24
  74. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_vulcan.py +3 -0
  75. sima_cli-2.1.18/sima_cli/mla/meminfo.py +0 -47
  76. sima_cli-2.1.18/sima_cli/models/client.py +0 -600
  77. sima_cli-2.1.18/sima_cli/sdk/preinstall.py +0 -715
  78. sima_cli-2.1.18/tests/unit/test_sdk_preinstall.py +0 -432
  79. {sima_cli-2.1.18 → sima_cli-2.1.19}/LICENSE +0 -0
  80. {sima_cli-2.1.18 → sima_cli-2.1.19}/MANIFEST.in +0 -0
  81. {sima_cli-2.1.18 → sima_cli-2.1.19}/setup.cfg +0 -0
  82. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/__init__.py +0 -0
  83. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/__main__.py +0 -0
  84. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/app_zoo/__init__.py +0 -0
  85. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/app_zoo/app.py +0 -0
  86. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/app_zoo/commands.py +0 -0
  87. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/auth/__init__.py +0 -0
  88. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/auth/auth0.py +0 -0
  89. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/auth/devportal.py +0 -0
  90. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/auth/login.py +0 -0
  91. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/cloudex/__init__.py +0 -0
  92. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/cloudex/commands.py +0 -0
  93. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/data/__init__.py +0 -0
  94. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/data/resources_internal.yaml +0 -0
  95. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/data/resources_public.yaml +0 -0
  96. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/deploy_only/__init__.py +0 -0
  97. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/deploy_only/device/__init__.py +0 -0
  98. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/deploy_only/device/commands.py +0 -0
  99. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/deploy_only/mpk/__init__.py +0 -0
  100. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/deploy_only/mpk/commands.py +0 -0
  101. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/discover/__init__.py +0 -0
  102. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/discover/discover.py +0 -0
  103. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/discover/linuxll.py +0 -0
  104. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/download/__init__.py +0 -0
  105. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/__init__.py +0 -0
  106. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/compatibility.py +0 -0
  107. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/github_assets.py +0 -0
  108. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/hostdriver.py +0 -0
  109. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/metadata_info.py +0 -0
  110. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/metadata_validator.py +0 -0
  111. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/optiview.py +0 -0
  112. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/package_builder.py +0 -0
  113. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/palette.py +0 -0
  114. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/registry.py +0 -0
  115. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/mla/__init__.py +0 -0
  116. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/model_zoo/__init__.py +0 -0
  117. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/model_zoo/model.py +0 -0
  118. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/models/__init__.py +0 -0
  119. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/models/rendering.py +0 -0
  120. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/network/__init__.py +0 -0
  121. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/network/network.py +0 -0
  122. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/playbooks/__init__.py +0 -0
  123. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/playbooks/commands.py +0 -0
  124. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/playbooks/manager.py +0 -0
  125. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/__init__.py +0 -0
  126. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/cmdexec.py +0 -0
  127. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/config.py +0 -0
  128. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/docker_staging.py +0 -0
  129. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/linux_devkit_network.py +0 -0
  130. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/linux_shared_network.py +0 -0
  131. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/model_compiler.py +0 -0
  132. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/requirements.json +0 -0
  133. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/script.py +0 -0
  134. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/stop.py +0 -0
  135. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/uninstall.py +0 -0
  136. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/serial/__init__.py +0 -0
  137. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/serial/serial.py +0 -0
  138. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/storage/__init__.py +0 -0
  139. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/storage/nvme.py +0 -0
  140. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/storage/sdcard.py +0 -0
  141. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/__init__.py +0 -0
  142. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/ab_state.py +0 -0
  143. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/bmaptool.py +0 -0
  144. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/bootimg.py +0 -0
  145. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/cleanlog.py +0 -0
  146. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/elxr.py +0 -0
  147. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/fast_upload.py +0 -0
  148. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/host.py +0 -0
  149. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/local.py +0 -0
  150. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/netboot_artifacts.py +0 -0
  151. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/overlay.py +0 -0
  152. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/query.py +0 -0
  153. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/remote.py +0 -0
  154. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/rootfs.py +0 -0
  155. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/staging.py +0 -0
  156. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/swu_artifacts.py +0 -0
  157. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/swu_certificate.py +0 -0
  158. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/swu_target.py +0 -0
  159. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/updater.py +0 -0
  160. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/upgrade/__init__.py +0 -0
  161. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/upgrade/selfupdate.py +0 -0
  162. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/__init__.py +0 -0
  163. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/api_common.py +0 -0
  164. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/artifactory.py +0 -0
  165. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/common.py +0 -0
  166. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/config.py +0 -0
  167. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/config_loader.py +0 -0
  168. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/container_registries.py +0 -0
  169. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/deprecation.py +0 -0
  170. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/device_api.py +0 -0
  171. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/disk.py +0 -0
  172. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/docker.py +0 -0
  173. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/env.py +0 -0
  174. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/errors.py +0 -0
  175. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/mpk_api.py +0 -0
  176. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/net.py +0 -0
  177. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/network.py +0 -0
  178. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/pcie.py +0 -0
  179. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/pkg_update_check.py +0 -0
  180. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/serializers.py +0 -0
  181. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/services.py +0 -0
  182. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/tag.py +0 -0
  183. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/vulcan/__init__.py +0 -0
  184. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli.egg-info/dependency_links.txt +0 -0
  185. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli.egg-info/entry_points.txt +0 -0
  186. {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli.egg-info/top_level.txt +0 -0
  187. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/__init__.py +0 -0
  188. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/e2e/__init__.py +0 -0
  189. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/__init__.py +0 -0
  190. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_ab_overlay.py +0 -0
  191. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_app_zoo.py +0 -0
  192. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_appzoo_commands.py +0 -0
  193. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_auth.py +0 -0
  194. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_auth0.py +0 -0
  195. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_bootimg_defaults.py +0 -0
  196. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_bootimg_recovery.py +0 -0
  197. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cli.py +0 -0
  198. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cli_stdio.py +0 -0
  199. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cli_update.py +0 -0
  200. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cli_update_rerun.py +0 -0
  201. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cloudex_commands.py +0 -0
  202. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_container_registries.py +0 -0
  203. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_deprecation.py +0 -0
  204. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_device_discovery_cloudex.py +0 -0
  205. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_docker_utils.py +0 -0
  206. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_elxr_update.py +0 -0
  207. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_fast_upload.py +0 -0
  208. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_firmware.py +0 -0
  209. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_firmware_creation_order.py +0 -0
  210. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_firmware_layout.py +0 -0
  211. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_firmware_selector.py +0 -0
  212. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_generate_cli_markdown_docs.py +0 -0
  213. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_host_update.py +0 -0
  214. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_install_stub.py +0 -0
  215. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_localized_docs_links.py +0 -0
  216. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_metadata_installer.py +0 -0
  217. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_metadata_validator.py +0 -0
  218. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_model_zoo.py +0 -0
  219. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_modelzoo_commands.py +0 -0
  220. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot_images.py +0 -0
  221. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot_ip_recovery.py +0 -0
  222. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot_mirror.py +0 -0
  223. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_network_custom_static.py +0 -0
  224. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_network_safety.py +0 -0
  225. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_overlay.py +0 -0
  226. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_package_builder.py +0 -0
  227. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_palette_image_discovery.py +0 -0
  228. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_pcie.py +0 -0
  229. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_pkg_update_check.py +0 -0
  230. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_sdk_linux_shared_network.py +0 -0
  231. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_sdk_model_compiler.py +0 -0
  232. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_sdk_uninstall.py +0 -0
  233. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_selfupdate.py +0 -0
  234. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_skills_commands.py +0 -0
  235. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_skills_manager.py +0 -0
  236. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_swu_certificate.py +0 -0
  237. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_swu_errors.py +0 -0
  238. {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_swu_target.py +0 -0
  239. {sima_cli-2.1.18 → sima_cli-2.1.19}/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.19
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.19"
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.19"
@@ -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') 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,15 @@ 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 or slot swap without installing, switching slots, or rebooting."
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("--swap", is_flag=True, help="Select the available inactive eLxr 3.0+ slot for next boot; use --reboot to restart now.")
358
370
  @click.option("--verbose", is_flag=True, help="Show detailed overlay file categories during eLxr 3.0+ inspection or update.")
359
371
  @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.")
372
+ @click.option("--reboot", is_flag=True, help="Reboot after successful eLxr 3.0+ installation or slot selection; verify remote boot health.")
361
373
  @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):
374
+ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force, troot_only, dryrun, inspect_state, verbose, reboot, signing_cert, swap):
363
375
  """
364
376
  Update a SiMa DevKit, remote device, or Linux PCIe host.
365
377
 
@@ -370,6 +382,15 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
370
382
 
371
383
  eLxr 3.0+ uses signed full-system SWU bundles. Use --inspect for A/B
372
384
  state, --ip for remote updates, and --reboot to reboot after installation.
385
+
386
+ Use --swap to select the valid inactive slot without downloading an update.
387
+ The current and target versions are shown before confirmation; --yes skips
388
+ confirmation. Reboot separately to apply, or add --reboot to restart now.
389
+ Shared data and overlay customizations persist and can override files in
390
+ the selected version. Blank, invalid, pending, and unknown states are refused.
391
+
392
+ Examples: sima-cli update --swap; sima-cli update --ip 192.168.6.5 --swap --reboot
393
+
373
394
  The verification certificate is downloaded for the selected channel when configured; use
374
395
  --signing-cert URL_OR_FILE for an image signed with your own certificate.
375
396
  Developer-portal version lookup for 3.0 is not available yet.
@@ -479,6 +500,7 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
479
500
  dryrun
480
501
  or troot_only
481
502
  or inspect_state
503
+ or swap
482
504
  or verbose
483
505
  or reboot
484
506
  or force
@@ -496,6 +518,8 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
496
518
  return
497
519
 
498
520
  is_elxr = is_devkit_running_elxr()
521
+ if swap and (inspect_state or version_or_url or force or troot_only or signing_cert is not None or flavor != "auto"):
522
+ raise click.UsageError("--swap cannot be combined with --inspect or installation options.")
499
523
  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
524
  raise click.UsageError("--inspect cannot be combined with installation options.")
501
525
  try:
@@ -503,7 +527,7 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
503
527
  internal=ctx.obj.get("internal", False),
504
528
  auto_confirm=yes or ctx.obj.get("yes", False), dryrun=dryrun,
505
529
  signing_cert=signing_cert, reboot=reboot, inspect=inspect_state, force=force,
506
- troot_only=troot_only, flavor=flavor, local_elxr=is_elxr, verbose=verbose):
530
+ troot_only=troot_only, flavor=flavor, local_elxr=is_elxr, verbose=verbose, swap=swap):
507
531
  return
508
532
  except (click.ClickException, click.Abort):
509
533
  raise
@@ -632,8 +656,10 @@ def mla(ctx):
632
656
  @click.pass_context
633
657
  def show_mla_memory_usage(ctx):
634
658
  """Show MLA Memory usage overtime."""
635
- monitor_simaai_mem_chart()
636
- pass
659
+ try:
660
+ monitor_simaai_mem_chart()
661
+ except MemoryTelemetryError as exc:
662
+ raise click.ClickException(str(exc)) from exc
637
663
 
638
664
 
639
665
  # ----------------------
@@ -554,20 +554,14 @@ def _reconcile_existing_connection(profile, store, progress):
554
554
 
555
555
  def connect(profile, store, progress, attempts=3, transport="auto"):
556
556
  """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
557
+ from .installer import ensure_forwarder
563
558
 
564
- progress.update("CloudEx forwarder is missing; installing from {}".format(DEFAULT_BRANCH))
565
- forwarder = install_forwarder(DEFAULT_BRANCH, progress=progress)["path"]
566
559
  authorize_admin()
567
560
  api = CloudExAPI.from_profile(profile)
568
561
  existing = _reconcile_existing_connection(profile, store, progress)
569
562
  if existing:
570
563
  return existing
564
+ forwarder = ensure_forwarder(progress=progress)
571
565
  choices = (["p2p"] * attempts + ["routed"] if transport == "auto"
572
566
  else ["routed"] if transport == "turn" else ["p2p"] * attempts)
573
567
  last_error = None
@@ -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,
@@ -1,4 +1,10 @@
1
1
  import os
2
+ import json
3
+ import hashlib
4
+ import re
5
+ from pathlib import Path
6
+ from sima_cli.utils.streaming import DOWNLOAD_CHUNK_SIZE, publish_partial
7
+ from sima_cli.download.parallel import PARALLEL_MIN_SIZE, download_parallel
2
8
  import requests
3
9
  from urllib.parse import urlparse
4
10
  from tqdm import tqdm
@@ -74,10 +80,11 @@ def download_file_from_url(url: str, dest_folder: str = ".", internal: bool = Fa
74
80
  os.makedirs(dest_folder, exist_ok=True)
75
81
  dest_path = os.path.join(dest_folder, file_name)
76
82
 
77
- resume_header = {}
78
- headers = {}
79
- mode = 'wb'
83
+ partial = Path(dest_path + ".part")
84
+ receipt = Path(dest_path + ".part.json")
85
+ headers = {"Accept-Encoding": "identity"}
80
86
  existing_size = 0
87
+ session = None
81
88
 
82
89
  try:
83
90
  if internal:
@@ -102,48 +109,98 @@ def download_file_from_url(url: str, dest_folder: str = ".", internal: bool = Fa
102
109
  request_fn = session.get
103
110
  head_fn = session.head
104
111
 
105
- # HEAD request to get total file size
106
- head = head_fn(url, headers=headers, timeout=10)
107
- head.raise_for_status()
108
- total_size = int(head.headers.get('content-length', 0))
109
-
110
- # Check for existing file
111
- if os.path.exists(dest_path):
112
- existing_size = os.path.getsize(dest_path)
113
-
114
- if existing_size == total_size:
112
+ # Only resume a partial when a strong validator identifies its bytes.
113
+ head = head_fn(url, headers=headers, timeout=10, allow_redirects=True)
114
+ try:
115
+ head.raise_for_status()
116
+ raw_size = head.headers.get('content-length')
117
+ total_size = int(raw_size) if raw_size is not None else None
118
+ etag = head.headers.get('ETag', '')
119
+ accepts_ranges = head.headers.get('Accept-Ranges', '').lower() == 'bytes'
120
+ finally:
121
+ head.close()
122
+ if etag.startswith('W/'):
123
+ etag = ''
124
+ identity = {"url_sha256": hashlib.sha256(url.encode()).hexdigest(), "etag": etag}
125
+ if os.path.exists(dest_path) and total_size is not None:
126
+ if os.path.getsize(dest_path) == total_size:
115
127
  print(f"✔ File already exists and is complete: {file_name}")
116
128
  return dest_path
117
- elif existing_size < total_size:
118
- resume_header['Range'] = f'bytes={existing_size}-'
119
- mode = 'ab'
120
- headers['Range'] = resume_header['Range']
121
- else:
122
- existing_size = 0
123
- mode = 'wb'
129
+ if partial.exists() and etag:
130
+ try:
131
+ if json.loads(receipt.read_text()) == identity:
132
+ existing_size = partial.stat().st_size
133
+ except (OSError, ValueError):
134
+ pass
135
+ if total_size is not None and existing_size >= total_size:
136
+ existing_size = 0
137
+ # Keep existing contiguous partials resumable. Parallelism is limited
138
+ # to the public mirror; authenticated/custom sessions retain their path.
139
+ if (not existing_size and parsed_url.scheme == 'https'
140
+ and parsed_url.hostname == 'artifacts.neat.sima.ai'
141
+ and not internal and total_size is not None
142
+ and total_size >= PARALLEL_MIN_SIZE and etag and accepts_ranges):
143
+ if download_parallel(url, dest_path, total_size, etag):
144
+ partial.unlink(missing_ok=True)
145
+ receipt.unlink(missing_ok=True)
146
+ return dest_path
147
+ if existing_size:
148
+ headers['Range'] = f'bytes={existing_size}-'
149
+ headers['If-Range'] = etag
124
150
 
125
- # Begin download with appropriate handler
126
151
  with request_fn(url, stream=True, headers=headers, timeout=30) as r:
127
152
  r.raise_for_status()
128
-
129
- content_length = int(r.headers.get('content-length', 0))
130
- final_total = existing_size + content_length
131
-
132
- with open(dest_path, mode) as f, tqdm(
133
- desc=f"⬇️ Downloading {file_name}",
134
- total=final_total,
135
- initial=existing_size,
136
- unit='B',
137
- unit_scale=True,
138
- unit_divisor=1024
153
+ if r.headers.get('Content-Encoding', 'identity').lower() != 'identity':
154
+ raise ValueError("Server ignored identity content encoding")
155
+ raw_length = r.headers.get('content-length')
156
+ content_length = int(raw_length) if raw_length is not None else None
157
+ if r.status_code == 206:
158
+ match = re.fullmatch(r'bytes (\d+)-(\d+)/(\d+)', r.headers.get('Content-Range', ''))
159
+ if not existing_size or not match:
160
+ raise ValueError("Invalid partial download response")
161
+ start, end, size = map(int, match.groups())
162
+ if (start != existing_size or end != size - 1 or end < start
163
+ or (content_length is not None and content_length != end - start + 1)
164
+ or (total_size is not None and size != total_size)
165
+ or (r.headers.get('ETag') and r.headers['ETag'] != etag)):
166
+ raise ValueError("Inconsistent partial download response")
167
+ final_total = size
168
+ elif r.status_code == 200:
169
+ # Range ignored, or If-Range detected a changed object: restart.
170
+ existing_size = 0
171
+ final_total = content_length if content_length is not None else total_size
172
+ else:
173
+ raise ValueError(f"Unexpected download status: {r.status_code}")
174
+ response_etag = r.headers.get('ETag', '')
175
+ if existing_size:
176
+ response_etag = etag
177
+ identity['etag'] = response_etag if not response_etag.startswith('W/') else ''
178
+ # Truncate before updating the receipt so old bytes cannot acquire a new validator.
179
+ with partial.open('ab' if existing_size else 'wb') as f, tqdm(
180
+ desc=f"⬇️ Downloading {file_name}", total=final_total,
181
+ initial=existing_size, unit='B', unit_scale=True, unit_divisor=1024
139
182
  ) as bar:
140
- for chunk in r.iter_content(chunk_size=8192):
183
+ receipt.write_text(json.dumps(identity), encoding='utf-8')
184
+ size = existing_size
185
+ for chunk in r.iter_content(chunk_size=DOWNLOAD_CHUNK_SIZE):
141
186
  if chunk:
142
187
  f.write(chunk)
188
+ size += len(chunk)
143
189
  bar.update(len(chunk))
190
+ if final_total is not None and size > final_total:
191
+ raise ValueError("Download exceeds expected size")
192
+ if final_total is not None and size != final_total:
193
+ raise ValueError("Download size mismatch")
194
+ f.flush()
195
+ os.fsync(f.fileno())
196
+ publish_partial(partial, dest_path)
197
+ receipt.unlink(missing_ok=True)
144
198
 
145
199
  except Exception as e:
146
200
  raise RuntimeError(f"Download failed: {e}")
201
+ finally:
202
+ if session is not None:
203
+ session.close()
147
204
 
148
205
 
149
206
  return dest_path