lager-cli 0.34.2__tar.gz → 0.34.4__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 (205) hide show
  1. {lager_cli-0.34.2/lager_cli.egg-info → lager_cli-0.34.4}/PKG-INFO +1 -1
  2. {lager_cli-0.34.2 → lager_cli-0.34.4}/__init__.py +1 -1
  3. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/diagnose.py +136 -1
  4. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/nets.py +60 -5
  5. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/communication/usb.py +22 -8
  6. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/power/battery.py +4 -4
  7. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/power/supply.py +4 -4
  8. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/utility/update.py +58 -0
  9. {lager_cli-0.34.2 → lager_cli-0.34.4}/core/net_helpers.py +22 -0
  10. {lager_cli-0.34.2 → lager_cli-0.34.4/lager_cli.egg-info}/PKG-INFO +1 -1
  11. {lager_cli-0.34.2 → lager_cli-0.34.4}/LICENSE +0 -0
  12. {lager_cli-0.34.2 → lager_cli-0.34.4}/MANIFEST.in +0 -0
  13. {lager_cli-0.34.2 → lager_cli-0.34.4}/README.md +0 -0
  14. {lager_cli-0.34.2 → lager_cli-0.34.4}/__main__.py +0 -0
  15. {lager_cli-0.34.2 → lager_cli-0.34.4}/address_utils.py +0 -0
  16. {lager_cli-0.34.2 → lager_cli-0.34.4}/battery/__init__.py +0 -0
  17. {lager_cli-0.34.2 → lager_cli-0.34.4}/battery/battery_tui.py +0 -0
  18. {lager_cli-0.34.2 → lager_cli-0.34.4}/battery/websocket_client.py +0 -0
  19. {lager_cli-0.34.2 → lager_cli-0.34.4}/box_storage.py +0 -0
  20. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/__init__.py +0 -0
  21. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/__init__.py +0 -0
  22. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/_host_ops.py +0 -0
  23. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/_mount_prep.py +0 -0
  24. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/_pip_validation.py +0 -0
  25. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/_shim_verbs.py +0 -0
  26. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/_ssh.py +0 -0
  27. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/box_group.py +0 -0
  28. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/boxes.py +0 -0
  29. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/config.py +0 -0
  30. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/dut.py +0 -0
  31. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/hello.py +0 -0
  32. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/instruments.py +0 -0
  33. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/labjack_pins.py +0 -0
  34. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/lock.py +0 -0
  35. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/net_tui.py +0 -0
  36. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/ssh.py +0 -0
  37. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/box/ssh_setup.py +0 -0
  38. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/communication/__init__.py +0 -0
  39. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/communication/ble.py +0 -0
  40. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/communication/blufi.py +0 -0
  41. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/communication/i2c.py +0 -0
  42. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/communication/router.py +0 -0
  43. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/communication/spi.py +0 -0
  44. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/communication/uart.py +0 -0
  45. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/communication/websocket_client.py +0 -0
  46. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/communication/wifi.py +0 -0
  47. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/development/__init__.py +0 -0
  48. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/development/arm.py +0 -0
  49. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/development/debug/__init__.py +0 -0
  50. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/development/debug/commands.py +0 -0
  51. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/development/debug/gdb.py +0 -0
  52. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/development/debug/net_cache.py +0 -0
  53. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/development/debug/service_client.py +0 -0
  54. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/development/debug/service_helper.py +0 -0
  55. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/development/debug/tunnel.py +0 -0
  56. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/development/devenv.py +0 -0
  57. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/development/python.py +0 -0
  58. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/measurement/__init__.py +0 -0
  59. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/measurement/adc.py +0 -0
  60. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/measurement/dac.py +0 -0
  61. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/measurement/energy.py +0 -0
  62. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/measurement/gpi.py +0 -0
  63. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/measurement/gpo.py +0 -0
  64. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/measurement/logic.py +0 -0
  65. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/measurement/scope.py +0 -0
  66. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/measurement/thermocouple.py +0 -0
  67. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/measurement/watt.py +0 -0
  68. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/power/__init__.py +0 -0
  69. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/power/battery_model_csv.py +0 -0
  70. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/power/eload.py +0 -0
  71. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/power/solar.py +0 -0
  72. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/utility/__init__.py +0 -0
  73. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/utility/_host_cli.py +0 -0
  74. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/utility/binaries.py +0 -0
  75. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/utility/defaults.py +0 -0
  76. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/utility/exec_.py +0 -0
  77. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/utility/install.py +0 -0
  78. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/utility/install_wheel.py +0 -0
  79. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/utility/login.py +0 -0
  80. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/utility/logs.py +0 -0
  81. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/utility/uninstall.py +0 -0
  82. {lager_cli-0.34.2 → lager_cli-0.34.4}/commands/utility/webcam.py +0 -0
  83. {lager_cli-0.34.2 → lager_cli-0.34.4}/config.py +0 -0
  84. {lager_cli-0.34.2 → lager_cli-0.34.4}/context/__init__.py +0 -0
  85. {lager_cli-0.34.2 → lager_cli-0.34.4}/context/ci_detection.py +0 -0
  86. {lager_cli-0.34.2 → lager_cli-0.34.4}/context/constants.py +0 -0
  87. {lager_cli-0.34.2 → lager_cli-0.34.4}/context/core.py +0 -0
  88. {lager_cli-0.34.2 → lager_cli-0.34.4}/context/error_handlers.py +0 -0
  89. {lager_cli-0.34.2 → lager_cli-0.34.4}/context/session.py +0 -0
  90. {lager_cli-0.34.2 → lager_cli-0.34.4}/core/__init__.py +0 -0
  91. {lager_cli-0.34.2 → lager_cli-0.34.4}/core/group_usage.py +0 -0
  92. {lager_cli-0.34.2 → lager_cli-0.34.4}/core/matchers.py +0 -0
  93. {lager_cli-0.34.2 → lager_cli-0.34.4}/core/net_group.py +0 -0
  94. {lager_cli-0.34.2 → lager_cli-0.34.4}/core/net_storage.py +0 -0
  95. {lager_cli-0.34.2 → lager_cli-0.34.4}/core/param_types.py +0 -0
  96. {lager_cli-0.34.2 → lager_cli-0.34.4}/core/ssh_utils.py +0 -0
  97. {lager_cli-0.34.2 → lager_cli-0.34.4}/core/utils.py +0 -0
  98. {lager_cli-0.34.2 → lager_cli-0.34.4}/core/version_skew.py +0 -0
  99. {lager_cli-0.34.2 → lager_cli-0.34.4}/core/ws_diagnose.py +0 -0
  100. {lager_cli-0.34.2 → lager_cli-0.34.4}/deployment/__init__.py +0 -0
  101. {lager_cli-0.34.2 → lager_cli-0.34.4}/deployment/scripts/__init__.py +0 -0
  102. {lager_cli-0.34.2 → lager_cli-0.34.4}/deployment/scripts/configure_docker_dns.py +0 -0
  103. {lager_cli-0.34.2 → lager_cli-0.34.4}/deployment/scripts/configure_docker_dns.sh +0 -0
  104. {lager_cli-0.34.2 → lager_cli-0.34.4}/deployment/scripts/convert_to_sparse_checkout.sh +0 -0
  105. {lager_cli-0.34.2 → lager_cli-0.34.4}/deployment/scripts/setup_and_deploy_box.sh +0 -0
  106. {lager_cli-0.34.2 → lager_cli-0.34.4}/deployment/scripts/setup_ssh_key.sh +0 -0
  107. {lager_cli-0.34.2 → lager_cli-0.34.4}/deployment/security/__init__.py +0 -0
  108. {lager_cli-0.34.2 → lager_cli-0.34.4}/deployment/security/secure_box_firewall.sh +0 -0
  109. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/__init__.py +0 -0
  110. {lager_cli-0.34.2/impl/power → lager_cli-0.34.4/elftools/common}/__init__.py +0 -0
  111. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/common/construct_utils.py +0 -0
  112. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/common/exceptions.py +0 -0
  113. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/common/py3compat.py +0 -0
  114. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/common/utils.py +0 -0
  115. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/construct/__init__.py +0 -0
  116. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/construct/adapters.py +0 -0
  117. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/construct/core.py +0 -0
  118. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/construct/debug.py +0 -0
  119. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/construct/macros.py +0 -0
  120. {lager_cli-0.34.2/impl → lager_cli-0.34.4/elftools/dwarf}/__init__.py +0 -0
  121. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/abbrevtable.py +0 -0
  122. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/aranges.py +0 -0
  123. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/callframe.py +0 -0
  124. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/compileunit.py +0 -0
  125. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/constants.py +0 -0
  126. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/descriptions.py +0 -0
  127. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/die.py +0 -0
  128. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/dwarf_expr.py +0 -0
  129. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/dwarfinfo.py +0 -0
  130. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/enums.py +0 -0
  131. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/lineprogram.py +0 -0
  132. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/locationlists.py +0 -0
  133. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/namelut.py +0 -0
  134. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/ranges.py +0 -0
  135. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/dwarf/structs.py +0 -0
  136. {lager_cli-0.34.2/elftools/elf → lager_cli-0.34.4/elftools/ehabi}/__init__.py +0 -0
  137. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/ehabi/constants.py +0 -0
  138. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/ehabi/decoder.py +0 -0
  139. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/ehabi/ehabiinfo.py +0 -0
  140. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/ehabi/structs.py +0 -0
  141. {lager_cli-0.34.2/elftools/ehabi → lager_cli-0.34.4/elftools/elf}/__init__.py +0 -0
  142. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/elf/constants.py +0 -0
  143. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/elf/descriptions.py +0 -0
  144. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/elf/dynamic.py +0 -0
  145. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/elf/elffile.py +0 -0
  146. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/elf/enums.py +0 -0
  147. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/elf/gnuversions.py +0 -0
  148. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/elf/hash.py +0 -0
  149. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/elf/notes.py +0 -0
  150. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/elf/relocation.py +0 -0
  151. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/elf/sections.py +0 -0
  152. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/elf/segments.py +0 -0
  153. {lager_cli-0.34.2 → lager_cli-0.34.4}/elftools/elf/structs.py +0 -0
  154. {lager_cli-0.34.2 → lager_cli-0.34.4}/errors.py +0 -0
  155. {lager_cli-0.34.2 → lager_cli-0.34.4}/exceptions.py +0 -0
  156. {lager_cli-0.34.2 → lager_cli-0.34.4}/gateway_auth.py +0 -0
  157. {lager_cli-0.34.2/elftools/dwarf → lager_cli-0.34.4/impl}/__init__.py +0 -0
  158. {lager_cli-0.34.2 → lager_cli-0.34.4}/impl/box_config.py +0 -0
  159. {lager_cli-0.34.2 → lager_cli-0.34.4}/impl/measurement/__init__.py +0 -0
  160. {lager_cli-0.34.2 → lager_cli-0.34.4}/impl/measurement/scope.py +0 -0
  161. {lager_cli-0.34.2 → lager_cli-0.34.4}/impl/measurement/scope_stream.py +0 -0
  162. {lager_cli-0.34.2/elftools/common → lager_cli-0.34.4/impl/power}/__init__.py +0 -0
  163. {lager_cli-0.34.2 → lager_cli-0.34.4}/impl/power/enable_disable.py +0 -0
  164. {lager_cli-0.34.2 → lager_cli-0.34.4}/lager_cli.egg-info/SOURCES.txt +0 -0
  165. {lager_cli-0.34.2 → lager_cli-0.34.4}/lager_cli.egg-info/dependency_links.txt +0 -0
  166. {lager_cli-0.34.2 → lager_cli-0.34.4}/lager_cli.egg-info/entry_points.txt +0 -0
  167. {lager_cli-0.34.2 → lager_cli-0.34.4}/lager_cli.egg-info/requires.txt +0 -0
  168. {lager_cli-0.34.2 → lager_cli-0.34.4}/lager_cli.egg-info/top_level.txt +0 -0
  169. {lager_cli-0.34.2 → lager_cli-0.34.4}/main.py +0 -0
  170. {lager_cli-0.34.2 → lager_cli-0.34.4}/pyproject.toml +0 -0
  171. {lager_cli-0.34.2 → lager_cli-0.34.4}/safe_unpickle.py +0 -0
  172. {lager_cli-0.34.2 → lager_cli-0.34.4}/setup.cfg +0 -0
  173. {lager_cli-0.34.2 → lager_cli-0.34.4}/setup.py +0 -0
  174. {lager_cli-0.34.2 → lager_cli-0.34.4}/simple_hdlc.py +0 -0
  175. {lager_cli-0.34.2 → lager_cli-0.34.4}/sort_utils.py +0 -0
  176. {lager_cli-0.34.2 → lager_cli-0.34.4}/status.py +0 -0
  177. {lager_cli-0.34.2 → lager_cli-0.34.4}/supply/__init__.py +0 -0
  178. {lager_cli-0.34.2 → lager_cli-0.34.4}/supply/supply_tui.py +0 -0
  179. {lager_cli-0.34.2 → lager_cli-0.34.4}/supply/websocket_client.py +0 -0
  180. {lager_cli-0.34.2 → lager_cli-0.34.4}/terminal/__init__.py +0 -0
  181. {lager_cli-0.34.2 → lager_cli-0.34.4}/terminal/core/__init__.py +0 -0
  182. {lager_cli-0.34.2 → lager_cli-0.34.4}/terminal/core/executor.py +0 -0
  183. {lager_cli-0.34.2 → lager_cli-0.34.4}/terminal/main.py +0 -0
  184. {lager_cli-0.34.2 → lager_cli-0.34.4}/terminal/ui/__init__.py +0 -0
  185. {lager_cli-0.34.2 → lager_cli-0.34.4}/terminal/ui/completer.py +0 -0
  186. {lager_cli-0.34.2 → lager_cli-0.34.4}/terminal/ui/display.py +0 -0
  187. {lager_cli-0.34.2 → lager_cli-0.34.4}/terminal/ui/logo.py +0 -0
  188. {lager_cli-0.34.2 → lager_cli-0.34.4}/terminal/ui/repl.py +0 -0
  189. {lager_cli-0.34.2 → lager_cli-0.34.4}/terminal/ui/themes.py +0 -0
  190. {lager_cli-0.34.2 → lager_cli-0.34.4}/tests/test_box_lager_imports.py +0 -0
  191. {lager_cli-0.34.2 → lager_cli-0.34.4}/tests/test_box_storage.py +0 -0
  192. {lager_cli-0.34.2 → lager_cli-0.34.4}/tests/test_gateway_auth.py +0 -0
  193. {lager_cli-0.34.2 → lager_cli-0.34.4}/tests/test_gateway_callsites.py +0 -0
  194. {lager_cli-0.34.2 → lager_cli-0.34.4}/tests/test_host_cli.py +0 -0
  195. {lager_cli-0.34.2 → lager_cli-0.34.4}/tests/test_io_imports.py +0 -0
  196. {lager_cli-0.34.2 → lager_cli-0.34.4}/tests/test_update_gate.py +0 -0
  197. {lager_cli-0.34.2 → lager_cli-0.34.4}/update_check.py +0 -0
  198. {lager_cli-0.34.2 → lager_cli-0.34.4}/vendor/PyCRC/CRC16.py +0 -0
  199. {lager_cli-0.34.2 → lager_cli-0.34.4}/vendor/PyCRC/CRC16DNP.py +0 -0
  200. {lager_cli-0.34.2 → lager_cli-0.34.4}/vendor/PyCRC/CRC16Kermit.py +0 -0
  201. {lager_cli-0.34.2 → lager_cli-0.34.4}/vendor/PyCRC/CRC16SICK.py +0 -0
  202. {lager_cli-0.34.2 → lager_cli-0.34.4}/vendor/PyCRC/CRC32.py +0 -0
  203. {lager_cli-0.34.2 → lager_cli-0.34.4}/vendor/PyCRC/CRCCCITT.py +0 -0
  204. {lager_cli-0.34.2 → lager_cli-0.34.4}/vendor/PyCRC/__init__.py +0 -0
  205. {lager_cli-0.34.2 → lager_cli-0.34.4}/vendor/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lager-cli
