aether-robotics 4.2.0a2__tar.gz → 4.2.0a3__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 (213) hide show
  1. {aether_robotics-4.2.0a2/aether_robotics.egg-info → aether_robotics-4.2.0a3}/PKG-INFO +1 -1
  2. aether_robotics-4.2.0a3/VERSION +1 -0
  3. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/__init__.py +1 -1
  4. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/app.py +21 -1
  5. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/tui/app.py +19 -9
  6. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/tui/widgets/__init__.py +4 -3
  7. aether_robotics-4.2.0a3/aether/tui/widgets/agent.py +297 -0
  8. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/tui/widgets/common.py +24 -1
  9. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3/aether_robotics.egg-info}/PKG-INFO +1 -1
  10. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/pyproject.toml +1 -1
  11. aether_robotics-4.2.0a3/tests/test_tui_widgets.py +277 -0
  12. aether_robotics-4.2.0a2/VERSION +0 -1
  13. aether_robotics-4.2.0a2/aether/tui/widgets/agent.py +0 -132
  14. aether_robotics-4.2.0a2/tests/test_tui_widgets.py +0 -131
  15. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/LICENSE +0 -0
  16. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/MANIFEST.in +0 -0
  17. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/README.md +0 -0
  18. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/__main__.py +0 -0
  19. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/actions/__init__.py +0 -0
  20. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/actions/abstract_actions.py +0 -0
  21. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/__init__.py +0 -0
  22. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/_generator.py +0 -0
  23. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/adeept_hat_v3.py +0 -0
  24. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/base_adapter.py +0 -0
  25. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/drone_adapter.py +0 -0
  26. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/freenove_4wd.py +0 -0
  27. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/freenove_mecanum.py +0 -0
  28. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/gopigo3.py +0 -0
  29. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/l298n_direct_gpio.py +0 -0
  30. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/pca9685_generic.py +0 -0
  31. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/rover_adapter.py +0 -0
  32. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/sunfounder_picarx.py +0 -0
  33. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/sunfounder_pidog.py +0 -0
  34. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/universal_adapter.py +0 -0
  35. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/waveshare_motor_driver.py +0 -0
  36. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/adapters/yahboom_robot_hat.py +0 -0
  37. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/__init__.py +0 -0
  38. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/adaptation_agent.py +0 -0
  39. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/camera_agent.py +0 -0
  40. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/correction_agent.py +0 -0
  41. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/execution_agent.py +0 -0
  42. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/fault_agent.py +0 -0
  43. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/memory_agent.py +0 -0
  44. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/movement_agent.py +0 -0
  45. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/navigation_agent.py +0 -0
  46. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/perception_agent.py +0 -0
  47. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/planner_agent.py +0 -0
  48. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/power_agent.py +0 -0
  49. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/task_manager.py +0 -0
  50. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/agents/thermal_agent.py +0 -0
  51. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/capabilities/__init__.py +0 -0
  52. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/capabilities/capability_loader.py +0 -0
  53. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/__init__.py +0 -0
  54. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/analytics.py +0 -0
  55. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/autonomous.py +0 -0
  56. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/calibrate.py +0 -0
  57. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/doctor.py +0 -0
  58. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/fix.py +0 -0
  59. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/kits.py +0 -0
  60. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/kits_install_recipes/freenove_4wd.yaml +0 -0
  61. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/kits_install_recipes/gopigo3.yaml +0 -0
  62. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/kits_install_recipes/sunfounder_picarx.yaml +0 -0
  63. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/kits_install_recipes/sunfounder_pidog.yaml +0 -0
  64. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/cli/kits_install_recipes/yahboom_robot_hat.yaml +0 -0
  65. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/__init__.py +0 -0
  66. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/analytics.py +0 -0
  67. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/auto_installer.py +0 -0
  68. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/auto_updater.py +0 -0
  69. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/autonomous.py +0 -0
  70. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/banner.py +0 -0
  71. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/calibration.py +0 -0
  72. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/decomposer.py +0 -0
  73. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/executor.py +0 -0
  74. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/feedback.py +0 -0
  75. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/genome.py +0 -0
  76. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/goal_parser.py +0 -0
  77. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/goals.py +0 -0
  78. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/llm_planner.py +0 -0
  79. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/mapper.py +0 -0
  80. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/mavlink_integration.py +0 -0
  81. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/memory.py +0 -0
  82. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/message_bus.py +0 -0
  83. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/metrics.py +0 -0
  84. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/navigation_engine.py +0 -0
  85. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/overrides.py +0 -0
  86. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/patches.py +0 -0
  87. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/planner.py +0 -0
  88. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/security.py +0 -0
  89. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/task_scheduler.py +0 -0
  90. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/telemetry_config.py +0 -0
  91. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/tool_builder.py +0 -0
  92. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/tool_discovery.py +0 -0
  93. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/tool_registry.py +0 -0
  94. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/ui.py +0 -0
  95. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/core/visualizer.py +0 -0
  96. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/faults/__init__.py +0 -0
  97. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/faults/fault_detector.py +0 -0
  98. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/faults/fault_injector.py +0 -0
  99. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/parallel/__init__.py +0 -0
  100. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/parallel/executor.py +0 -0
  101. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/simulation/__init__.py +0 -0
  102. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/simulation/environment.py +0 -0
  103. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/simulation/real_perception.py +0 -0
  104. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/simulation/scenarios.py +0 -0
  105. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/skills/__init__.py +0 -0
  106. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/skills/drive_until_obstacle.py +0 -0
  107. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/tui/__init__.py +0 -0
  108. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether/tui/widgets/doctor.py +0 -0
  109. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether_robotics.egg-info/SOURCES.txt +0 -0
  110. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether_robotics.egg-info/dependency_links.txt +0 -0
  111. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether_robotics.egg-info/entry_points.txt +0 -0
  112. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether_robotics.egg-info/requires.txt +0 -0
  113. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/aether_robotics.egg-info/top_level.txt +0 -0
  114. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/configs/calibrated_camera_only_20260331_121709.json +0 -0
  115. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/configs/calibrated_camera_only_20260331_122446.json +0 -0
  116. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/configs/calibrated_camera_only_20260401_094544.json +0 -0
  117. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/configs/calibrated_camera_only_20260401_094554.json +0 -0
  118. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/configs/drone_v1.json +0 -0
  119. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/configs/rover_v1.json +0 -0
  120. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/context/aether_definitions.txt +0 -0
  121. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/install.sh +0 -0
  122. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/main.py +0 -0
  123. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/requirements.txt +0 -0
  124. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/setup.cfg +0 -0
  125. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_adapter_gpio_manifests.py +0 -0
  126. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_adapter_override_wiring.py +0 -0
  127. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_adapter_preflight.py +0 -0
  128. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_adapter_resolver_v397.py +0 -0
  129. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_analytics_cli.py +0 -0
  130. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_analytics_module.py +0 -0
  131. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_analytics_wiring.py +0 -0
  132. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_atexit_adapter_shutdown.py +0 -0
  133. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_atexit_reaches_stop_all.py +0 -0
  134. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_auto_adapter.py +0 -0
  135. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_auto_updater.py +0 -0
  136. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_autonomous_cli.py +0 -0
  137. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_autonomous_loop.py +0 -0
  138. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_autonomous_no_between_step_emergency_stop.py +0 -0
  139. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_boot_resolver_flow.py +0 -0
  140. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_calibrate_wizard.py +0 -0
  141. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_calibration.py +0 -0
  142. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_calibration_unit.py +0 -0
  143. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_chaos.py +0 -0
  144. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_doctor.py +0 -0
  145. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_fix.py +0 -0
  146. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_fix_patches.py +0 -0
  147. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_fix_v3_10_6.py +0 -0
  148. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_fix_v3_10_8.py +0 -0
  149. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_fix_visibility.py +0 -0
  150. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_kits.py +0 -0
  151. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_cli_routing.py +0 -0
  152. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_consent_flow.py +0 -0
  153. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_correction_agent.py +0 -0
  154. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_decomposer.py +0 -0
  155. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_decomposer_avoids_legacy.py +0 -0
  156. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_decomposer_routing.py +0 -0
  157. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_display_bug_fix.py +0 -0
  158. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_doctor_gpio_allocation.py +0 -0
  159. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_doctor_multi_adapter.py +0 -0
  160. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_drive_until_obstacle.py +0 -0
  161. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_entrypoint.py +0 -0
  162. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_fix_scope_validation.py +0 -0
  163. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_fix_sees_named_servos.py +0 -0
  164. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_genome.py +0 -0
  165. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_goal_persistence.py +0 -0
  166. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_gopigo3_adapter.py +0 -0
  167. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_gpio_conflict_detection.py +0 -0
  168. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_high_level_task_examples.py +0 -0
  169. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_map_room_high_level_task.py +0 -0
  170. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_mapper.py +0 -0
  171. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_mavlink_integration.py +0 -0
  172. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_multi_adapter_boot_flow.py +0 -0
  173. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_multi_adapter_registration.py +0 -0
  174. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_multi_discovery.py +0 -0
  175. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_named_servo_registration.py +0 -0
  176. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_navigation_engine.py +0 -0
  177. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_overrides.py +0 -0
  178. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_overrides_multi_adapter.py +0 -0
  179. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_overrides_schema_v1_2.py +0 -0
  180. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_parallel_executor.py +0 -0
  181. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_pca9685_emergency_stop.py +0 -0
  182. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_pca9685_emergency_stop_hardware.py +0 -0
  183. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_pca9685_hard_off.py +0 -0
  184. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_pca9685_pwm_primitives.py +0 -0
  185. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_pca9685_wake_sequence.py +0 -0
  186. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_persistent_memory.py +0 -0
  187. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_planner_routing.py +0 -0
  188. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_planner_stop_routing.py +0 -0
  189. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_posthog_integration.py +0 -0
  190. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_primary_adapter_designation.py +0 -0
  191. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_robot_json_v1_1.py +0 -0
  192. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_security.py +0 -0
  193. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_set_servo_angle_duration.py +0 -0
  194. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_stop_full_off_behavior.py +0 -0
  195. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_stop_servo_sleep_fallback.py +0 -0
  196. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_sunfounder_picarx_adapter.py +0 -0
  197. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_sunfounder_pidog_adapter.py +0 -0
  198. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_synthesizer_fidelity_check.py +0 -0
  199. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_task_scheduler.py +0 -0
  200. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tier1_motion_cleanup.py +0 -0
  201. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tool_builder.py +0 -0
  202. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tool_discovery.py +0 -0
  203. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tool_discovery_gpio_cleanup.py +0 -0
  204. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tool_namespacing.py +0 -0
  205. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tui_app.py +0 -0
  206. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_tui_backward_compat.py +0 -0
  207. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_ui_autonomous.py +0 -0
  208. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_ui_dashboard.py +0 -0
  209. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_ui_module.py +0 -0
  210. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_ui_wizard.py +0 -0
  211. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_validator_rejects_legacy.py +0 -0
  212. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_yahboom_robot_hat_adapter.py +0 -0
  213. {aether_robotics-4.2.0a2 → aether_robotics-4.2.0a3}/tests/test_yolo_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-robotics
