lager-cli 0.4.2__tar.gz → 0.7.0__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 (226) hide show
  1. {lager_cli-0.4.2/lager_cli.egg-info → lager_cli-0.7.0}/PKG-INFO +1 -1
  2. {lager_cli-0.4.2 → lager_cli-0.7.0}/__init__.py +1 -1
  3. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/box/boxes.py +97 -0
  4. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/development/devenv.py +13 -1
  5. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/development/python.py +128 -7
  6. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/utility/__init__.py +0 -2
  7. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/utility/update.py +2 -81
  8. {lager_cli-0.4.2 → lager_cli-0.7.0}/context/session.py +20 -1
  9. {lager_cli-0.4.2 → lager_cli-0.7.0}/deployment/scripts/setup_and_deploy_box.sh +12 -0
  10. {lager_cli-0.4.2 → lager_cli-0.7.0/lager_cli.egg-info}/PKG-INFO +1 -1
  11. {lager_cli-0.4.2 → lager_cli-0.7.0}/lager_cli.egg-info/SOURCES.txt +0 -2
  12. {lager_cli-0.4.2 → lager_cli-0.7.0}/main.py +32 -2
  13. {lager_cli-0.4.2 → lager_cli-0.7.0}/pyproject.toml +1 -1
  14. lager_cli-0.4.2/commands/utility/factory.py +0 -73
  15. {lager_cli-0.4.2 → lager_cli-0.7.0}/LICENSE +0 -0
  16. {lager_cli-0.4.2 → lager_cli-0.7.0}/MANIFEST.in +0 -0
  17. {lager_cli-0.4.2 → lager_cli-0.7.0}/README.md +0 -0
  18. {lager_cli-0.4.2 → lager_cli-0.7.0}/__main__.py +0 -0
  19. {lager_cli-0.4.2 → lager_cli-0.7.0}/battery/__init__.py +0 -0
  20. {lager_cli-0.4.2 → lager_cli-0.7.0}/battery/battery_tui.py +0 -0
  21. {lager_cli-0.4.2 → lager_cli-0.7.0}/battery/websocket_client.py +0 -0
  22. {lager_cli-0.4.2 → lager_cli-0.7.0}/box_storage.py +0 -0
  23. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/__init__.py +0 -0
  24. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/box/__init__.py +0 -0
  25. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/box/hello.py +0 -0
  26. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/box/instruments.py +0 -0
  27. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/box/net_tui.py +0 -0
  28. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/box/nets.py +0 -0
  29. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/box/ssh.py +0 -0
  30. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/communication/__init__.py +0 -0
  31. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/communication/ble.py +0 -0
  32. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/communication/blufi.py +0 -0
  33. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/communication/i2c.py +0 -0
  34. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/communication/spi.py +0 -0
  35. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/communication/uart.py +0 -0
  36. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/communication/usb.py +0 -0
  37. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/communication/websocket_client.py +0 -0
  38. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/communication/wifi.py +0 -0
  39. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/development/__init__.py +0 -0
  40. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/development/arm.py +0 -0
  41. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/development/debug/__init__.py +0 -0
  42. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/development/debug/commands.py +0 -0
  43. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/development/debug/gdb.py +0 -0
  44. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/development/debug/net_cache.py +0 -0
  45. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/development/debug/service_client.py +0 -0
  46. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/development/debug/service_helper.py +0 -0
  47. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/development/debug/tunnel.py +0 -0
  48. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/measurement/__init__.py +0 -0
  49. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/measurement/adc.py +0 -0
  50. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/measurement/dac.py +0 -0
  51. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/measurement/energy.py +0 -0
  52. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/measurement/gpi.py +0 -0
  53. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/measurement/gpo.py +0 -0
  54. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/measurement/logic.py +0 -0
  55. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/measurement/scope.py +0 -0
  56. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/measurement/thermocouple.py +0 -0
  57. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/measurement/watt.py +0 -0
  58. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/power/__init__.py +0 -0
  59. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/power/battery.py +0 -0
  60. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/power/eload.py +0 -0
  61. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/power/solar.py +0 -0
  62. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/power/supply.py +0 -0
  63. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/utility/binaries.py +0 -0
  64. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/utility/defaults.py +0 -0
  65. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/utility/exec_.py +0 -0
  66. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/utility/install.py +0 -0
  67. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/utility/logs.py +0 -0
  68. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/utility/pip.py +0 -0
  69. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/utility/uninstall.py +0 -0
  70. {lager_cli-0.4.2 → lager_cli-0.7.0}/commands/utility/webcam.py +0 -0
  71. {lager_cli-0.4.2 → lager_cli-0.7.0}/config.py +0 -0
  72. {lager_cli-0.4.2 → lager_cli-0.7.0}/context/__init__.py +0 -0
  73. {lager_cli-0.4.2 → lager_cli-0.7.0}/context/ci_detection.py +0 -0
  74. {lager_cli-0.4.2 → lager_cli-0.7.0}/context/constants.py +0 -0
  75. {lager_cli-0.4.2 → lager_cli-0.7.0}/context/core.py +0 -0
  76. {lager_cli-0.4.2 → lager_cli-0.7.0}/context/error_handlers.py +0 -0
  77. {lager_cli-0.4.2 → lager_cli-0.7.0}/core/__init__.py +0 -0
  78. {lager_cli-0.4.2 → lager_cli-0.7.0}/core/matchers.py +0 -0
  79. {lager_cli-0.4.2 → lager_cli-0.7.0}/core/net_helpers.py +0 -0
  80. {lager_cli-0.4.2 → lager_cli-0.7.0}/core/net_storage.py +0 -0
  81. {lager_cli-0.4.2 → lager_cli-0.7.0}/core/param_types.py +0 -0
  82. {lager_cli-0.4.2 → lager_cli-0.7.0}/core/ssh_utils.py +0 -0
  83. {lager_cli-0.4.2 → lager_cli-0.7.0}/core/utils.py +0 -0
  84. {lager_cli-0.4.2 → lager_cli-0.7.0}/deployment/__init__.py +0 -0
  85. {lager_cli-0.4.2 → lager_cli-0.7.0}/deployment/scripts/__init__.py +0 -0
  86. {lager_cli-0.4.2 → lager_cli-0.7.0}/deployment/scripts/convert_to_sparse_checkout.sh +0 -0
  87. {lager_cli-0.4.2 → lager_cli-0.7.0}/deployment/scripts/setup_ssh_key.sh +0 -0
  88. {lager_cli-0.4.2 → lager_cli-0.7.0}/deployment/security/__init__.py +0 -0
  89. {lager_cli-0.4.2 → lager_cli-0.7.0}/deployment/security/secure_box_firewall.sh +0 -0
  90. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/__init__.py +0 -0
  91. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/common/__init__.py +0 -0
  92. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/common/construct_utils.py +0 -0
  93. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/common/exceptions.py +0 -0
  94. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/common/py3compat.py +0 -0
  95. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/common/utils.py +0 -0
  96. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/construct/__init__.py +0 -0
  97. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/construct/adapters.py +0 -0
  98. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/construct/core.py +0 -0
  99. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/construct/debug.py +0 -0
  100. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/construct/macros.py +0 -0
  101. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/__init__.py +0 -0
  102. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/abbrevtable.py +0 -0
  103. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/aranges.py +0 -0
  104. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/callframe.py +0 -0
  105. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/compileunit.py +0 -0
  106. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/constants.py +0 -0
  107. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/descriptions.py +0 -0
  108. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/die.py +0 -0
  109. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/dwarf_expr.py +0 -0
  110. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/dwarfinfo.py +0 -0
  111. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/enums.py +0 -0
  112. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/lineprogram.py +0 -0
  113. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/locationlists.py +0 -0
  114. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/namelut.py +0 -0
  115. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/ranges.py +0 -0
  116. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/dwarf/structs.py +0 -0
  117. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/ehabi/__init__.py +0 -0
  118. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/ehabi/constants.py +0 -0
  119. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/ehabi/decoder.py +0 -0
  120. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/ehabi/ehabiinfo.py +0 -0
  121. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/ehabi/structs.py +0 -0
  122. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/__init__.py +0 -0
  123. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/constants.py +0 -0
  124. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/descriptions.py +0 -0
  125. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/dynamic.py +0 -0
  126. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/elffile.py +0 -0
  127. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/enums.py +0 -0
  128. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/gnuversions.py +0 -0
  129. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/hash.py +0 -0
  130. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/notes.py +0 -0
  131. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/relocation.py +0 -0
  132. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/sections.py +0 -0
  133. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/segments.py +0 -0
  134. {lager_cli-0.4.2 → lager_cli-0.7.0}/elftools/elf/structs.py +0 -0
  135. {lager_cli-0.4.2 → lager_cli-0.7.0}/exceptions.py +0 -0
  136. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/__init__.py +0 -0
  137. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/communication/__init__.py +0 -0
  138. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/communication/ble.py +0 -0
  139. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/communication/blufi.py +0 -0
  140. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/communication/i2c.py +0 -0
  141. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/communication/spi.py +0 -0
  142. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/communication/uart.py +0 -0
  143. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/communication/wifi.py +0 -0
  144. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/device/__init__.py +0 -0
  145. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/device/arm.py +0 -0
  146. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/device/hello.py +0 -0
  147. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/device/usb.py +0 -0
  148. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/device/webcam.py +0 -0
  149. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/measurement/__init__.py +0 -0
  150. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/measurement/adc.py +0 -0
  151. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/measurement/dac.py +0 -0
  152. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/measurement/energy.py +0 -0
  153. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/measurement/gpio.py +0 -0
  154. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/measurement/scope.py +0 -0
  155. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/measurement/scope_stream.py +0 -0
  156. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/measurement/thermocouple.py +0 -0
  157. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/measurement/watt.py +0 -0
  158. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/net.py +0 -0
  159. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/power/__init__.py +0 -0
  160. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/power/battery.py +0 -0
  161. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/power/eload.py +0 -0
  162. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/power/enable_disable.py +0 -0
  163. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/power/solar.py +0 -0
  164. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/power/supply.py +0 -0
  165. {lager_cli-0.4.2 → lager_cli-0.7.0}/impl/query_instruments.py +0 -0
  166. {lager_cli-0.4.2 → lager_cli-0.7.0}/lager_cli.egg-info/dependency_links.txt +0 -0
  167. {lager_cli-0.4.2 → lager_cli-0.7.0}/lager_cli.egg-info/entry_points.txt +0 -0
  168. {lager_cli-0.4.2 → lager_cli-0.7.0}/lager_cli.egg-info/requires.txt +0 -0
  169. {lager_cli-0.4.2 → lager_cli-0.7.0}/lager_cli.egg-info/top_level.txt +0 -0
  170. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/__init__.py +0 -0
  171. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/__main__.py +0 -0
  172. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/server.py +0 -0
  173. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/__init__.py +0 -0
  174. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/arm.py +0 -0
  175. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/battery.py +0 -0
  176. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/binaries.py +0 -0
  177. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/ble.py +0 -0
  178. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/blufi.py +0 -0
  179. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/box.py +0 -0
  180. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/debug.py +0 -0
  181. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/defaults.py +0 -0
  182. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/eload.py +0 -0
  183. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/i2c.py +0 -0
  184. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/logic.py +0 -0
  185. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/logs.py +0 -0
  186. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/measurement.py +0 -0
  187. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/pip_tools.py +0 -0
  188. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/power.py +0 -0
  189. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/python_run.py +0 -0
  190. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/scope.py +0 -0
  191. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/solar.py +0 -0
  192. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/spi.py +0 -0
  193. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/uart.py +0 -0
  194. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/usb.py +0 -0
  195. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/webcam.py +0 -0
  196. {lager_cli-0.4.2 → lager_cli-0.7.0}/mcp/tools/wifi.py +0 -0
  197. {lager_cli-0.4.2 → lager_cli-0.7.0}/safe_unpickle.py +0 -0
  198. {lager_cli-0.4.2 → lager_cli-0.7.0}/setup.cfg +0 -0
  199. {lager_cli-0.4.2 → lager_cli-0.7.0}/setup.py +0 -0
  200. {lager_cli-0.4.2 → lager_cli-0.7.0}/simple_hdlc.py +0 -0
  201. {lager_cli-0.4.2 → lager_cli-0.7.0}/sort_utils.py +0 -0
  202. {lager_cli-0.4.2 → lager_cli-0.7.0}/status.py +0 -0
  203. {lager_cli-0.4.2 → lager_cli-0.7.0}/supply/__init__.py +0 -0
  204. {lager_cli-0.4.2 → lager_cli-0.7.0}/supply/supply_tui.py +0 -0
  205. {lager_cli-0.4.2 → lager_cli-0.7.0}/supply/websocket_client.py +0 -0
  206. {lager_cli-0.4.2 → lager_cli-0.7.0}/terminal/__init__.py +0 -0
  207. {lager_cli-0.4.2 → lager_cli-0.7.0}/terminal/core/__init__.py +0 -0
  208. {lager_cli-0.4.2 → lager_cli-0.7.0}/terminal/core/executor.py +0 -0
  209. {lager_cli-0.4.2 → lager_cli-0.7.0}/terminal/main.py +0 -0
  210. {lager_cli-0.4.2 → lager_cli-0.7.0}/terminal/ui/__init__.py +0 -0
  211. {lager_cli-0.4.2 → lager_cli-0.7.0}/terminal/ui/completer.py +0 -0
  212. {lager_cli-0.4.2 → lager_cli-0.7.0}/terminal/ui/display.py +0 -0
  213. {lager_cli-0.4.2 → lager_cli-0.7.0}/terminal/ui/logo.py +0 -0
  214. {lager_cli-0.4.2 → lager_cli-0.7.0}/terminal/ui/repl.py +0 -0
  215. {lager_cli-0.4.2 → lager_cli-0.7.0}/terminal/ui/themes.py +0 -0
  216. {lager_cli-0.4.2 → lager_cli-0.7.0}/tests/test_box_lager_imports.py +0 -0
  217. {lager_cli-0.4.2 → lager_cli-0.7.0}/tests/test_io_imports.py +0 -0
  218. {lager_cli-0.4.2 → lager_cli-0.7.0}/update_check.py +0 -0
  219. {lager_cli-0.4.2 → lager_cli-0.7.0}/vendor/PyCRC/CRC16.py +0 -0
  220. {lager_cli-0.4.2 → lager_cli-0.7.0}/vendor/PyCRC/CRC16DNP.py +0 -0
  221. {lager_cli-0.4.2 → lager_cli-0.7.0}/vendor/PyCRC/CRC16Kermit.py +0 -0
  222. {lager_cli-0.4.2 → lager_cli-0.7.0}/vendor/PyCRC/CRC16SICK.py +0 -0
  223. {lager_cli-0.4.2 → lager_cli-0.7.0}/vendor/PyCRC/CRC32.py +0 -0
  224. {lager_cli-0.4.2 → lager_cli-0.7.0}/vendor/PyCRC/CRCCCITT.py +0 -0
  225. {lager_cli-0.4.2 → lager_cli-0.7.0}/vendor/PyCRC/__init__.py +0 -0
  226. {lager_cli-0.4.2 → lager_cli-0.7.0}/vendor/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lager-cli