3
- Version: 0.34.2
3
+ Version: 0.34.4
4
4
  Summary: Lager CLI - Box and Docker connectivity
5
5
  Home-page: https://github.com/lagerdata/lager
6
6
  Author: Lager Data
@@ -7,4 +7,4 @@ Lager CLI
7
7
  A Command Line Interface for Lager Data
8
8
  """
9
9
 
10
- __version__ = '0.34.2'
10
+ __version__ = '0.34.4'
@@ -120,7 +120,12 @@ def _call(url: str, timeout: float = 8.0) -> dict:
120
120
  r = requests.get(url, timeout=timeout, headers=auth_headers_for_box(box_host))
121
121
  r = _check_gateway(r, box_host)
122
122
  if r.status_code == 404:
123
- return {'unavailable': 'endpoint not on this box (pre-0.20 image)'}
123
+ # Deliberately does not name a version: this is shared by every
124
+ # diagnose endpoint, and they were not all added in the same
125
+ # release. Naming 0.20 sent a reader looking for the wrong thing
126
+ # when a newer endpoint was the one missing.
127
+ return {'unavailable': 'endpoint not served by this box; '
128
+ 'update the box to use this section'}
124
129
  if r.status_code >= 400:
125
130
  return {'transport_error': f'HTTP {r.status_code}: {r.text[:200]}'}
126
131
  return r.json()
@@ -398,6 +403,128 @@ def _classify_jlink(usb_info: dict, jlink_info: dict) -> tuple[str, str]:
398
403
  return ('yellow', 'UNCLEAR — review the J-Link section above and rerun if needed.')
399
404
 
400
405
 
406
+ def _fmt_usbhub_lines(d: dict):
407
+ """Render the USB-hub section."""
408
+ devnum = d.get('devnum')
409
+ lo, hi = d.get('devnum_min'), d.get('devnum_max')
410
+ med = d.get('devnum_median')
411
+ churn = f'{devnum}' if devnum is not None else '—'
412
+ if (devnum is not None and lo is not None and hi is not None
413
+ and med is not None):
414
+ # The interpretation, inline, because the raw number means nothing to
415
+ # someone who does not already know how devnums are assigned.
416
+ churn += f' (bench: {lo}–{hi}, median {med})'
417
+ if devnum > med * 1.3 and devnum >= hi:
418
+ churn += ' <- far above its peers: this device has re-enumerated'
419
+
420
+ visible = d.get('serial_visible_to_sdk')
421
+ visible_s = {True: 'yes', False: 'NO', None: 'unknown'}.get(visible, 'unknown')
422
+
423
+ lines = [
424
+ f'enumerated: {d.get("enumerated")}',
425
+ f'sysfs: {d.get("sysfs_path") or "—"}',
426
+ f'devnum: {churn}',
427
+ f'seen by SDK: {visible_s}',
428
+ ]
429
+ serials = d.get('sdk_scan_serials')
430
+ if serials is not None:
431
+ lines.append(f' SDK scan: {", ".join(serials) if serials else "(nothing)"}')
432
+ if d.get('sdk_scan_error'):
433
+ lines.append(f' scan error: {d["sdk_scan_error"]}')
434
+
435
+ for dev in (d.get('vendor_devices') or []):
436
+ lines.append(
437
+ f' bus: {dev.get("sysfs_name")} {dev.get("vid")}:{dev.get("pid")}'
438
+ f' serial={dev.get("serial") or "(none)"}'
439
+ f' devnum={dev.get("devnum")} {dev.get("product") or ""}'.rstrip()
440
+ )
441
+
442
+ if d.get('probe_skipped'):
443
+ lines.append(f'hub open: skipped — {d.get("probe_skip_reason")}')
444
+ elif d.get('hub_opens') is True:
445
+ lines.append('hub open: OK')
446
+ elif d.get('hub_opens') is False:
447
+ lines.append(f'hub open: FAILED — {d.get("hub_open_error")}')
448
+ if d.get('hub_open_detail'):
449
+ lines.append(f' detail: {d["hub_open_detail"]}')
450
+ for h in (d.get('holders') or []):
451
+ lines.append(f' held by: pid {h.get("pid")} {h.get("command") or ""}'.rstrip())
452
+ return lines
453
+
454
+
455
+ def _classify_usb_hub(d: dict):
456
+ """(colour, headline) for a USB hub net. Most specific first."""
457
+ if d.get('unavailable') or d.get('transport_error'):
458
+ return ('yellow',
459
+ 'UNCLEAR — this box does not serve /diagnose/usbhub. '
460
+ 'Update the box to diagnose hub nets.')
461
+
462
+ if d.get('enumerated') is False:
463
+ return ('red',
464
+ 'NOT ENUMERATED — the kernel does not see this hub at all. '
465
+ 'Check its upstream cable and whether its port has power.')
466
+
467
+ if d.get('probe_skipped'):
468
+ return ('yellow',
469
+ f'BUSY — {d.get("probe_skip_reason")}. Something else is '
470
+ f'driving this hub; rerun when it is idle.')
471
+
472
+ # The distinction this endpoint exists for: enumerated, and the vendor SDK
473
+ # still cannot see it. sysfs and lsof both report a healthy device here.
474
+ if d.get('serial_visible_to_sdk') is False:
475
+ extra = ''
476
+ devnum, med = d.get('devnum'), d.get('devnum_median')
477
+ if devnum is not None and med is not None and devnum > med * 1.3:
478
+ extra = (f' Its devnum ({devnum}) is well above the rest of the '
479
+ f'bench (median {med}), so it has been re-enumerating.')
480
+ return ('red',
481
+ 'HUB WEDGED (electrical) — the kernel has it enumerated but it '
482
+ 'does not answer its vendor SDK. That is the hub or its cabling, '
483
+ 'not the box software: restarting box services will not help.'
484
+ + extra)
485
+
486
+ if d.get('hub_opens') is False:
487
+ holders = d.get('holders') or []
488
+ if holders:
489
+ who = ', '.join(f'pid {h.get("pid")}' for h in holders)
490
+ return ('yellow',
491
+ f'HUB CLAIMED — the SDK can see it but the open failed and '
492
+ f'it is held by {who}.')
493
+ return ('red', f'HUB WILL NOT OPEN — {d.get("hub_open_error")}')
494
+
495
+ if d.get('hub_opens') is True:
496
+ return ('green',
497
+ 'REACHABLE — the hub enumerated, the SDK sees it, and it opened '
498
+ 'and closed cleanly. This tests the path to the hub, not the '
499
+ 'DUT behind any given port.')
500
+
501
+ return ('yellow', 'UNCLEAR — review the section above and rerun if needed.')
502
+
503
+
504
+ def _diagnose_usb_hub(box_ip: str, address: str) -> None:
505
+ """USB-hub branch of `lager diagnose`.
506
+
507
+ Fetches the instrument-agnostic host-side USB section plus
508
+ `/diagnose/usbhub` in parallel, renders them, and classifies.
509
+ """
510
+ urls = {
511
+ 'usb': f'http://{box_ip}:9000/diagnose/usb?address={address}',
512
+ 'hub': f'http://{box_ip}:9000/diagnose/usbhub?address={address}',
513
+ }
514
+ results: dict[str, dict] = {}
515
+ with ThreadPoolExecutor(max_workers=2) as pool:
516
+ futures = {pool.submit(_call, url): name for name, url in urls.items()}
517
+ for fut in as_completed(futures):
518
+ results[futures[fut]] = fut.result()
519
+
520
+ _print_section('USB (host-side)', results['usb'], _fmt_usb_lines)
521
+ _print_section('USB hub', results['hub'], _fmt_usbhub_lines)
522
+
523
+ color, headline = _classify_usb_hub(results['hub'])
524
+ click.echo()
525
+ click.echo(click.style(f'Classification: {headline}', fg=color, bold=True))
526
+
527
+
401
528
  def _diagnose_debug(box_ip: str, net: str, address: str) -> None:
402
529
  """Debug-net (J-Link) branch of `lager diagnose`.