3
- Version: 4.2.0a2
3
+ Version: 4.2.0a3
4
4
  Summary: Autonomous multi-agent robotics system with DRL-First Hybrid FDIR
5
5
  Author: Chahel Paatur
6
6
  License: MIT
@@ -0,0 +1 @@
1
+ 4.2.0a3
@@ -1,4 +1,4 @@
1
- __version__ = "4.2.0a2"
1
+ __version__ = "4.2.0a3"
2
2
  __author__ = "Chahel Paatur"
3
3
 
4
4
  # v3.9.6: one-time scrub of any secrets that leaked into experience memory in
@@ -1719,7 +1719,8 @@ def _summarize_output(output) -> str:
1719
1719
 
1720
1720
  def _execute_objective(objective: str, registry, llm_planner, corrector,
1721
1721
  parser_obj, manifest, recent_memory,
1722
- available, fix_mode: bool = False) -> Dict:
1722
+ available, fix_mode: bool = False,
1723
+ on_step=None) -> Dict:
1723
1724
  """Execute a single objective and return structured result.
1724
1725
 
1725
1726
  Used by agent mode (interactive), server mode (HTTP), and `aether --fix`
@@ -1755,6 +1756,20 @@ def _execute_objective(objective: str, registry, llm_planner, corrector,
1755
1756
  if fix_mode:
1756
1757
  print(f" [agent] planning: {objective}")
1757
1758
 
1759
+ # v4.2.0a3: optional live-progress hook (TUI). Phases: "plan" (full plan
1760
+ # list, once), "start" (before each step), "done" (after each step).
1761
+ # Presentation-only — a hook failure must never affect execution.
1762
+ def _report(phase: str, data: Dict) -> None:
1763
+ if on_step is None:
1764
+ return
1765
+ try:
1766
+ on_step(phase, data)
1767
+ except Exception:
1768
+ pass
1769
+
1770
+ _report("plan", {"plan": plan, "source": plan_source,
1771
+ "objective": objective})
1772
+
1758
1773
  # Execute plan steps
1759
1774
  step_faults = 0
1760
1775
  prev_output = None
@@ -1767,6 +1782,8 @@ def _execute_objective(objective: str, registry, llm_planner, corrector,
1767
1782
  for i, action in enumerate(plan, 1):
1768
1783
  tool_name = action["tool"]
1769
1784
  params = dict(action.get("params", {}))
1785
+ _report("start", {"index": i, "total": len(plan),
1786
+ "tool": tool_name, "params": dict(params)})
1770
1787
 
1771
1788
  # Pipe previous output
1772
1789
  pipe_key = action.get("pipe_input")
@@ -1854,6 +1871,7 @@ def _execute_objective(objective: str, registry, llm_planner, corrector,
1854
1871
 
1855
1872
  action_log.append(entry)
1856
1873
  step_outputs.append(str(prev_output) if prev_output else None)
1874
+ _report("done", {"index": i, "total": len(plan), "entry": dict(entry)})
1857
1875
 
1858
1876
  if fix_mode:
1859
1877
  from aether.core import ui
@@ -1976,6 +1994,8 @@ def bootstrap_agent_state_for_fix() -> dict:
1976
1994
  "parser": parser,
1977
1995
  "manifest": manifest,
1978
1996
  "available": registry.available_tools(),
1997
+ # v4.2.0a3: adapter identity for the TUI status bar / boot detail.
1998
+ "adapter_id": _adapter_id,
1979
1999
  }
1980
2000
 
1981
2001
 
@@ -60,8 +60,8 @@ AETHER_THEME = Theme(
60
60
  )
61
61
 
62
62
  from aether import __version__
63
- from aether.tui.widgets import (AgentWidget, DoctorWidget, PlaceholderWidget,
64
- WelcomeWidget)
63
+ from aether.tui.widgets import (AgentWidget, DoctorWidget, MascotWidget,
64
+ PlaceholderWidget, WelcomeWidget)
65
65
 
66
66
  # (mode id, sidebar label, phase-1 availability)
67
67
  MODES = [
@@ -140,9 +140,11 @@ class AetherApp(App):
140
140
  #header-bar { dock: top; height: 1; background: ansi_default;
141
141
  padding: 0 1; }
142
142
  #body { height: 1fr; background: ansi_default; }
143
- #sidebar { width: 14; background: ansi_default; border: none;
144
- padding: 0 1; }
143
+ #sidebar-col { width: 17; background: ansi_default; }
144
+ #sidebar { width: 100%; height: 1fr; background: ansi_default;
145
+ border: none; padding: 0 1; }
145
146
  #sidebar ListItem { background: ansi_default; padding: 0 1; }
147
+ #mascot { dock: bottom; }
146
148
  #main-switcher { width: 1fr; border: none; background: ansi_default;
147
149
  padding: 0 1; }
148
150
  #status-bar { height: 1; background: ansi_default;
@@ -170,10 +172,12 @@ class AetherApp(App):
170
172
  def compose(self) -> ComposeResult:
171
173
  yield Static(id="header-bar")
172
174
  with Horizontal(id="body"):
173
- yield ListView(
174
- *[ListItem(Label(label), id=f"nav-{mode_id}")
175
- for mode_id, label, _ in MODES],
176
- id="sidebar")
175
+ with Vertical(id="sidebar-col"):
176
+ yield ListView(
177
+ *[ListItem(Label(label), id=f"nav-{mode_id}")
178
+ for mode_id, label, _ in MODES],
179
+ id="sidebar")
180
+ yield MascotWidget(id="mascot")
177
181
  with ContentSwitcher(initial="welcome", id="main-switcher"):
178
182
  yield WelcomeWidget(id="welcome")
179
183
  yield AgentWidget(id="agent")
@@ -266,8 +270,14 @@ class AetherApp(App):
266
270
  return
267
271
  except Exception:
268
272
  pass
273
+ # Single-adapter path (load_tier1 doesn't fill the active list).
274
+ adapter_id = state.get("adapter_id")
269
275
  n = len(state.get("available", []))
270
- self.adapters_line = f"registry ready ({n} tools)"
276
+ if adapter_id:
277
+ self.adapters_line = f"{adapter_id} (primary) · {n} tools"
278
+ self.adapter_count = 1
279
+ else:
280
+ self.adapters_line = f"registry ready ({n} tools)"
271
281
 
272
282
  # ── mode switching ─────────────────────────────────────────────────
273
283
  def activate_mode(self, mode_id: str) -> None:
@@ -5,7 +5,8 @@ render a PlaceholderWidget until phase 2/3.
5
5
  """