3
- Version: 0.4.2
3
+ Version: 0.7.0
4
4
  Summary: Lager CLI - Box and Docker connectivity
5
5
  Home-page: https://github.com/lagerdata/lager
6
6
  Author: Lager Data LLC
@@ -7,4 +7,4 @@ Lager CLI
7
7
  A Command Line Interface for Lager Data
8
8
  """
9
9
 
10
- __version__ = '0.4.2'
10
+ __version__ = '0.7.0'
@@ -679,6 +679,103 @@ def import_boxes(file, merge, yes):
679
679
  click.echo(click.style(f"[OK] Successfully imported {len(import_boxes_data)} box(es) from {file}", fg='green'))
680
680
 
681
681
 
682
+ @boxes.command('connect')
683
+ @click.option('--box', required=True, help='Name of the box to connect')
684
+ @click.option('--url', default='https://api.stoutdata.ai', help='Control plane URL')
685
+ @click.option('--api-key', required=True, help='API key for control plane authentication')
686
+ @click.option('--heartbeat-interval', default=30, type=int, help='Heartbeat interval in seconds (default: 30)')
687
+ @click.option('--yes', is_flag=True, help='Confirm the action without prompting.')
688
+ def connect(box, url, api_key, heartbeat_interval, yes):
689
+ """Connect a box to a control plane for heartbeat reporting."""
690
+ import subprocess
691
+ import time
692
+ import requests
693
+ import shlex
694
+ from ...box_storage import get_box_ip, get_box_user
695
+ from ...core.ssh_utils import get_reusable_ssh_command
696
+
697
+ # Validate URL
698
+ from urllib.parse import urlparse
699
+ parsed = urlparse(url)
700
+ if parsed.scheme not in ('http', 'https'):
701
+ click.secho(f"Error: URL scheme must be http or https, got '{parsed.scheme}'", fg='red', err=True)
702
+ raise click.Abort()
703
+ if not parsed.hostname:
704
+ click.secho("Error: URL must include a hostname", fg='red', err=True)
705
+ raise click.Abort()
706
+
707
+ # Warn if control plane is unreachable from this machine (may still work from the box)
708
+ import requests as _req
709
+ try:
710
+ _req.get(f'{url.rstrip("/")}/healthz', timeout=5)
711
+ except _req.exceptions.RequestException:
712
+ click.secho(
713
+ f"Warning: Could not reach {url} from this machine. "
714
+ "It may still be reachable from the box.",
715
+ fg='yellow',
716
+ )
717
+
718
+ ip = get_box_ip(box)
719
+ if not ip:
720
+ click.secho(f"Error: Box '{box}' not found in configuration", fg='red', err=True)
721
+ raise click.Abort()
722
+
723
+ user = get_box_user(box) or 'lagerdata'
724
+
725
+ if not yes:
726
+ click.echo(f"\nConnect box '{box}' ({ip}) to control plane:")
727
+ click.echo(f" URL: {url}")
728
+ click.echo(f" API Key: {api_key[:8]}...")
729
+ click.echo(f" Interval: {heartbeat_interval}s")
730
+ click.echo()
731
+ if not click.confirm("Proceed?", default=False):
732
+ click.echo("Cancelled.")
733
+ return
734
+
735
+ config = json.dumps({
736
+ 'url': url,
737
+ 'api_key': api_key,
738
+ 'heartbeat_interval_seconds': heartbeat_interval,
739
+ 'enabled': True,
740
+ })
741
+
742
+ # Write config into the lager Docker container (avoids needing sudo on host)
743
+ click.echo(f"Writing control plane config to {box}...")
744
+ write_cmd = f'echo {shlex.quote(config)} | docker exec -u root -i lager tee /etc/lager/control_plane.json > /dev/null'
745
+ ssh_cmd = get_reusable_ssh_command(ip, user=user, command=write_cmd)
746
+ result = subprocess.run(ssh_cmd, capture_output=True, text=True)
747
+ if result.returncode != 0:
748
+ click.secho(f"Error writing config: {result.stderr.strip()}", fg='red', err=True)
749
+ raise click.Abort()
750
+
751
+ # Restart the box container
752
+ click.echo(f"Restarting lager container on {box}...")
753
+ ssh_cmd = get_reusable_ssh_command(ip, user=user, command='docker restart lager')
754
+ result = subprocess.run(ssh_cmd, capture_output=True, text=True)
755
+ if result.returncode != 0:
756
+ click.secho(f"Error restarting container: {result.stderr.strip()}", fg='red', err=True)
757
+ raise click.Abort()
758
+
759
+ # Verify the box is healthy
760
+ click.echo("Waiting for box to come back up...")
761
+ time.sleep(5)
762
+ try:
763
+ resp = requests.get(f'http://{ip}:5000/status', timeout=10)
764
+ if resp.status_code == 200:
765
+ data = resp.json()
766
+ click.secho(f"Box '{box}' connected successfully!", fg='green')
767
+ click.echo(f" Healthy: {data.get('healthy')}")
768
+ click.echo(f" Version: {data.get('version')}")
769
+ nets = data.get('nets', [])
770
+ if nets:
771
+ click.echo(f" Nets: {len(nets)}")
772
+ else:
773
+ click.secho(f"Warning: Box returned HTTP {resp.status_code}", fg='yellow')
774
+ except requests.exceptions.RequestException as e:
775
+ click.secho(f"Warning: Could not verify box status: {e}", fg='yellow')
776
+ click.echo("The config was written. The box may still be starting up.")
777
+
778
+
682
779
  def compare_versions(v1, v2):
683
780
  """