403
530
 
@@ -471,6 +598,14 @@ def diagnose(ctx, net, box, net_type):
471
598
  _diagnose_debug(resolved_box, net, address)
472
599
  return
473
600
 
601
+ # USB hub nets are not USB-TMC either, and the pyvisa probe below has
602
+ # nothing to say about them. They used to fall through to the generic
603
+ # "NOT USB-TMC, check the role command yourself" bucket, which is exactly
604
+ # no help when the question is why a hub will not answer.
605
+ if role == 'usb':
606
+ _diagnose_usb_hub(resolved_box, address)
607
+ return
608
+
474
609
  # Fire the three endpoints in parallel.
475
610
  # Port mapping inside the box container:
476
611
  # 8080 — hardware_service.py (/invoke, /diagnose/dispatcher)
@@ -739,6 +739,7 @@ def _display_table(
739
739
  records,
740
740
  state_map: dict[str, str | None] | None = None,
741
741
  reason_map: dict[str, str] | None = None,
742
+ code_map: dict[str, str] | None = None,
742
743
  ):
743
744
  """Render the grouped net table.
744
745
 
@@ -850,7 +851,7 @@ def _display_table(
850
851
  click.secho(f"{row[0]:<{col_w[0]}}", bold=True, nl=False)
851
852
  click.echo(" " + " ".join(f"{row[i]:<{col_w[i]}}" for i in range(1, len(col_w))))
852
853
 
853
- _print_state_reasons(records, reason_map)
854
+ _print_state_reasons(records, reason_map, code_map)
854
855
 
855
856
 
856
857
  # Roles with no live probe report no state by design (uart, spi, i2c, ...).
@@ -858,12 +859,48 @@ def _display_table(
858
859
  # on every bench and stops being read.
859
860
  _EXPECTED_NO_STATE_REASON = "no probe for role"
860
861
 
862
+ # What a classification means for the person reading the footnote, and how
863
+ # loudly to say it. Only "go and look at the hardware" is red; the others are
864
+ # usually a bench in a normal state or a net pointing at the wrong device.
865
+ #
866
+ # Looked up with .get() and ALWAYS optional -- a newer box can send a code this
867
+ # CLI has never heard of, and the box's own `reason` already says everything
868
+ # needed. A KeyError here would crash the exact command someone runs when
869
+ # something is already broken.
870
+ _REASON_CODE_NOTES = {
871
+ "hub-unreachable": (
872
+ "the hub is on the USB bus but will not answer. That is hardware:\n"
873
+ " check its power and upstream cable. Restarting box services\n"
874
+ " will not help.",
875
+ "red",
876
+ ),
877
+ "hub-absent": (
878
+ "the hub is not on the USB bus at all -- unplugged, or its\n"
879
+ " upstream port is off.",
880
+ "yellow",
881
+ ),
882
+ "hub-serial-mismatch": (
883
+ "hubs are on the bus but none has the serial this net names.\n"
884
+ " Check the net's address.",
885
+ "yellow",
886
+ ),
887
+ }
888
+
861
889
 
862
- def _print_state_reasons(records, reason_map: dict[str, str] | None) -> None:
890
+ def _print_state_reasons(
891
+ records,
892
+ reason_map: dict[str, str] | None,
893
+ code_map: dict[str, str] | None = None,
894
+ ) -> None:
863
895
  """Explain the ``–`` cells, grouped by reason.