6
6
  from aether.tui.widgets.agent import AgentWidget
7
7
  from aether.tui.widgets.doctor import DoctorWidget
8
- from aether.tui.widgets.common import PlaceholderWidget, WelcomeWidget
8
+ from aether.tui.widgets.common import (MascotWidget, PlaceholderWidget,
9
+ WelcomeWidget)
9
10
 
10
- __all__ = ["AgentWidget", "DoctorWidget", "PlaceholderWidget",
11
- "WelcomeWidget"]
11
+ __all__ = ["AgentWidget", "DoctorWidget", "MascotWidget",
12
+ "PlaceholderWidget", "WelcomeWidget"]
@@ -0,0 +1,297 @@
1
+ """Agent mode widget: objective input + live plan progress (v4.2.0a3).
2
+
3
+ Dispatches through the exact same pipeline as the classic REPL:
4
+ bootstrap_agent_state_for_fix() builds the registry/planner once (in a
5
+ thread worker, since it touches hardware), then each objective runs through
6
+ aether.app._execute_objective — the function agent/server/--fix already
7
+ share. The optional on_step hook it now exposes drives a Claude Code-style
8
+ live step list: ○ pending, spinner while running, ● green/red with the
9
+ step's wall-clock seconds when finished. Only presentation; execution is
10
+ untouched (DO NOT CHANGE contract).
11
+ """
12
+ from typing import Callable, List, Optional
13
+
14
+ from aether.tui import AETHER_ORANGE
15
+
16
+ from textual import work
17
+ from textual.app import ComposeResult
18
+ from textual.containers import Vertical
19
+ from textual.widgets import Input, RichLog, Static
20
+
21
+ _SLASH_PREFIX = "/"
22
+ _SPINNER = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
23
+
24
+
25
+ def _params_str(params: dict) -> str:
26
+ return ", ".join(f"{k}={v!r}" for k, v in (params or {}).items())
27
+
28
+
29
+ class AgentWidget(Vertical):
30
+ """Objective REPL rendered as a TUI pane."""
31
+
32
+ DEFAULT_CSS = """
33
+ AgentWidget { background: ansi_default; }
34
+ AgentWidget #agent-log { height: 1fr; border: none;
35
+ background: ansi_default; padding: 0 1; }
36
+ AgentWidget #agent-live { height: auto; background: ansi_default;
37
+ padding: 0 2; display: none; }
38
+ AgentWidget #agent-input { border: round $primary 50%;
39
+ background: ansi_default; padding: 0 1; }
40
+ AgentWidget #agent-input:focus { border: round $primary; }
41
+ """
42
+
43
+ def __init__(self, bootstrap_fn: Optional[Callable] = None,
44
+ execute_fn: Optional[Callable] = None, **kwargs) -> None:
45
+ """bootstrap_fn/execute_fn are injectable for tests; the defaults are
46
+ the production agent pipeline. An injected execute_fn may accept
47
+ (objective, state) or (objective, state, on_step)."""
48
+ super().__init__(**kwargs)
49
+ self._bootstrap_fn = bootstrap_fn
50
+ self._execute_fn = execute_fn
51
+ self._agent_state: Optional[dict] = None
52
+ # Live-run state (UI thread only)
53
+ self._steps: List[dict] = []
54
+ self._plan_source = ""
55
+ self._spin = 0
56
+
57
+ # -- layout --
58
+ def compose(self) -> ComposeResult:
59
+ yield RichLog(id="agent-log", markup=True, wrap=True)
60
+ yield Static(id="agent-live")
61
+ yield Input(placeholder="objective> (or / for commands)",
62
+ id="agent-input")
63
+
64
+ def on_mount(self) -> None:
65
+ log = self.query_one("#agent-log", RichLog)
66
+ log.write("[bold]AETHER Agent[/bold]")
67
+ log.write("[dim]Type an objective in plain English. "
68
+ "First objective boots the hardware registry.[/dim]")
69
+ self.set_interval(0.12, self._animate)
70
+
71
+ def focus_input(self) -> None:
72
+ self.query_one("#agent-input", Input).focus()
73
+
74
+ # -- input handling --
75
+ def on_input_submitted(self, event: Input.Submitted) -> None:
76
+ text = event.value.strip()
77
+ event.input.value = ""
78
+ if not text:
79
+ return
80
+ if text.startswith(_SLASH_PREFIX):
81
+ self.app.run_slash_command(text)
82
+ return
83
+ self._log(f"\n[bold {AETHER_ORANGE}]objective>"
84
+ f"[/bold {AETHER_ORANGE}] {text}")
85
+ self.run_objective(text)
86
+
87
+ # -- objective pipeline (thread worker: hardware + LLM latency) --
88
+ @work(thread=True, exclusive=True, group="agent-objective")
89
+ def run_objective(self, objective: str) -> None:
90
+ app = self.app
91
+ try:
92
+ state = self._ensure_agent_state()
93
+ app.call_from_thread(app.set_busy,
94
+ "planning objective with Claude...")
95
+ app.call_from_thread(self._live_planning)
96
+
97
+ def on_step(phase: str, data: dict) -> None:
98
+ app.call_from_thread(self._on_step_ui, phase, data)
99
+
100
+ execute = self._execute_fn or self._default_execute
101
+ try:
102
+ result = execute(objective, state, on_step)
103
+ except TypeError:
104
+ result = execute(objective, state) # legacy 2-arg fake
105
+ app.call_from_thread(self._render_result, result)
106
+ app.call_from_thread(app.count_action)
107
+ except Exception as e: # noqa: BLE001 — surface, never crash the TUI
108
+ app.call_from_thread(
109
+ self._log, f" [red]\\[FAIL][/red] {type(e).__name__}: {e}")
110
+ app.call_from_thread(self._live_clear)
111
+ finally:
112
+ app.call_from_thread(app.set_busy, "")
113
+
114
+ def _ensure_agent_state(self) -> dict:
115
+ if self._agent_state is None:
116
+ app = self.app
117
+ app.call_from_thread(app.set_busy, "booting hardware registry...")
118
+ app.call_from_thread(
119
+ self._log, "[dim]Discovering hardware + adapters...[/dim]")
120
+ bootstrap = self._bootstrap_fn or self._default_bootstrap
121
+ self._agent_state = bootstrap()
122
+ app.call_from_thread(app.on_agent_state_ready, self._agent_state)
123
+ for line in _adapter_detail_lines(self._agent_state):
124
+ app.call_from_thread(self._log, line)
125
+ return self._agent_state
126
+
127
+ @staticmethod
128
+ def _default_bootstrap() -> dict:
129
+ from aether.app import bootstrap_agent_state_for_fix
130
+ return bootstrap_agent_state_for_fix()
131
+
132
+ @staticmethod
133
+ def _default_execute(objective: str, state: dict, on_step=None) -> dict:
134
+ from aether.app import _execute_objective
135
+ return _execute_objective(
136
+ objective, state["registry"], state["planner"],
137
+ state["corrector"], state["parser"], state["manifest"],
138
+ recent_memory=[], available=state["available"], on_step=on_step)
139
+
140
+ # -- live plan panel (all methods below run on the UI thread) --
141
+ def _live(self) -> Static:
142
+ return self.query_one("#agent-live", Static)
143
+
144
+ def _live_planning(self) -> None:
145
+ self._steps = []
146
+ self._plan_source = ""
147
+ live = self._live()
148
+ live.styles.display = "block"
149
+ self._render_live()
150
+
151
+ def _live_clear(self) -> None:
152
+ self._steps = []
153
+ live = self._live()
154
+ live.update("")
155
+ live.styles.display = "none"
156
+
157
+ def _on_step_ui(self, phase: str, data: dict) -> None:
158
+ if phase == "plan":
159
+ self._plan_source = data.get("source", "?")
160
+ self._steps = [
161
+ {"tool": a.get("tool", "?"),
162
+ "params": dict(a.get("params", {})),
163
+ "status": "pending", "secs": None, "error": ""}
164
+ for a in data.get("plan", [])]
165
+ elif phase == "start" and self._steps:
166
+ i = data.get("index", 1) - 1
167
+ if 0 <= i < len(self._steps):
168
+ self._steps[i]["status"] = "running"
169
+ elif phase == "done" and self._steps:
170
+ i = data.get("index", 1) - 1
171
+ entry = data.get("entry", {})
172
+ if 0 <= i < len(self._steps):
173
+ step = self._steps[i]
174
+ step["status"] = "done" if entry.get("success") else "failed"
175
+ step["secs"] = entry.get("duration_ms", 0) / 1000.0
176
+ step["error"] = entry.get("error", "")
177
+ self._render_live()
178
+
179
+ def _animate(self) -> None:
180
+ if any(s["status"] == "running" for s in self._steps) or (
181
+ self._live().styles.display == "block" and not self._steps):
182
+ self._spin = (self._spin + 1) % len(_SPINNER)
183
+ self._render_live()
184
+
185
+ def _step_line(self, step: dict) -> str:
186
+ call = f"{step['tool']}({_params_str(step['params'])})"
187
+ status = step["status"]
188
+ if status == "pending":
189
+ return f" [dim]○ {call}[/dim]"
190
+ if status == "running":
191
+ frame = _SPINNER[self._spin]
192
+ return (f" [{AETHER_ORANGE}]{frame}[/{AETHER_ORANGE}] {call} "
193
+ f"[dim]running...[/dim]")
194
+ secs = f" [dim]({step['secs']:.1f}s)[/dim]" if step["secs"] is not None \
195
+ else ""
196
+ if status == "done":
197
+ return f" [green]●[/green] {call}{secs}"
198
+ return (f" [red]●[/red] {call}{secs} "
199
+ f"[red]{step.get('error', '')}[/red]")
200
+
201
+ def _render_live(self) -> None:
202
+ if not self._steps:
203
+ frame = _SPINNER[self._spin]
204
+ self._live().update(
205
+ f"[{AETHER_ORANGE}]{frame}[/{AETHER_ORANGE}] "
206
+ f"[dim]planning objective with Claude...[/dim]")
207
+ return
208
+ done = sum(1 for s in self._steps if s["status"] in ("done", "failed"))
209
+ header = (f"[{AETHER_ORANGE}]\\[PLAN][/{AETHER_ORANGE}] "
210
+ f"{self._plan_source} · {len(self._steps)} step(s) · "
211
+ f"[dim]{done}/{len(self._steps)} complete[/dim]")
212
+ self._live().update(
213
+ "\n".join([header] + [self._step_line(s) for s in self._steps]))
214
+
215
+ # -- final render into scrollback --
216
+ def _log(self, line: str) -> None:
217
+ self.query_one("#agent-log", RichLog).write(line)
218
+
219
+ def _render_result(self, result: dict) -> None:
220
+ if self._steps:
221
+ # Rich path: persist the live step list (with dots + seconds).
222
+ done = sum(1 for s in self._steps if s["status"] == "done")
223
+ self._log(f" [{AETHER_ORANGE}]\\[PLAN][/{AETHER_ORANGE}] "
224
+ f"{self._plan_source} · {len(self._steps)} step(s)")
225
+ total_s = 0.0
226
+ for step in self._steps:
227
+ self._log(self._step_line(step))
228
+ total_s += step["secs"] or 0.0
229
+ summary = f"{done}/{len(self._steps)} steps · {total_s:.1f}s total"
230
+ else:
231
+ # Legacy path (no on_step data): render from the result dict.
232
+ actions = result.get("actions", [])
233
+ self._log(f" [{AETHER_ORANGE}]\\[PLAN][/{AETHER_ORANGE}] "
234
+ f"{result.get('plan_source', '?')} plan · "
235
+ f"{len(actions)} step(s)")
236
+ for entry in actions:
237
+ self._log(f" [{AETHER_ORANGE}]\\[EXEC][/{AETHER_ORANGE}] "
238
+ f"{entry.get('tool', '?')}")
239
+ if entry.get("success"):
240
+ self._log(f" [green]\\[OK][/green] success "
241
+ f"({entry.get('duration_ms', 0):.0f}ms)")
242
+ else:
243
+ self._log(f" [red]\\[FAIL][/red] {entry.get('error', '')}")
244
+ summary = f"{len(actions)} step(s)"
245
+ status = result.get("status", "?")
246
+ color = "green" if status in ("ok", "success", "SUCCESS",
247
+ "completed") else "yellow"
248
+ self._log(f" [{color}]status: {status}[/{color}] [dim]· {summary}[/dim]")
249
+ self._live_clear()
250
+
251
+ def clear_log(self) -> None:
252
+ self.query_one("#agent-log", RichLog).clear()
253
+
254
+
255
+ def _adapter_detail_lines(state: dict) -> List[str]:
256
+ """Boot-time adapter detail for the log: name, role, tool count,
257
+ address, GPIO pins. Best effort — never raises."""
258
+ lines: List[str] = []
259
+ try:
260
+ from aether.adapters import (get_active_adapters, get_adapter_gpio_pins,
261
+ get_last_detected, get_primary_adapter,
262
+ get_tool_origin)
263
+ active = get_active_adapters()
264
+ addr_of = {d["adapter"]: d.get("address") for d in get_last_detected()}
265
+ if active:
266
+ primary = get_primary_adapter()
267
+ counts: dict = {}
268
+ for _tool, adp in get_tool_origin().items():
269
+ counts[adp] = counts.get(adp, 0) + 1
270
+ for name in active:
271
+ lines.append(_adapter_line(
272
+ name, name == primary, counts.get(name, 0),
273
+ addr_of.get(name), get_adapter_gpio_pins(name)))
274
+ return lines
275
+ adapter_id = state.get("adapter_id")
276
+ if adapter_id:
277
+ tools = len(state.get("manifest", {}).get("adapter_tools", []))
278
+ lines.append(_adapter_line(
279
+ adapter_id, True, tools, addr_of.get(adapter_id),
280
+ get_adapter_gpio_pins(adapter_id)))
281
+ return lines
282
+ except Exception:
283
+ pass
284
+ if not lines:
285
+ lines.append(f"[dim]● registry ready "
286
+ f"({len(state.get('available', []))} tools)[/dim]")
287
+ return lines
288
+
289
+
290
+ def _adapter_line(name: str, primary: bool, tools: int, address,
291
+ gpio_pins) -> str:
292
+ role = ("[green]primary[/green]" if primary
293
+ else f"[{AETHER_ORANGE}]namespaced[/{AETHER_ORANGE}]")
294
+ gpio = ", ".join(str(p) for p in (gpio_pins or [])) or "none (I2C only)"
295
+ addr = address or "vendor lib"
296
+ return (f"[green]●[/green] [bold]{name}[/bold] — {role} · "
297
+ f"{tools} tools · addr {addr} · [dim]GPIO {gpio}[/dim]")
@@ -1,9 +1,32 @@
1
- """Shared TUI widgets: first-run welcome screen and phase-2 placeholders."""
1
+ """Shared TUI widgets: mascot mark, first-run welcome, phase-2 placeholders."""
2
2
  from textual.app import ComposeResult