684
781
  Compare two version strings.
@@ -140,7 +140,9 @@ def create(ctx, image, mount_dir, shell):
140
140
  @click.option('--entrypoint', help='Container entrypoint', required=False)
141
141
  @click.option('--network', help='Network mode', required=False)
142
142
  @click.option('--platform', help='Platform', required=False)
143
- def terminal(ctx, mount, user, group, name, detach, port, entrypoint, network, platform):
143
+ @click.option('--attach', '-a', 'attach_container', help='Attach to a running container by name', required=False, default=None)
144
+ @click.option('--shell', '-s', help='Shell to use when attaching (default: config shell or /bin/bash)', required=False, default=None)
145
+ def terminal(ctx, mount, user, group, name, detach, port, entrypoint, network, platform, attach_container, shell):
144
146
  """
145
147
  Start interactive terminal
146
148
  """
@@ -151,6 +153,16 @@ def terminal(ctx, mount, user, group, name, detach, port, entrypoint, network, p
151
153
  ctx.exit(1)
152
154
  devenv_config = data['DEVENV']
153
155
 
156
+ if attach_container:
157
+ shell_cmd = shell or devenv_config.get('shell', '/bin/bash')
158
+ attach_args = ['docker', 'exec', '-it', attach_container, shell_cmd]
159
+ try:
160
+ proc = subprocess.run(attach_args, check=False)
161
+ except FileNotFoundError:
162
+ click.secho("Error: Docker is not installed or not in PATH", fg='red', err=True)
163
+ ctx.exit(1)
164
+ ctx.exit(proc.returncode)
165
+
154
166
  image = devenv_config.get('image')
155
167
  source_dir = os.path.dirname(path)
156
168
  mount_dir = devenv_config.get('mount_dir')
@@ -66,7 +66,7 @@ def sigint_handler(kill_python, box_ip, _sig, _frame):
66
66
 
67
67
  # Now restore original handler and kill remote process
68
68
  signal.signal(signal.SIGINT, _ORIGINAL_SIGINT_HANDLER)
69
- kill_python(signal.SIGINT)
69
+ kill_python(signal.SIGTERM)
70
70
 
71
71
 
72
72
  def _do_exit(exit_code, box, session, downloads):
@@ -377,6 +377,19 @@ def run_python_internal(ctx, runnable, box, env, passenv, kill, download, allow_
377
377
  click.echo(resp.text, err=True)
378
378
  ctx.exit(1)
379
379
 
380
+ # Handle detached mode: parse JSON response and return immediately
381
+ if detach:
382
+ try:
383
+ data = resp.json()
384
+ process_id = data.get('lager_process_id', lager_process_id)
385
+ box_label = dut_name or box_ip
386
+ click.echo(f'Process detached (Process ID: {process_id})')
387
+ click.echo(f'To reattach: lager python --reattach {process_id} --box {box_label}')
388
+ click.echo(f'To kill: lager python --kill {process_id} --box {box_label}')
389
+ except Exception:
390
+ click.echo('Process detached.')
391
+ return
392
+
380
393
  kill_python = functools.partial(session.kill_python, box_ip, lager_process_id)
381
394
  handler = functools.partial(sigint_handler, kill_python, box_ip)
382
395
  signal.signal(signal.SIGINT, handler)
@@ -414,6 +427,84 @@ def run_python_internal(ctx, runnable, box, env, passenv, kill, download, allow_
414
427
  sys.exit(1)
415
428
 
416
429
 
430
+ def _handle_reattach(ctx, box_ip, process_id, session, dut_name):
431
+ """
432
+ Reattach to a detached process and stream its output.
433
+
434
+ Ctrl+C kills the process (same as normal lager python).
435
+ Ctrl+D detaches from the stream without killing the process.
436
+ """
437
+ try:
438
+ resp = session.attach_python(box_ip, process_id)
439
+ except requests.exceptions.ConnectionError:
440
+ click.secho(f'Could not connect to box at {box_ip}', fg='red', err=True)
441
+ ctx.exit(1)
442
+ except requests.exceptions.Timeout:
443
+ click.secho(f'Connection to box at {box_ip} timed out', fg='red', err=True)
444
+ ctx.exit(1)
445
+
446
+ if resp.status_code == 404 or resp.status_code == 422:
447
+ try:
448
+ error_data = resp.json()
449
+ click.secho(error_data.get('error', 'Process not found'), fg='red', err=True)
450
+ except Exception:
451
+ click.secho(f'Process not found: {process_id}', fg='red', err=True)
452
+ ctx.exit(1)
453
+ elif resp.status_code >= 400:
454
+ click.secho(f'Error: Box returned HTTP {resp.status_code}', fg='red', err=True)
455
+ ctx.exit(1)
456
+
457
+ # Ctrl+C = kill the process (same as normal lager python)
458
+ kill_python = functools.partial(session.kill_python, box_ip, process_id)
459
+ handler = functools.partial(sigint_handler, kill_python, box_ip)
460
+ signal.signal(signal.SIGINT, handler)
461
+
462
+ # Ctrl+D = detach (stdin EOF watcher thread)
463
+ detached_by_user = False
464
+
465
+ def watch_stdin_for_detach():
466
+ nonlocal detached_by_user
467
+ try:
468
+ while True:
469
+ ch = sys.stdin.read(1)
470
+ if not ch: # EOF (Ctrl+D)
471
+ detached_by_user = True
472
+ click.echo('\nDetaching...')
473
+ resp.close()
474
+ return
475
+ except (ValueError, OSError):
476
+ return
477
+
478
+ if sys.stdin.isatty():
479
+ stdin_thread = threading.Thread(target=watch_stdin_for_detach, daemon=True)
480
+ stdin_thread.start()
481
+
482
+ try:
483
+ for (datatype, content) in stream_python_output(resp):
484
+ if datatype == StreamDatatypes.EXIT:
485
+ signal.signal(signal.SIGINT, _ORIGINAL_SIGINT_HANDLER)
486
+ click.echo(f'Process exited with code {content}')
487
+ sys.exit(content)
488
+ elif datatype == StreamDatatypes.STDOUT:
489
+ click.echo(content.decode("utf-8", errors="ignore"), nl=False)
490
+ elif datatype == StreamDatatypes.STDERR:
491
+ click.echo(content.decode("utf-8", errors="ignore"), nl=False, err=True)
492
+ elif datatype == StreamDatatypes.OUTPUT:
493
+ click.echo(content)
494
+ except (BrokenPipeError, requests.exceptions.ChunkedEncodingError):
495
+ pass
496
+ except OutputFormatNotSupported:
497
+ click.secho('Response format not supported. Please upgrade lager-cli', fg='red', err=True)
498
+ sys.exit(1)
499
+ finally:
500
+ signal.signal(signal.SIGINT, _ORIGINAL_SIGINT_HANDLER)
501
+
502
+ if detached_by_user:
503
+ box_label = dut_name or box_ip
504
+ click.echo(f'To reattach: lager python --reattach {process_id} --box {box_label}')
505
+ click.echo(f'To kill: lager python --kill {process_id} --box {box_label}')
506
+
507
+
417
508
  @click.command()
418
509
  @click.pass_context
419
510
  @click.argument('runnable', required=False, type=click.Path(exists=True))
@@ -424,17 +515,19 @@ def run_python_internal(ctx, runnable, box, env, passenv, kill, download, allow_
424
515
  @click.option(
425
516
  '--passenv',
426
517
  multiple=True, help='Environment variable to inherit')
427
- @click.option('--kill', is_flag=True, default=False, help='Terminate running script')
518
+ @click.option('--kill', default=None, help='Kill a specific process by process ID')
519
+ @click.option('--kill-all', is_flag=True, default=False, help='Kill all running scripts')
428
520
  @click.option('--download', type=click.Path(exists=False, dir_okay=False), multiple=True, help='File to download after completion')
429
521
  @click.option('--allow-overwrite', is_flag=True, default=False, help='Overwrite existing files when downloading')
430
- @click.option('--signal', 'signum', default='SIGTERM', type=_SIGNAL_CHOICES, help='Signal to use with --kill', show_default=True)
522
+ @click.option('--signal', 'signum', default='SIGTERM', type=_SIGNAL_CHOICES, help='Signal to use with --kill/--kill-all', show_default=True)
431
523
  @click.option('--timeout', type=click.IntRange(min=0), default=0, required=False, help='Max runtime in seconds (0=no timeout)')
432
524
  @click.option('--detach', '-d', is_flag=True, required=False, default=False, help='Detach')
433
525
  @click.option('--port', '-p', multiple=True, help='Port forwarding (SRC_PORT[:DST_PORT][/PROTOCOL])', type=PortForwardType())
434
526
  @click.option('--org', default=None, hidden=True)
435
527
  @click.option('--add-file', type=click.Path(exists=True, dir_okay=False), multiple=True, help='File to upload with script')
528
+ @click.option('--reattach', default=None, help='Reattach to detached process by process ID')
436
529
  @click.argument('args', nargs=-1)
437
- def python(ctx, runnable, box, env, passenv, kill, download, allow_overwrite, signum, timeout, detach, port, org, add_file, args):
530
+ def python(ctx, runnable, box, env, passenv, kill, kill_all, download, allow_overwrite, signum, timeout, detach, port, org, add_file, reattach, args):
438
531
  """Run Python script on box"""
439
532
  from ...box_storage import resolve_and_validate_box
440
533
 
@@ -442,8 +535,36 @@ def python(ctx, runnable, box, env, passenv, kill, download, allow_overwrite, si
442
535
  box_name = box
443
536
  box_ip = resolve_and_validate_box(ctx, box_name)
444
537
 
445
- if not runnable and not kill:
446
- raise click.UsageError('Please supply a RUNNABLE or the --kill option')
538
+ if not runnable and not kill and not kill_all and not reattach:
539
+ raise click.UsageError('Please supply a RUNNABLE, --kill, --kill-all, or --reattach option')
540
+
541
+ if kill:
542
+ session = ctx.obj.get_session_for_box(box_ip, box_name=box_name)
543
+ signum_val = _get_signal_number(signum)
544
+ resp = session.kill_python(box_ip, kill, signum_val)
545
+ if resp.status_code == 422:
546
+ try:
547
+ error_data = resp.json()
548
+ click.secho(error_data.get('error', 'Invalid request'), fg='red', err=True)
549
+ except Exception:
550
+ click.secho(f'Invalid process ID: {kill}', fg='red', err=True)
551
+ ctx.exit(1)
552
+ resp.raise_for_status()
553
+ click.echo(f'Process {kill} killed')
554
+ return
555
+
556
+ if kill_all:
557
+ session = ctx.obj.get_session_for_box(box_ip, box_name=box_name)
558
+ signum_val = _get_signal_number(signum)
559
+ resp = session.kill_python(box_ip, None, signum_val)
560
+ resp.raise_for_status()
561
+ click.echo('All processes killed')
562
+ return
563
+
564
+ if reattach:
565
+ session = ctx.obj.get_session_for_box(box_ip, box_name=box_name)
566
+ _handle_reattach(ctx, box_ip, reattach, session, box_name)
567
+ return
447
568
 
448
569
  if not allow_overwrite:
449
570
  for filename in download:
@@ -457,4 +578,4 @@ def python(ctx, runnable, box, env, passenv, kill, download, allow_overwrite, si
457
578
  if box_name:
458
579
  env.append(f'LAGER_BOX={box_name}')
459
580
 
460
- run_python_internal(ctx, runnable, box_ip, env, passenv, kill, download, allow_overwrite, signum, timeout, detach, port, org, args, add_file, dut_name=box_name)
581
+ run_python_internal(ctx, runnable, box_ip, env, passenv, False, download, allow_overwrite, signum, timeout, detach, port, org, args, add_file, dut_name=box_name)
@@ -24,7 +24,6 @@ from .logs import logs
24
24
  from .webcam import webcam
25
25
  from .install import install
26
26
  from .uninstall import uninstall
27
- from .factory import factory
28
27
 
29
28
  __all__ = [
30
29
  "defaults",
@@ -36,5 +35,4 @@ __all__ = [
36
35
  "webcam",
37
36
  "install",
38
37
  "uninstall",
39
- "factory",
40
38
  ]
@@ -304,8 +304,8 @@ def update(ctx, box, update_all, yes, skip_restart, version, verbose, force):
304
304
  ctx.exit(0)
305
305
 
306
306
  # Initialize progress bar (only in non-verbose mode)
307
- # Total steps: connectivity, repo check, git state check, fetch, checkout/pull, flatten, udev, sudoers, docker stop, [force image removal], docker build, cleanup, /etc/lager, docker start, binaries, jlink, verify, version, factory sync
308
- total_steps = 21 if force else 20
307
+ # Total steps: connectivity, repo check, git state check, fetch, checkout/pull, flatten, udev, sudoers, docker stop, [force image removal], docker build, cleanup, /etc/lager, docker start, binaries, jlink, verify, version
308
+ total_steps = 20 if force else 19
309
309
  progress = None if verbose else ProgressBar(total_steps=total_steps)
310
310
 
311
311
  if not verbose:
@@ -1206,85 +1206,6 @@ fi
1206
1206
  if box_cli_version and box:
1207
1207
  update_box_version(box, box_cli_version)
1208
1208
 
1209
- # Step 14: Sync .lager boxes to factory dashboard (if factory is deployed)
1210
- if progress:
1211
- progress.update("Syncing factory...")
1212
- log('Syncing factory dashboard...', nl=False)
1213
-
1214
- try:
1215
- # Check if factory directory exists on the host (container was stopped earlier)
1216
- factory_check = run_ssh_command_with_output(
1217
- 'test -d /home/lagerdata/factory',
1218
- timeout_secs=10
1219
- )
1220
-
1221
- if factory_check.returncode != 0:
1222
- log_status('Syncing factory dashboard...', 'SKIPPED (no factory)', 'yellow')
1223
- else:
1224
- import json as json_mod
1225
- # Build boxes.json from all saved boxes (dict keyed by box name)
1226
- saved_boxes = list_boxes()
1227
- boxes_dict = {}
1228
- for name, box_info in sorted(saved_boxes.items()):
1229
- if isinstance(box_info, dict):
1230
- ip = box_info.get('ip', '')
1231
- user = box_info.get('user', 'lagerdata')
1232
- else:
1233
- ip = box_info
1234
- user = 'lagerdata'
1235
-
1236
- if not ip or ip == 'unknown':
1237
- continue
1238
-
1239
- # For the box being updated, use localhost
1240
- # so the factory container can reach its own lager container
1241
- if name == box_name:
1242
- entry_ip = '127.0.0.1'
1243
- else:
1244
- entry_ip = ip
1245
-
1246
- boxes_dict[name] = {
1247
- 'name': name,
1248
- 'ip': entry_ip,
1249
- 'ssh_user': user,
1250
- 'container': 'lager',
1251
- }
1252
-
1253
- boxes_json = json_mod.dumps(boxes_dict, indent=2)
1254
- # Escape single quotes for shell
1255
- escaped_json = boxes_json.replace("'", "'\\''")
1256
-
1257
- # Write boxes.json to the host filesystem
1258
- write_result = run_ssh_command_with_output(
1259
- f"echo '{escaped_json}' > /home/lagerdata/factory/webapp/boxes.json",
1260
- timeout_secs=10
1261
- )
1262
-
1263
- if write_result.returncode != 0:
1264
- log_status('Syncing factory dashboard...', 'FAILED (write)', 'red')
1265
- else:
1266
- # Start factory container (it was stopped by the "stop all" step)
1267
- start_result = run_ssh_command_with_output(
1268
- 'cd /home/lagerdata/factory && docker compose up -d',
1269
- timeout_secs=60
1270
- )
1271
- if start_result.returncode != 0:
1272
- log_status('Syncing factory dashboard...', 'FAILED (start)', 'red')
1273
- else:
1274
- # Copy boxes.json into the running container and restart
1275
- # so it picks up the new config (docker cp works even without bind mounts)
1276
- cp_result = run_ssh_command_with_output(
1277
- 'docker cp /home/lagerdata/factory/webapp/boxes.json factory:/factory/webapp/boxes.json && '
1278
- 'docker restart factory',
1279
- timeout_secs=30
1280
- )
1281
- if cp_result.returncode == 0:
1282
- log_status('Syncing factory dashboard...', f'OK ({len(boxes_dict)} boxes)', 'green')
1283
- else:
1284
- log_status('Syncing factory dashboard...', 'FAILED (copy)', 'red')
1285
- except Exception as e:
1286
- log_status('Syncing factory dashboard...', f'FAILED ({e})', 'red')
1287
-
1288
1209
  # Finish progress bar
1289
1210
  if progress:
1290
1211
  progress.finish(success=True)
@@ -625,7 +625,6 @@ class DirectHTTPSession:
625
625
  'lager_process_id': lager_process_id,
626
626
  'signal': int(sig)
627
627
  })
628
- response.raise_for_status()
629
628
  return response
630
629
 
631
630
  def box_hello(self, box):
@@ -643,6 +642,26 @@ class DirectHTTPSession:
643
642
  url = f'{self.base_url}/nets'
644
643
  return self.session.get(url)
645
644
 
645
+ def attach_python(self, box, lager_process_id):
646
+ """
647
+ Reattach to a detached Python process on box.
648
+
649
+ Args:
650
+ box: Box IP (ignored, uses self.box_ip)
651
+ lager_process_id: Process ID to reattach to
652
+
653
+ Returns:
654
+ requests.Response object with streaming content
655
+ """
656
+ url = f'{self.base_url}/python/attach'
657
+ response = self.session.post(
658
+ url,
659
+ json={'lager_process_id': lager_process_id},
660
+ stream=True,
661
+ timeout=(7, None),
662
+ )
663
+ return response
664
+
646
665
  def download_file(self, box, filename):
647
666
  """
648
667
  Download a file from box via direct HTTP.
@@ -302,6 +302,18 @@ else
302
302
  fi
303
303
  fi
304
304
 
305
+ # Always ensure current user is in docker group (handles multi-user scenario)
306
+ print_info "Ensuring ${BOX_USER} is in the docker group..."
307
+ ssh_t "${BOX_USER}@${BOX_IP}" "sudo usermod -aG docker \$USER" 2>/dev/null || true
308
+
309
+ # Verify docker access works for this user
310
+ if ssh $SSH_OPTS "${BOX_USER}@${BOX_IP}" "docker info >/dev/null 2>&1"; then
311
+ print_success "${BOX_USER} has docker access"
312
+ else
313
+ print_warning "Docker group membership may require re-login"
314
+ print_info "If Docker commands fail, log out and back in to the box, then re-run install"
315
+ fi
316
+
305
317
  # =============================================================================
306
318
  # STEP 1: SSH Key Setup
307
319
  # =============================================================================
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lager-cli
3
- Version: 0.4.2
3
+ Version: 0.7.0
4
4
  Summary: Lager CLI - Box and Docker connectivity
5
5
  Home-page: https://github.com/lagerdata/lager
6
6
  Author: Lager Data LLC
@@ -76,7 +76,6 @@ update_check.py
76
76
  ./commands/utility/binaries.py
77
77
  ./commands/utility/defaults.py
78
78
  ./commands/utility/exec_.py
79
- ./commands/utility/factory.py
80
79
  ./commands/utility/install.py
81
80
  ./commands/utility/logs.py
82
81
  ./commands/utility/pip.py
@@ -276,7 +275,6 @@ commands/utility/__init__.py
276
275
  commands/utility/binaries.py
277
276
  commands/utility/defaults.py
278
277
  commands/utility/exec_.py
279
- commands/utility/factory.py
280
278
  commands/utility/install.py
281
279
  commands/utility/logs.py
282
280
  commands/utility/pip.py
@@ -72,7 +72,36 @@ from .commands.measurement.energy import energy
72
72
  from .commands.box import hello, boxes, instruments, nets, ssh
73
73
 
74
74
  # Utility commands (from commands.utility package)
75
- from .commands.utility import defaults, binaries, update, pip, exec_, logs, webcam, install, uninstall, factory
75
+ from .commands.utility import defaults, binaries, update, pip, exec_, logs, webcam, install, uninstall
76
+
77
+ def _check_venv_shadowing():
78
+ """Warn if a system-installed lager is shadowing the venv version."""
79
+ virtual_env = os.environ.get('VIRTUAL_ENV')
80
+ if not virtual_env:
81
+ return
82
+
83
+ # If VIRTUAL_ENV is set but sys.prefix doesn't point to it, the
84
+ # system `lager` entry-point script (with a hardcoded shebang) is
85
+ # running instead of the venv's copy. This is the exact shadowing
86
+ # scenario we want to catch.
87
+ if os.path.realpath(sys.prefix) == os.path.realpath(virtual_env):
88
+ return
89
+
90
+ click.secho(
91
+ f"WARNING: Lager CLI v{__version__} is running from a system Python ({sys.prefix}), "
92
+ f"not from your active virtual environment ({virtual_env}).",
93
+ fg='yellow', err=True,
94
+ )
95
+ click.secho(
96
+ "This can cause version mismatches. To fix:\n"
97
+ " hash -r # clear shell cache, then retry\n"
98
+ " pip install --force-reinstall lager-cli # if hash -r alone doesn't help\n"
99
+ "Or uninstall the system version:\n"
100
+ " deactivate && pip uninstall lager-cli && source <venv>/bin/activate",
101
+ fg='yellow', err=True,
102
+ )
103
+ click.echo(err=True)
104
+
76
105
 
77
106
  def _decode_environment():
78
107
  for key in os.environ:
@@ -89,6 +118,8 @@ def cli(ctx=None, see_version=None, debug=False, colorize=False, interpreter=Non
89
118
  """
90
119
  Lager CLI
91
120
  """
121
+ _check_venv_shadowing()
122
+
92
123
  if os.getenv('LAGER_DECODE_ENV'):
93
124
  _decode_environment()
94
125
 
@@ -141,7 +172,6 @@ cli.add_command(logs)
141
172
  cli.add_command(binaries)
142
173
  cli.add_command(install)
143
174
  cli.add_command(uninstall)
144
- cli.add_command(factory)
145
175
  cli.add_command(terminal_cmd)
146
176
 
147
177
  def _schedule_update_check(ctx):
@@ -1,3 +1,3 @@
1
1
  [build-system]
2
- requires = ["setuptools>=45", "wheel"]
2
+ requires = ["setuptools>=45,<78", "wheel"]
3
3
  build-backend = "setuptools.build_meta"
@@ -1,73 +0,0 @@
1
- # Copyright 2024-2026 Lager Data LLC
2
- # SPDX-License-Identifier: Apache-2.0
3
-
4
- """
5
- lager.commands.utility.factory
6
-
7
- Start the Lager Factory webapp.
8
- """
9
-
10
- import os
11
- import socket
12
- import subprocess
13
- import sys
14
-
15
- import click
16
-
17
-
18
- def _get_local_ip():
19
- """Best-effort detection of the machine's LAN IP address."""
20
- try:
21
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
22
- s.settimeout(0)
23
- s.connect(('10.254.254.254', 1))
24
- ip = s.getsockname()[0]
25
- s.close()
26
- return ip
27
- except Exception:
28
- return '127.0.0.1'
29
-
30
-
31
- @click.command('factory')
32
- @click.option('--port', default=5001, type=int, help='Port number')
33
- @click.option('--host', default='0.0.0.0', help='Host to bind to')
34
- def factory(port, host):
35
- """Start the Lager Factory."""
36
- if port < 0 or port > 65535:
37
- click.secho(f'Error: Port must be between 0 and 65535, got {port}.', fg='red', err=True)
38
- raise SystemExit(1)
39
-
40
- # Locate webapp directory relative to CLI package
41
- cli_dir = os.path.dirname(os.path.dirname(os.path.dirname(
42
- os.path.abspath(__file__)
43
- )))
44
- webapp_dir = os.path.join(os.path.dirname(cli_dir), 'factory', 'webapp')
45
-
46
- if not os.path.isdir(webapp_dir):
47
- click.secho(
48
- f'Factory webapp not found at {webapp_dir}', fg='red', err=True
49
- )
50
- raise SystemExit(1)
51
-
52
- run_py = os.path.join(webapp_dir, 'run.py')
53
- if not os.path.isfile(run_py):
54
- click.secho(
55
- f'run.py not found at {run_py}', fg='red', err=True
56
- )
57
- raise SystemExit(1)
58
-
59
- local_ip = _get_local_ip()
60
- click.secho('Lager Factory starting...', fg='green')
61
- click.secho(f' Local: http://127.0.0.1:{port}')
62
- click.secho(f' Network: http://{local_ip}:{port}')
63
-
64
- env = dict(os.environ, PORT=str(port), HOST=host)
65
-
66
- try:
67
- subprocess.run(
68
- [sys.executable, 'run.py'],
69
- cwd=webapp_dir,
70
- env=env,
71
- )
72
- except KeyboardInterrupt:
73
- pass
File without changes
File without changes
File without changes
File without changes
File without changes