864
896
 
865
897
  Only unexpected ones: a role with no probe is normal. Silent when the box
866
898
  sent no reasons at all, which is what an older box does.
899
+
900
+ ``code_map`` only ever adds a remedy line under a group. The box's
901
+ ``reason`` is always printed and always self-sufficient, so an older box
902
+ that sends no codes, or a newer one sending a code this CLI does not know,
903
+ both render exactly as they did before.
867
904
  """
868
905
  if not reason_map:
869
906
  return
@@ -879,11 +916,19 @@ def _print_state_reasons(records, reason_map: dict[str, str] | None) -> None:
879
916
  if not by_reason:
880
917
  return
881
918
 
919
+ code_map = code_map or {}
882
920
  click.echo()
883
921
  click.secho("Nets reporting no state:", bold=True, err=True)
884
922
  for reason in sorted(by_reason):
885
- names = ", ".join(by_reason[reason])
886
- click.echo(f" {names}: {reason}", err=True)
923
+ names = by_reason[reason]
924
+ click.echo(f" {', '.join(names)}: {reason}", err=True)
925
+ # One remedy per group, not per net: four dead nets on one hub are one
926
+ # fault, and printing the same sentence four times buries it.
927
+ codes = {code_map.get(n) for n in names}
928
+ note = _REASON_CODE_NOTES.get(codes.pop()) if len(codes) == 1 else None
929
+ if note:
930
+ text, colour = note
931
+ click.secho(f" -> {text}", fg=colour, err=True)
887
932
  if any(r == "deadline" for r in by_reason):
888
933
  click.secho(
889
934
  " Note: 'deadline' is the box's budget for probing every\n"
@@ -2251,6 +2296,10 @@ def state_cmd(ctx: click.Context, box: str | None, as_json: bool) -> None:
2251
2296
  # Why a net has no state. Absent on boxes older than this field; the
2252
2297
  # display and the JSON both have to read as they did before in that case.
2253
2298
  reason_map: dict[str, str] = {}
2299
+ # The machine-readable half of the same answer, where the box sent one.
2300
+ # Never required: an older box sends no codes and everything below falls
2301
+ # back to printing `reason`, which is what it did before this existed.
2302
+ code_map: dict[str, str] = {}
2254
2303
  state_list = _fetch_net_state(resolved_box)
2255
2304
  if state_list is None:
2256
2305
  click.secho(
@@ -2265,6 +2314,9 @@ def state_cmd(ctx: click.Context, box: str | None, as_json: bool) -> None:
2265
2314
  reason = entry.get("reason")
2266
2315
  if reason:
2267
2316
  reason_map[name] = reason
2317
+ code = entry.get("reason_code")
2318
+ if code:
2319
+ code_map[name] = code
2268
2320
 
2269
2321
  if as_json:
2270
2322
  merged = []
@@ -2273,11 +2325,14 @@ def state_cmd(ctx: click.Context, box: str | None, as_json: bool) -> None:
2273
2325
  item = {**rec, "live_state": state_map.get(name)}
2274
2326
  if name in reason_map:
2275
2327
  item["live_state_reason"] = reason_map[name]
2328
+ if name in code_map:
2329
+ item["live_state_reason_code"] = code_map[name]
2276
2330
  merged.append(item)
2277
2331
  click.echo(json.dumps(merged, indent=2))
2278
2332
  return
2279
2333
 
2280
- _display_table(records, state_map=state_map, reason_map=reason_map)
2334
+ _display_table(records, state_map=state_map, reason_map=reason_map,
2335
+ code_map=code_map)
2281
2336
 
2282
2337
 
2283
2338
  @nets.command("show", help="Show full details of a saved net, including metadata")
@@ -25,6 +25,7 @@ from ...core.net_helpers import (
25
25
  list_nets_by_role,
26
26
  display_nets_table,
27
27
  validate_net_exists,
28
+ box_command_error,
28
29
  echo_box_request_failure,
29
30
  NET_HTTP_PORT,
30
31
  )
@@ -49,6 +50,18 @@ def _display_usb_nets(ctx: click.Context, box: str) -> None:
49
50
  display_nets_table(nets, empty_message="No USB nets found on this box.")
50
51
 
51
52
 
53
+ # Must exceed the box's own worst case for /usb/command, or the box's
54
+ # structured errors are undeliverable by construction: the client gives up
55
+ # before the answer it is waiting for exists. The handler's bounds are
56
+ # additive — up to `_USB_LOCK_TIMEOUT_S` (10s) queueing on the process-wide
57
+ # hub lock, then up to `HUB_OP_TIMEOUT_S` (30s) in the driver deadline — so a
58
+ # wedged hub answers 504 at ~40s. A 30s client timeout turned that into a
59
+ # transport error, which reads as "the box is unreachable" and hides both the
60
+ # real diagnosis and the fact that the box already scheduled its own recovery.
61
+ # Raise this if either box-side bound grows.
62
+ _USB_COMMAND_TIMEOUT_S = 45
63
+
64
+
52
65
  def _invoke_remote(
53
66
  ctx: click.Context,
54
67
  net_name: str,
@@ -68,18 +81,16 @@ def _invoke_remote(
68
81
  from ...box_storage import _check_gateway
69
82
 
70
83
  url = f"http://{target_box}:{NET_HTTP_PORT}/usb/command"
71
- # First contact with an Acroname hub runs BrainStem USB discovery on the
72
- # box, which can take well over 10s cold; budget for it.
73
84
  try:
74
85
  resp = requests.post(
75
86
  url,
76
87
  json={"netname": net_name, "action": command},
77
- timeout=30,
88
+ timeout=_USB_COMMAND_TIMEOUT_S,
78
89
  headers=auth_headers_for_box(target_box),
79
90
  )
80
91
  resp = _check_gateway(resp, target_box)
81
92
  except (requests.ConnectionError, requests.Timeout) as e:
82
- echo_box_request_failure(target_box, e, timeout=30)
93
+ echo_box_request_failure(target_box, e, timeout=_USB_COMMAND_TIMEOUT_S)
83
94
  ctx.exit(1)
84
95
  except requests.RequestException as e:
85
96
  click.secho(f"Error: USB request to box failed: {e}", fg='red', err=True)
@@ -99,11 +110,14 @@ def _invoke_remote(
99
110
  click.echo(f"[OK] {message}")
100
111
  return
101
112
 
102
- error = result.get('error') or f'USB command failed (HTTP {resp.status_code})'
103
- if resp.status_code == 404:
104
- error = (f"{error}. This box image does not expose /usb/command; "
105
- f"update the box.")
113
+ error = box_command_error(
114
+ result, resp.status_code, '/usb/command',
115
+ f'USB command failed (HTTP {resp.status_code})')
106
116
  click.secho(f"Error: {error}", fg='red', err=True)
117
+
118
+ detail = result.get('reason_detail')
119
+ if detail:
120
+ click.secho(f" {detail}", fg='yellow', err=True)
107
121
  ctx.exit(1)
108
122
 
109
123
 
@@ -21,6 +21,7 @@ import click
21
21
  from ...core.net_group import NetGroup
22
22
  # Import consolidated helpers from cli.core.net_helpers
23
23
  from ...core.net_helpers import (
24
+ box_command_error,
24
25
  require_netname,
25
26
  resolve_box,
26
27
  validate_net,
@@ -81,10 +82,9 @@ def _run_backend(ctx, box, action: str, **params):
81
82
  click.echo(f"\033[92m{result.get('message', 'Command executed')}\033[0m")
82
83
  return
83
84
 
84
- error = result.get('error') or f"battery command failed (HTTP {response.status_code})"
85
- if response.status_code == 404:
86
- error = (f"{error}. This box image does not expose /battery/command; "
87
- f"update the box.")
85
+ error = box_command_error(
86
+ result, response.status_code, '/battery/command',
87
+ f"battery command failed (HTTP {response.status_code})")
88
88
  click.secho(f"Error: {error}", fg='red', err=True)
89
89
  raise SystemExit(1)
90
90
 
@@ -24,6 +24,7 @@ import click
24
24
  from ...core.net_group import NetGroup
25
25
  # Import consolidated helpers from cli.core.net_helpers
26
26
  from ...core.net_helpers import (
27
+ box_command_error,
27
28
  require_netname,
28
29
  resolve_box,
29
30
  validate_net,
@@ -127,10 +128,9 @@ def _run_backend(ctx, box, action: str, **params):
127
128
  click.echo(f"[OK] {result.get('message', 'Command executed')}")
128
129
  return
129
130
 
130
- error = result.get('error') or f"supply command failed (HTTP {response.status_code})"
131
- if response.status_code == 404:
132
- error = (f"{error}. This box image does not expose /supply/command; "
133
- f"update the box.")
131
+ error = box_command_error(
132
+ result, response.status_code, '/supply/command',
133
+ f"supply command failed (HTTP {response.status_code})")
134
134
  click.secho(f"Error: {error}", fg='red', err=True)
135
135
  raise SystemExit(1)
136
136
 
@@ -103,6 +103,42 @@ def wait_for_box_ready(box_ip, *, timeout_s=60, initial_delay_s=2):
103
103
  return False
104
104
 
105
105
 
106
+ # Files whose contents are secret and whose owner therefore matters as much as
107
+ # their mode. Mode 0600 grants the OWNER alone, so tightening one of these under
108
+ # the wrong owner locks the runtime out of it entirely.
109
+ _SECRET_FILES = ('/etc/lager/org_secrets.json', '/etc/lager/secret_key')
110
+
111
+ # uid 33 is www-data, the user the container runs as. Hardcoded because it is
112
+ # baked into the container image, not discovered at runtime.
113
+ _CONTAINER_UID = 33
114
+
115
+
116
+ def _secret_ownership_shell_cmd(files=_SECRET_FILES, uid=_CONTAINER_UID):
117
+ """Shell snippet that gives the secret files back to the container user.
118
+
119
+ A secrets file copied onto a box by hand belongs to whoever copied it. The
120
+ boot script then tightens it to 0600 — successfully, because that same user
121
+ owns it — and uid 33 can no longer read its own secrets. Nothing fails
122
+ loudly: secret injection just returns empty and scripts break far from the
123
+ cause.
124
+
125
+ Best-effort by construction. `sudo -n` so a box whose sudoers predates the
126
+ chown grant fails instantly instead of blocking on a password prompt no one
127
+ is there to answer, and every branch swallows its failure — an unreadable
128
+ secrets file must never be the reason an update fails. Boxes that miss the
129
+ repair still get the boot-time warning telling them what to run by hand.
130
+
131
+ No chmod: after the chown this user no longer owns the file, and the
132
+ container tightens the mode itself on the next load.
133
+ """
134
+ quoted = ' '.join(shlex.quote(f) for f in files)
135
+ return (
136
+ f'for f in {quoted}; do '
137
+ f'if [ -f "$f" ]; then sudo -n /bin/chown {uid}:{uid} "$f" 2>/dev/null || true; fi; '
138
+ 'done; true'
139
+ )
140
+
141
+
106
142
  def _flatten_shell_cmd():
107
143
  """Shell snippet that flattens the sparse-checkout layout (`box/` -> root).