3
3
  from textual.containers import Horizontal, Vertical
4
4
  from textual.widgets import Button, Static
5
5
 
6
6
  from aether import __version__
7
+ from aether.tui import AETHER_ORANGE
8
+
9
+ # Compact rendition of the AETHER banner mascot (the robot face) for the
10
+ # bottom-left corner of the TUI.
11
+ _MASCOT_ART = (
12
+ f"[{AETHER_ORANGE}]▄████▄ ▄████▄[/{AETHER_ORANGE}]\n"
13
+ f"[{AETHER_ORANGE}]█████████████[/{AETHER_ORANGE}]\n"
14
+ f"[dim]█▀▀[/dim][{AETHER_ORANGE}]███████[/{AETHER_ORANGE}][dim]▀▀█[/dim]\n"
15
+ f"[{AETHER_ORANGE}]▀████▀ ▀████▀[/{AETHER_ORANGE}]\n"
16
+ f"[dim] ━━ AETHER ━━[/dim]"
17
+ )
18
+
19
+
20
+ class MascotWidget(Static):
21
+ """The AETHER robot face, parked bottom-left under the sidebar."""
22
+
23
+ DEFAULT_CSS = """
24
+ MascotWidget { height: 5; background: ansi_default; padding: 0 1;
25
+ color: $primary; }
26
+ """
27
+
28
+ def __init__(self, **kwargs) -> None:
29
+ super().__init__(_MASCOT_ART, **kwargs)
7
30
 
8
31
 
9
32
  class WelcomeWidget(Vertical):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-robotics
3
- Version: 4.2.0a2
3
+ Version: 4.2.0a3
4
4
  Summary: Autonomous multi-agent robotics system with DRL-First Hybrid FDIR
5
5
  Author: Chahel Paatur
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aether-robotics"
7
- version = "4.2.0a2"
7
+ version = "4.2.0a3"
8
8
  description = "Autonomous multi-agent robotics system with DRL-First Hybrid FDIR"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}