108
144
 
@@ -1846,6 +1882,27 @@ def _update_logic(ctx, *, box, yes, version, verbose, check, force=False):
1846
1882
  # to be running: 'container-down' falls through to the rebuild path below
1847
1883
  # and brings the box back up. `--force` still skips the early-exit
1848
1884
  # unconditionally.
1885
+ # Secret files must be owned by uid 33 (the container user), not merely
1886
+ # mode 0600. 0600 grants the owner alone, so a secrets file left under a
1887
+ # different owner — e.g. copied onto the box by hand — locks the runtime
1888
+ # out of it the moment anything tightens the mode. The symptom is silent:
1889
+ # secret injection returns empty and scripts fail nowhere near the cause.
1890
+ #
1891
+ # BEFORE the rebuild gate on purpose, like the host-CLI reconcile below.
1892
+ # This is a heal, not part of building an image, and the box that most
1893
+ # needs it is the one already running current code: an operator whose
1894
+ # secrets stopped working reaches for `lager update`, and if the repair
1895
+ # sat after the gate that update would early-exit "already up to date"
1896
+ # and fix nothing. Running it here costs one short SSH round-trip on the
1897
+ # fast path and makes the remedy work whenever it is reached for.
1898
+ #
1899
+ # `lager update` is the only path that runs with real privilege, so it is
1900
+ # where the repair belongs; the box's boot script can only warn when it
1901
+ # lacks the grant. Best-effort throughout — an unreadable secrets file must
1902
+ # never be the reason an update fails — so boxes that miss the repair still
1903
+ # get the boot warning telling them what to run.
1904
+ run_ssh_command_with_output(_secret_ownership_shell_cmd(), timeout_secs=30)
1905
+
1849
1906
  _gate = _rebuild_gate_verdict(
1850
1907
  facts,
1851
1908
  git_sync_confirmed=git_sync_confirmed,
@@ -2323,6 +2380,7 @@ def _update_logic(ctx, *, box, yes, version, verbose, check, force=False):
2323
2380
  ctx.exit(1)
2324
2381
  log_status('OK', 'green')
2325
2382
 
2383
+
2326
2384
  # Persist the build-inputs hash now that /etc/lager is group-writable by this
2327
2385
  # user (Step 10). This is what lets the next run's cache-validity check short-circuit to
2328
2386
  # the no-op fast path instead of rebuilding; a stale hash defeats it. Unlike
@@ -166,6 +166,28 @@ NET_HTTP_PORT = 9000
166
166
  _NET_HTTP_TIMEOUT = 10
167
167
 
168
168
 
169
+ def box_command_error(result, status_code, endpoint, fallback) -> str:
170
+ """Build the user-facing error for a failed :9000 command response.
171
+
172
+ The "update the box" hint is gated on the SHAPE of the body, not on the
173
+ status code. Every :9000 command handler sets ``success`` in its own JSON,
174
+ including its 404s — and a handler's 404 means the net or its instrument
175
+ was not found, which an update cannot fix. Only Flask's built-in 404, which
176
+ carries no ``success`` key, means this box image predates the endpoint.
177
+
178
+ Keying the hint on the code alone told people to update the box whenever an
179
+ instrument was unplugged: an absent USB hub reported "device-not-found: No
180
+ Acroname hub detected on USB with serial 0x… . This box image does not
181
+ expose /usb/command; update the box." Both halves cannot be true, and the
182
+ second one sends the diagnosis somewhere the fault never was.
183
+ """
184
+ body = result if isinstance(result, dict) else {}
185
+ error = body.get('error') or fallback
186
+ if status_code == 404 and 'success' not in body:
187
+ error = f"{error}. This box image does not expose {endpoint}; update the box."
188
+ return error
189
+
190
+
169
191
  def echo_box_request_failure(box_ip: str, exc, timeout=None) -> None:
170
192
  """Print the right diagnosis for a failed box HTTP call.
171
193
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lager-cli
3
- Version: 0.34.2
3
+ Version: 0.34.4
4
4
  Summary: Lager CLI - Box and Docker connectivity
5
5
  Home-page: https://github.com/lagerdata/lager
6
6
  Author: Lager Data
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes