thestage 0.6.6__tar.gz → 0.6.7__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 (166) hide show
  1. {thestage-0.6.6 → thestage-0.6.7}/PKG-INFO +2 -1
  2. {thestage-0.6.6 → thestage-0.6.7}/pyproject.toml +2 -1
  3. {thestage-0.6.6 → thestage-0.6.7}/thestage/__init__.py +1 -1
  4. {thestage-0.6.6 → thestage-0.6.7}/thestage/controllers/container_controller.py +13 -66
  5. {thestage-0.6.6 → thestage-0.6.7}/thestage/controllers/instance_controller.py +4 -4
  6. {thestage-0.6.6 → thestage-0.6.7}/thestage/controllers/project_controller.py +8 -10
  7. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/project_inference_simulator_model.py +1 -1
  8. {thestage-0.6.6 → thestage-0.6.7}/thestage/helpers/error_handler.py +2 -2
  9. {thestage-0.6.6 → thestage-0.6.7}/thestage/main.py +1 -1
  10. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/git/git_client.py +1 -1
  11. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/api_client.py +3 -5
  12. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/base_controller/connect_resolve_response.py +1 -0
  13. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_instance_request.py +0 -2
  14. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_instance_response.py +2 -0
  15. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_request.py +1 -1
  16. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_response.py +3 -0
  17. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/connect/connect_service.py +6 -6
  18. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/container/container_service.py +97 -22
  19. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/logging/logging_service.py +5 -8
  20. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/project/dto/project_config.py +1 -2
  21. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/project/project_service.py +16 -12
  22. {thestage-0.6.6 → thestage-0.6.7}/LICENSE.txt +0 -0
  23. {thestage-0.6.6 → thestage-0.6.7}/README.md +0 -0
  24. {thestage-0.6.6 → thestage-0.6.7}/thestage/.env +0 -0
  25. {thestage-0.6.6 → thestage-0.6.7}/thestage/__main__.py +0 -0
  26. {thestage-0.6.6 → thestage-0.6.7}/thestage/cli_command.py +0 -0
  27. {thestage-0.6.6 → thestage-0.6.7}/thestage/cli_command_helper.py +0 -0
  28. {thestage-0.6.6 → thestage-0.6.7}/thestage/color_scheme/color_scheme.py +0 -0
  29. {thestage-0.6.6 → thestage-0.6.7}/thestage/config/__init__.py +0 -0
  30. {thestage-0.6.6 → thestage-0.6.7}/thestage/config/config_storage.py +0 -0
  31. {thestage-0.6.6 → thestage-0.6.7}/thestage/config/env_base.py +0 -0
  32. {thestage-0.6.6 → thestage-0.6.7}/thestage/controllers/__init__.py +0 -0
  33. {thestage-0.6.6 → thestage-0.6.7}/thestage/controllers/base_controller.py +0 -0
  34. {thestage-0.6.6 → thestage-0.6.7}/thestage/controllers/config_controller.py +0 -0
  35. {thestage-0.6.6 → thestage-0.6.7}/thestage/controllers/utils_controller.py +0 -0
  36. {thestage-0.6.6 → thestage-0.6.7}/thestage/debug_main.dist.py +0 -0
  37. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/__init__.py +0 -0
  38. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/container.py +0 -0
  39. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/enums/__init__.py +0 -0
  40. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/enums/order_direction_type.py +0 -0
  41. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/enums/shell_type.py +0 -0
  42. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/enums/tail_output_type.py +0 -0
  43. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/enums/yes_no_response.py +0 -0
  44. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/file_item.py +0 -0
  45. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/project_inference_simulator.py +0 -0
  46. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/project_task.py +0 -0
  47. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/rented_instance.py +0 -0
  48. {thestage-0.6.6 → thestage-0.6.7}/thestage/entities/self_hosted_instance.py +0 -0
  49. {thestage-0.6.6 → thestage-0.6.7}/thestage/exceptions/__init__.py +0 -0
  50. {thestage-0.6.6 → thestage-0.6.7}/thestage/exceptions/auth_exception.py +0 -0
  51. {thestage-0.6.6 → thestage-0.6.7}/thestage/exceptions/base_exception.py +0 -0
  52. {thestage-0.6.6 → thestage-0.6.7}/thestage/exceptions/business_logic_exception.py +0 -0
  53. {thestage-0.6.6 → thestage-0.6.7}/thestage/exceptions/config_exception.py +0 -0
  54. {thestage-0.6.6 → thestage-0.6.7}/thestage/exceptions/file_system_exception.py +0 -0
  55. {thestage-0.6.6 → thestage-0.6.7}/thestage/exceptions/git_access_exception.py +0 -0
  56. {thestage-0.6.6 → thestage-0.6.7}/thestage/exceptions/remote_server_exception.py +0 -0
  57. {thestage-0.6.6 → thestage-0.6.7}/thestage/git/ProgressPrinter.py +0 -0
  58. {thestage-0.6.6 → thestage-0.6.7}/thestage/helpers/__init__.py +0 -0
  59. {thestage-0.6.6 → thestage-0.6.7}/thestage/helpers/exception_hook.py +0 -0
  60. {thestage-0.6.6 → thestage-0.6.7}/thestage/helpers/logger/__init__.py +0 -0
  61. {thestage-0.6.6 → thestage-0.6.7}/thestage/helpers/logger/app_logger.py +0 -0
  62. {thestage-0.6.6 → thestage-0.6.7}/thestage/helpers/ssh_util.py +0 -0
  63. {thestage-0.6.6 → thestage-0.6.7}/thestage/i18n/en_GB/messages.po +0 -0
  64. {thestage-0.6.6 → thestage-0.6.7}/thestage/i18n/translation.py +0 -0
  65. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/.env +0 -0
  66. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/__init__.py +0 -0
  67. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/abstract_mapper.py +0 -0
  68. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/abstract_service.py +0 -0
  69. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/app_config_service.py +0 -0
  70. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/__init__.py +0 -0
  71. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/git/__init__.py +0 -0
  72. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/__init__.py +0 -0
  73. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/core/api_client_core.py +0 -0
  74. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/core/http_client_exception.py +0 -0
  75. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/__init__.py +0 -0
  76. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/base_response.py +0 -0
  77. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/container_param_request.py +0 -0
  78. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/container_response.py +0 -0
  79. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/docker_container_controller/docker_container_list_request.py +0 -0
  80. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/docker_container_controller/docker_container_list_response.py +0 -0
  81. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/docker_container_mapping.py +0 -0
  82. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/entity_filter_request.py +0 -0
  83. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/enums/__init__.py +0 -0
  84. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/enums/container_pending_action.py +0 -0
  85. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/enums/container_status.py +0 -0
  86. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/enums/cpu_type.py +0 -0
  87. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/enums/gpu_name.py +0 -0
  88. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/enums/inference_model_status.py +0 -0
  89. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/enums/inference_simulator_status.py +0 -0
  90. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/enums/instance_rented_status.py +0 -0
  91. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/enums/provider_name.py +0 -0
  92. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/enums/selfhosted_status.py +0 -0
  93. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/enums/task_execution_status.py +0 -0
  94. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/enums/task_status.py +0 -0
  95. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/frontend_status.py +0 -0
  96. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_sagemaker_request.py +0 -0
  97. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_sagemaker_response.py +0 -0
  98. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_response.py +0 -0
  99. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_list_request.py +0 -0
  100. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_list_response.py +0 -0
  101. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_model_list_for_project_request.py +0 -0
  102. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_model_list_for_project_response.py +0 -0
  103. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/inference_simulator_model_response.py +0 -0
  104. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/inference_simulator_response.py +0 -0
  105. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/installed_service.py +0 -0
  106. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/instance_detected_gpus.py +0 -0
  107. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/instance_rented_response.py +0 -0
  108. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/logging_controller/docker_container_log_stream_request.py +0 -0
  109. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/logging_controller/log_polling_request.py +0 -0
  110. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/logging_controller/log_polling_response.py +0 -0
  111. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/logging_controller/task_log_stream_request.py +0 -0
  112. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/logging_controller/user_logs_query_request.py +0 -0
  113. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/logging_controller/user_logs_query_response.py +0 -0
  114. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/paginated_entity_list.py +0 -0
  115. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/pagination_data.py +0 -0
  116. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_request.py +0 -0
  117. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_response.py +0 -0
  118. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_request.py +0 -0
  119. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_response.py +0 -0
  120. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_request.py +0 -0
  121. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_request.py +0 -0
  122. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_response.py +0 -0
  123. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/project_response.py +0 -0
  124. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/selfhosted_instance_response.py +0 -0
  125. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/sftp_path_helper.py +0 -0
  126. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_key_to_user_request.py +0 -0
  127. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_key_to_user_response.py +0 -0
  128. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_public_key_to_instance_request.py +0 -0
  129. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_public_key_to_instance_response.py +0 -0
  130. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/ssh_key_controller/is_user_has_public_ssh_key_request.py +0 -0
  131. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/ssh_key_controller/is_user_has_public_ssh_key_response.py +0 -0
  132. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/task_controller/task_list_for_project_request.py +0 -0
  133. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/task_controller/task_list_for_project_response.py +0 -0
  134. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/task_controller/task_status_localized_map_response.py +0 -0
  135. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/task_controller/task_view_response.py +0 -0
  136. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/user_controller/user_profile.py +0 -0
  137. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/clients/thestage_api/dtos/validate_token_response.py +0 -0
  138. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/config_provider/__init__.py +0 -0
  139. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/config_provider/config_provider.py +0 -0
  140. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/connect/dto/remote_server_config.py +0 -0
  141. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/container/__init__.py +0 -0
  142. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/container/mapper/__init__.py +0 -0
  143. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/container/mapper/container_mapper.py +0 -0
  144. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/core_files/config_entity.py +0 -0
  145. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/filesystem_service.py +0 -0
  146. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/instance/__init__.py +0 -0
  147. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/instance/instance_service.py +0 -0
  148. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/instance/mapper/__init__.py +0 -0
  149. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/instance/mapper/instance_mapper.py +0 -0
  150. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/instance/mapper/selfhosted_mapper.py +0 -0
  151. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/logging/byte_print_style.py +0 -0
  152. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/logging/dto/log_message.py +0 -0
  153. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/logging/dto/log_type.py +0 -0
  154. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/logging/exception/log_polling_exception.py +0 -0
  155. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/logging/logging_constants.py +0 -0
  156. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/project/__init__.py +0 -0
  157. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/project/dto/inference_simulator_dto.py +0 -0
  158. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/project/dto/inference_simulator_model_dto.py +0 -0
  159. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/project/mapper/__init__.py +0 -0
  160. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/project/mapper/project_inference_simulator_mapper.py +0 -0
  161. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/project/mapper/project_inference_simulator_model_mapper.py +0 -0
  162. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/project/mapper/project_task_mapper.py +0 -0
  163. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/remote_server_service.py +0 -0
  164. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/service_factory.py +0 -0
  165. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/task/dto/task_dto.py +0 -0
  166. {thestage-0.6.6 → thestage-0.6.7}/thestage/services/validation_service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thestage
3
- Version: 0.6.6
3
+ Version: 0.6.7
4
4
  Summary:
5
5
  License-File: LICENSE.txt
6
6
  Author: TheStage AI team
@@ -21,6 +21,7 @@ Requires-Dist: pydantic (>=2.4.2,<3.0.0)
21
21
  Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
22
22
  Requires-Dist: python-gettext-translations (>=1.1.0,<2.0.0)
23
23
  Requires-Dist: requests (>=2.31.0,<3.0.0)
24
+ Requires-Dist: rich (>=14.2.0,<15.0.0)
24
25
  Requires-Dist: tabulate (>=0.9.0,<0.10.0)
25
26
  Requires-Dist: typer[all] (>=0.15.2,<0.16.0)
26
27
  Description-Content-Type: text/markdown
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "thestage"
3
3
 
4
- version = "0.6.6"
4
+ version = "0.6.7"
5
5
 
6
6
  description = ""
7
7
  authors = ["TheStage AI team <hello@thestage.ai>"]
@@ -34,6 +34,7 @@ paramiko = "^3.5.1"
34
34
  aioconsole = "^0.8.0"
35
35
  httpx = "^0.27.2"
36
36
  boto3 = "^1.35.80"
37
+ rich = "^14.2.0"
37
38
 
38
39
  [tool.poetry.dev-dependencies]
39
40
  pytest = "^7.4.3"
@@ -1,3 +1,3 @@
1
1
  from . import *
2
2
  __app_name__ = "thestage"
3
- __version__ = "0.6.6"
3
+ __version__ = "0.6.7"
@@ -2,9 +2,12 @@
2
2
  import re
3
3
  from pathlib import Path
4
4
  from typing import Optional, List
5
+ from rich import print
5
6
 
6
7
  from thestage.cli_command import CliCommand
7
8
  from thestage.cli_command_helper import get_command_metadata, check_command_permission
9
+ from thestage.color_scheme.color_scheme import ColorScheme
10
+ from thestage.services.clients.thestage_api.api_client import TheStageApiClient
8
11
  from thestage.services.clients.thestage_api.dtos.enums.container_pending_action import DockerContainerAction
9
12
  from thestage.services.clients.thestage_api.dtos.container_response import DockerContainerDto
10
13
  from thestage.i18n.translation import __
@@ -215,7 +218,7 @@ def container_connect(
215
218
  @app.command(name="upload", no_args_is_help=True, help=__("Upload file to container"), **get_command_metadata(CliCommand.CONTAINER_UPLOAD))
216
219
  def upload_file(
217
220
  source_path: str = typer.Argument(help=__("Source file path"),),
218
- destination: Optional[str] = typer.Argument(help=__("Destination directory path in container. Format: container_name:/path/to/file"),),
221
+ destination: Optional[str] = typer.Argument(help=__("Destination directory path in container. Format: container_id_or_name:/path/to/file"),),
219
222
  username: Optional[str] = typer.Option(
220
223
  None,
221
224
  '--username',
@@ -228,50 +231,22 @@ def upload_file(
228
231
  app_logger.info(f'Running {command_name} from {get_current_directory()}')
229
232
  check_command_permission(command_name)
230
233
 
231
- container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", destination)
232
-
233
- if container_args is None:
234
- typer.echo(__('Container name and source file path are required as the second argument'))
235
- typer.echo(__('Example: container_uid:/path/to/file'))
236
- raise typer.Exit(1)
237
- container_slug = container_args.groups()[0]
238
- destination_path = container_args.groups()[1].rstrip("/")
239
-
240
- if not container_slug:
241
- typer.echo(__('Container name is required'))
242
- raise typer.Exit(1)
243
-
244
234
  service_factory = validate_config_and_get_service_factory()
245
235
  container_service: ContainerService = service_factory.get_container_service()
246
236
 
247
- container: Optional[DockerContainerDto] = container_service.get_container(
248
- container_slug=container_slug,
237
+ container_service.put_file_to_container(
238
+ source_path=source_path,
239
+ destination=destination,
240
+ username_param=username,
249
241
  )
250
242
 
251
- if container:
252
- container_service.check_if_container_running(
253
- container=container
254
- )
255
-
256
- typer.echo(f"Uploading file(s) to container '{container_slug}'")
257
-
258
- container_service.put_file_to_container(
259
- container=container,
260
- src_path=source_path,
261
- destination_path=destination_path,
262
- username_param=username,
263
- copy_only_folder_contents=source_path.endswith("/")
264
- )
265
- else:
266
- typer.echo(__("Container not found: %container_item%", {'container_item': container_slug}))
267
-
268
243
  app_logger.info(f'End send files to container')
269
244
  raise typer.Exit(0)
270
245
 
271
246
 
272
247
  @app.command(name="download", no_args_is_help=True, help=__("Download file from container"), **get_command_metadata(CliCommand.CONTAINER_DOWNLOAD))
273
248
  def download_file(
274
- source_path: str = typer.Argument(help=__("Source file path in container. Format: container_name:/path/to/file"),),
249
+ source: str = typer.Argument(help=__("Source file path in container. Format: container_name:/path/to/file"),),
275
250
  destination_path: str = typer.Argument(help=__("Destination directory path on local machine"),),
276
251
  username: Optional[str] = typer.Option(
277
252
  None,
@@ -285,43 +260,15 @@ def download_file(
285
260
  app_logger.info(f'Running {command_name} from {get_current_directory()}')
286
261
  check_command_permission(command_name)
287
262
 
288
- container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", source_path)
289
-
290
- if container_args is None:
291
- typer.echo(__('Container name and source directory path are required as the first argument'))
292
- typer.echo(__('Example: container-uid:/path/to/file'))
293
- raise typer.Exit(1)
294
- container_slug = container_args.groups()[0]
295
- source_path = container_args.groups()[1]
296
-
297
- if not container_slug:
298
- typer.echo(__('Container name is required'))
299
- raise typer.Exit(1)
300
-
301
263
  service_factory = validate_config_and_get_service_factory()
302
264
  container_service: ContainerService = service_factory.get_container_service()
303
265
 
304
- container: Optional[DockerContainerDto] = container_service.get_container(
305
- container_slug=container_slug,
266
+ container_service.get_file_from_container(
267
+ source=source,
268
+ destination_path=destination_path.rstrip("/"),
269
+ username_param=username,
306
270
  )
307
271
 
308
- if container:
309
- container_service.check_if_container_running(
310
- container=container
311
- )
312
-
313
- typer.echo(f"Downloading files from container: '{container_slug}'")
314
-
315
- container_service.get_file_from_container(
316
- container=container,
317
- src_path=source_path,
318
- destination_path=destination_path.rstrip("/"),
319
- username_param=username,
320
- copy_only_folder_contents=source_path.endswith("/"),
321
- )
322
- else:
323
- typer.echo(f"Container not found: {container_slug}")
324
-
325
272
  app_logger.info(f'End download files from container')
326
273
  raise typer.Exit(0)
327
274
 
@@ -66,14 +66,14 @@ def rented_list(
66
66
  def instance_connect(
67
67
  public_id: Optional[str] = typer.Option(
68
68
  None,
69
- '--id',
69
+ '--rented-instance-id',
70
70
  '-rid',
71
71
  help="Rented instance ID",
72
72
  is_eager=False,
73
73
  ),
74
74
  slug: Optional[str] = typer.Option(
75
75
  None,
76
- '--name',
76
+ '--rented-instance-name',
77
77
  '-rn',
78
78
  help="Rented instance name",
79
79
  is_eager=False,
@@ -156,14 +156,14 @@ def self_hosted_list(
156
156
  def self_hosted_connect(
157
157
  public_id: Optional[str] = typer.Option(
158
158
  None,
159
- '--id',
159
+ '--self-hosted-instance-id',
160
160
  '-sid',
161
161
  help="Self-hosted instance ID",
162
162
  is_eager=False,
163
163
  ),
164
164
  slug: Optional[str] = typer.Option(
165
165
  None,
166
- '--name',
166
+ '--self-hosted-instance-name',
167
167
  '-sn',
168
168
  help="Self-hosted instance name",
169
169
  is_eager=False,
@@ -63,7 +63,8 @@ def clone(
63
63
  raise typer.Exit(1)
64
64
 
65
65
  if not working_directory:
66
- working_directory = get_current_directory().joinpath(project_slug)
66
+ project_dir_name = project_public_id if project_slug is None else project_slug
67
+ working_directory = get_current_directory().joinpath(project_dir_name)
67
68
 
68
69
  service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
69
70
  project_service = service_factory.get_project_service()
@@ -842,7 +843,7 @@ def inference_simulator_logs(
842
843
  ),
843
844
  slug: Optional[str] = typer.Option(
844
845
  None,
845
- '--name',
846
+ '--inference-simulator-name',
846
847
  '-isn',
847
848
  help="Inference simulator name",
848
849
  is_eager=False,
@@ -952,17 +953,14 @@ def deploy_inference_simulator_model_to_instance(
952
953
  check_command_permission(command_name)
953
954
 
954
955
  if model_slug and not re.match(r"^[a-zA-Z0-9-]+$", model_slug):
955
- raise typer.BadParameter(__("Invalid UID format. The UID can only contain letters, numbers, and hyphens."))
956
-
957
- new_inference_simulator_slug = f"{model_slug}-{int(time.time())}"
956
+ raise typer.BadParameter("Invalid name format. Name can only contain letters, numbers, and hyphens.")
958
957
 
959
958
  service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
960
959
  project_service = service_factory.get_project_service()
961
960
 
962
- project_service.project_deploy_inference_simulator_model_to_instance(
961
+ inference_simulator_public_id = project_service.project_deploy_inference_simulator_model_to_instance(
963
962
  model_public_id=model_public_id,
964
963
  model_slug=model_slug,
965
- new_inference_simulator_slug=new_inference_simulator_slug,
966
964
  rented_instance_public_id=rented_instance_public_id,
967
965
  rented_instance_slug=rented_instance_slug,
968
966
  self_hosted_instance_public_id=self_hosted_instance_public_id,
@@ -973,7 +971,7 @@ def deploy_inference_simulator_model_to_instance(
973
971
  logging_service: LoggingService = service_factory.get_logging_service()
974
972
 
975
973
  logging_service.stream_inference_simulator_logs_with_controls(
976
- slug=new_inference_simulator_slug
974
+ public_id=inference_simulator_public_id
977
975
  )
978
976
  raise typer.Exit(0)
979
977
 
@@ -982,14 +980,14 @@ def deploy_inference_simulator_model_to_instance(
982
980
  def deploy_inference_simulator_model_to_sagemaker(
983
981
  model_public_id: Optional[str] = typer.Option(
984
982
  None,
985
- '--id',
983
+ '--model-id',
986
984
  '-mid',
987
985
  help="Inference simulator model ID",
988
986
  is_eager=False,
989
987
  ),
990
988
  model_slug: Optional[str] = typer.Option(
991
989
  None,
992
- '--name',
990
+ '--model-name',
993
991
  '-mn',
994
992
  help="Inference simulator model name",
995
993
  is_eager=False,
@@ -11,7 +11,7 @@ class ProjectInferenceSimulatorModelEntity(BaseModel):
11
11
  )
12
12
 
13
13
  public_id: Optional[str] = Field(None, alias='ID')
14
- slug: Optional[str] = Field(None, alias='UNIQUE_ID')
14
+ slug: Optional[str] = Field(None, alias='NAME')
15
15
  status: Optional[str] = Field(None, alias='STATUS')
16
16
  commit_hash: Optional[str] = Field(None, alias='COMMIT_HASH')
17
17
  environment_metadata: Optional[Dict[str, Any]] = Field(None, alias='ENVIRONMENT_METADATA')
@@ -106,8 +106,8 @@ def error_handler() -> Callable:
106
106
  raise e100
107
107
  else:
108
108
  typer.echo(__('Undefined error occurred'))
109
- typer.echo(e100.__class__.__name__)
110
- print(traceback.format_exc())
109
+ # typer.echo(e100.__class__.__name__)
110
+ # print(traceback.format_exc())
111
111
  # TODO send all exceptions to backend?
112
112
  app_logger.error(f'{traceback.format_exc()}')
113
113
  raise typer.Exit(1)
@@ -19,7 +19,7 @@ def main():
19
19
  config_provider.update_allowed_commands_and_is_token_valid(validate_token_response=token_info)
20
20
  except Exception as e:
21
21
  app_logger.error(f'{traceback.format_exc()}')
22
- print('Unexpected error occurred') # TODO inquire what we want here if backend is offline
22
+ print('Error connecting to TheStage servers') # TODO inquire what we want here if backend is offline
23
23
  print(f'Application logs path: {str(get_log_path_from_os())}')
24
24
  return
25
25
 
@@ -196,7 +196,7 @@ class GitLocalClient:
196
196
  ) -> Optional[str]:
197
197
  repo = self.__get_repo(path=path)
198
198
  if repo.head.is_detached:
199
- line_color = ColorScheme.GIT_HEADLESS
199
+ line_color = ColorScheme.GIT_HEADLESS.value
200
200
  print(f'[{line_color}]Committing in detached head state at {repo.head.commit.hexsha}[/{line_color}]')
201
201
  commit_name = name if name else f"Auto commit {str(datetime.datetime.now().date())}"
202
202
  commit = repo.git.commit('--allow-empty', '-m', commit_name, )
@@ -667,7 +667,6 @@ class TheStageApiClient(TheStageApiClientCore):
667
667
  self,
668
668
  model_public_id: str,
669
669
  model_slug: str,
670
- new_inference_simulator_slug: str,
671
670
  rented_instance_public_id: Optional[str] = None,
672
671
  rented_instance_slug: Optional[str] = None,
673
672
  self_hosted_instance_public_id: Optional[str] = None,
@@ -675,10 +674,9 @@ class TheStageApiClient(TheStageApiClientCore):
675
674
 
676
675
  ) -> Optional[DeployInferenceModelToInstanceResponse]:
677
676
  request = DeployInferenceModelToInstanceRequest(
678
- inferenceSimulatorSlug=new_inference_simulator_slug,
679
677
  modelPublicId=model_public_id,
680
678
  modelSlug=model_slug,
681
- instancePublicId=rented_instance_public_id,
679
+ instanceRentedPublicId=rented_instance_public_id,
682
680
  instanceRentedSlug=rented_instance_slug,
683
681
  selfhostedInstancePublicId=self_hosted_instance_public_id,
684
682
  selfhostedInstanceSlug=self_hosted_instance_slug
@@ -735,7 +733,7 @@ class TheStageApiClient(TheStageApiClientCore):
735
733
  return result
736
734
 
737
735
 
738
- def resolve_connect_options(
736
+ def resolve_user_input(
739
737
  self,
740
738
  entity_identifier: str
741
739
  ) -> Optional[ConnectResolveOptionsResponse]:
@@ -745,7 +743,7 @@ class TheStageApiClient(TheStageApiClientCore):
745
743
 
746
744
  response = self._request(
747
745
  method='POST',
748
- url='/user-api/v1/connect/resolve-options',
746
+ url='/user-api/v1/resolve-user-input',
749
747
  data=data,
750
748
  token=self.__config_provider.get_config().main.thestage_auth_token,
751
749
  )
@@ -11,6 +11,7 @@ class EntityMatchData(BaseModel):
11
11
  publicId: str = Field(alias='publicId')
12
12
  frontendStatus: FrontendStatusDto = Field(alias='frontendStatus')
13
13
  canConnect: bool = Field(alias='canConnect')
14
+ canDownloadUploadOnContainer: bool = Field(alias='canDownloadUploadOnContainer')
14
15
  matchedField: Optional[str] = Field(None, alias='matchedField')
15
16
 
16
17
 
@@ -16,5 +16,3 @@ class DeployInferenceModelToInstanceRequest(BaseModel):
16
16
 
17
17
  selfhostedInstancePublicId: Optional[str] = Field(None, alias='selfhostedInstancePublicId')
18
18
  selfhostedInstanceSlug: Optional[str] = Field(None, alias='selfhostedInstanceSlug')
19
-
20
- inferenceSimulatorSlug: Optional[str] = Field(None, alias='inferenceSimulatorSlug')
@@ -9,4 +9,6 @@ from thestage.services.project.dto.inference_simulator_model_dto import Inferenc
9
9
  class DeployInferenceModelToInstanceResponse(TheStageBaseResponse):
10
10
  model_config = ConfigDict(use_enum_values=True)
11
11
 
12
+ inferenceSimulatorPublicId: Optional[str] = Field(None, alias='inferenceSimulatorPublicId')
13
+
12
14
 
@@ -8,5 +8,5 @@ from thestage.services.clients.thestage_api.dtos.entity_filter_request import En
8
8
  class GetInferenceSimulatorRequest(BaseModel):
9
9
  model_config = ConfigDict(use_enum_values=True)
10
10
 
11
- public_id: Optional[str] = Field(None, alias='publicId')
11
+ publicId: Optional[str] = Field(None, alias='publicId')
12
12
  slug: Optional[str] = Field(None, alias='slug')
@@ -1,3 +1,5 @@
1
+ from typing import Optional, List
2
+
1
3
  from pydantic import Field, ConfigDict
2
4
 
3
5
  from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
@@ -8,3 +10,4 @@ class ProjectRunTaskResponse(TheStageBaseResponse):
8
10
  model_config = ConfigDict(use_enum_values=True)
9
11
 
10
12
  task: TaskDto = Field(None, alias='task')
13
+ tasksInQueue: Optional[List[TaskDto]] = Field(None, alias='tasksInQueue')
@@ -45,7 +45,7 @@ class ConnectService(AbstractService):
45
45
  username: Optional[str],
46
46
  private_key_path: Optional[str],
47
47
  ):
48
- resolved_options = self.__thestage_api_client.resolve_connect_options(entity_identifier=input_entity_identifier)
48
+ resolved_options = self.__thestage_api_client.resolve_user_input(entity_identifier=input_entity_identifier)
49
49
  entities_available_for_connect_count = 0
50
50
  task_presence = False
51
51
  container_presence = False
@@ -56,7 +56,7 @@ class ConnectService(AbstractService):
56
56
  if resolved_options.taskMatchData:
57
57
  for task_item in resolved_options.taskMatchData:
58
58
  message = f"Found a task with with matching {task_item.matchedField} in status: '{task_item.frontendStatus.status_translation}' (ID: {task_item.publicId})"
59
- line_color = ColorScheme.SUCCESS if task_item.canConnect else 'default'
59
+ line_color = ColorScheme.SUCCESS.value if task_item.canConnect else 'default'
60
60
  print(f"[{line_color}]{message}[{line_color}]")
61
61
  if task_item.canConnect:
62
62
  task_presence = True
@@ -66,7 +66,7 @@ class ConnectService(AbstractService):
66
66
  if resolved_options.dockerContainerMatchData:
67
67
  for container_item in resolved_options.dockerContainerMatchData:
68
68
  message = f"Found a container with matching {container_item.matchedField} in status: '{container_item.frontendStatus.status_translation}' (ID: {container_item.publicId})"
69
- line_color = ColorScheme.SUCCESS if container_item.canConnect else 'default'
69
+ line_color = ColorScheme.SUCCESS.value if container_item.canConnect else 'default'
70
70
  print(f"[{line_color}]{message}[{line_color}]")
71
71
  if container_item.canConnect:
72
72
  container_presence = True
@@ -76,7 +76,7 @@ class ConnectService(AbstractService):
76
76
  if resolved_options.instanceRentedMatchData:
77
77
  for instance_rented_item in resolved_options.instanceRentedMatchData:
78
78
  message = f"Found a rented instance with matching {instance_rented_item.matchedField} in status: '{instance_rented_item.frontendStatus.status_translation}' (ID: {instance_rented_item.publicId})"
79
- line_color = ColorScheme.SUCCESS if instance_rented_item.canConnect else 'default'
79
+ line_color = ColorScheme.SUCCESS.value if instance_rented_item.canConnect else 'default'
80
80
  print(f"[{line_color}]{message}[{line_color}]")
81
81
 
82
82
  if instance_rented_item.canConnect:
@@ -87,7 +87,7 @@ class ConnectService(AbstractService):
87
87
  if resolved_options.selfhostedInstanceMatchData:
88
88
  for selfhosted_item in resolved_options.selfhostedInstanceMatchData:
89
89
  message = f"Found a self-hosted instance with matching {selfhosted_item.matchedField} in status: '{selfhosted_item.frontendStatus.status_translation}' (ID: {selfhosted_item.publicId})"
90
- line_color = ColorScheme.SUCCESS if selfhosted_item.canConnect else 'default'
90
+ line_color = ColorScheme.SUCCESS.value if selfhosted_item.canConnect else 'default'
91
91
  print(f"[{line_color}]{message}[{line_color}]")
92
92
 
93
93
  if selfhosted_item.canConnect:
@@ -153,7 +153,7 @@ class ConnectService(AbstractService):
153
153
 
154
154
  # if no instances found - exit 1
155
155
  if instance_rented is None:
156
- typer.echo(f"No rented instance found with matching unique ID '{instance_slug}'")
156
+ typer.echo(f"No rented instance found with matching identifier")
157
157
  raise typer.Exit(1)
158
158
 
159
159
  note_to_send: Optional[str] = None
@@ -1,7 +1,11 @@
1
+ import re
1
2
  from pathlib import Path
2
3
  from typing import List, Tuple, Optional, Dict
4
+ from rich import print
3
5
 
4
6
  import typer
7
+
8
+ from thestage.color_scheme.color_scheme import ColorScheme
5
9
  from thestage.entities.container import DockerContainerEntity
6
10
  from thestage.services.clients.thestage_api.dtos.container_param_request import DockerContainerActionRequestDto
7
11
  from thestage.services.clients.thestage_api.dtos.enums.container_pending_action import DockerContainerAction
@@ -150,7 +154,7 @@ class ContainerService(AbstractService):
150
154
  if private_key_path:
151
155
  typer.echo(f'Using configured private key for {ip_address}: {private_key_path}')
152
156
  else:
153
- typer.echo(f'Using SSH agent to connect to {ip_address}')
157
+ typer.echo(f'Using SSH agent to connect to {ip_address} as {username}')
154
158
  else:
155
159
  self.__config_provider.update_remote_server_config_entry(ip_address, Path(private_key_path))
156
160
  typer.echo(f'Updated private key path for {ip_address}: {private_key_path}')
@@ -267,22 +271,49 @@ class ContainerService(AbstractService):
267
271
  @error_handler()
268
272
  def put_file_to_container(
269
273
  self,
270
- container: DockerContainerDto,
271
- src_path: str,
272
- copy_only_folder_contents: bool,
273
- destination_path: Optional[str] = None,
274
- username_param: Optional[str] = None,
274
+ source_path: str,
275
+ destination: str,
276
+ username_param: Optional[str],
275
277
  ):
276
- if not self.__file_system_service.check_if_path_exist(file=src_path):
277
- typer.echo(__("File not found at specified path"))
278
+ container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", destination)
279
+ if container_args is None:
280
+ typer.echo(__('Container name and source file path are required as the second argument'))
281
+ typer.echo(__('Example: container_name:/path/to/file'))
278
282
  raise typer.Exit(1)
283
+ container_identifier = container_args.groups()[0]
284
+ destination_path = container_args.groups()[1].rstrip("/")
279
285
 
280
- username, ip_address, private_key_path = self.get_server_auth(
281
- container=container,
282
- username_param=username_param,
283
- private_key_path_override=None
286
+ if not container_identifier:
287
+ typer.echo('Container identifier (container_id_or_name) is required')
288
+ raise typer.Exit(1)
289
+
290
+ resolved_options = self.__thestage_api_client.resolve_user_input(entity_identifier=container_identifier)
291
+ container_public_id = None
292
+ valid_container_count = 0
293
+ for container_item in resolved_options.dockerContainerMatchData:
294
+ message = f"Found a container with matching {container_item.matchedField} in status: '{container_item.frontendStatus.status_translation}' (ID: {container_item.publicId})"
295
+ line_color = ColorScheme.SUCCESS.value if container_item.canDownloadUploadOnContainer else 'default'
296
+ print(f"[{line_color}]{message}[{line_color}]")
297
+ if container_item.canDownloadUploadOnContainer:
298
+ valid_container_count += 1
299
+ container_public_id = container_item.publicId
300
+
301
+ if valid_container_count != 1:
302
+ typer.echo(f"Failed to resolve the container by provided identifier, as total of {valid_container_count} containers are valid options")
303
+ raise typer.Exit(1)
304
+
305
+ container: Optional[DockerContainerDto] = self.__thestage_api_client.get_container(
306
+ container_public_id=container_public_id,
284
307
  )
285
308
 
309
+ if not container:
310
+ typer.echo(f"Unexpected error: container '{container_public_id}' not found")
311
+ raise typer.Exit(1)
312
+
313
+ if not self.__file_system_service.check_if_path_exist(file=source_path):
314
+ typer.echo(__("File not found at specified path"))
315
+ raise typer.Exit(1)
316
+
286
317
  if not container.mappings or not container.mappings.directory_mappings:
287
318
  typer.echo(__("Mapping folders not found"))
288
319
  raise typer.Exit(1)
@@ -296,10 +327,18 @@ class ContainerService(AbstractService):
296
327
  typer.echo(__("Cannot find matching container volume mapping for specified file path"))
297
328
  raise typer.Exit(1)
298
329
 
330
+ username, ip_address, private_key_path = self.get_server_auth(
331
+ container=container,
332
+ username_param=username_param,
333
+ private_key_path_override=None
334
+ )
335
+
336
+ copy_only_folder_contents = source_path.endswith("/")
337
+
299
338
  self.__remote_server_service.upload_data_to_container(
300
339
  ip_address=ip_address,
301
340
  username=username,
302
- src_path=src_path,
341
+ src_path=source_path,
303
342
  dest_path=destination_path,
304
343
  instance_path=instance_path,
305
344
  container_path=container_path,
@@ -310,31 +349,67 @@ class ContainerService(AbstractService):
310
349
  @error_handler()
311
350
  def get_file_from_container(
312
351
  self,
313
- container: DockerContainerDto,
314
- src_path: str,
315
- copy_only_folder_contents: bool,
316
- destination_path: Optional[str] = None,
352
+ source: str,
353
+ destination_path: str,
317
354
  username_param: Optional[str] = None,
318
355
  ):
319
- username, ip_address, private_key_path = self.get_server_auth(
320
- container=container,
321
- username_param=username_param,
322
- private_key_path_override=None,
356
+ container_args = re.match(r"^([\w\W]+?):([\w\W]+)$", source)
357
+
358
+ if container_args is None:
359
+ typer.echo(__('Container name and source directory path are required as the first argument'))
360
+ typer.echo(__('Example: container_name:/path/to/file'))
361
+ raise typer.Exit(1)
362
+ container_identifier = container_args.groups()[0]
363
+ source_path = container_args.groups()[1]
364
+
365
+ if not container_identifier:
366
+ typer.echo('Container identifier (container_id_or_name) is required')
367
+ raise typer.Exit(1)
368
+
369
+ resolved_options = self.__thestage_api_client.resolve_user_input(entity_identifier=container_identifier)
370
+ container_public_id = None
371
+ valid_container_count = 0
372
+ for container_item in resolved_options.dockerContainerMatchData:
373
+ message = f"Found a container with matching {container_item.matchedField} in status: '{container_item.frontendStatus.status_translation}' (ID: {container_item.publicId})"
374
+ line_color = ColorScheme.SUCCESS.value if container_item.canDownloadUploadOnContainer else 'default'
375
+ print(f"[{line_color}]{message}[{line_color}]")
376
+ if container_item.canDownloadUploadOnContainer:
377
+ valid_container_count += 1
378
+ container_public_id = container_item.publicId
379
+
380
+ if valid_container_count != 1:
381
+ typer.echo(f"Failed to resolve the container by provided identifier, as total of {valid_container_count} containers are valid options")
382
+ raise typer.Exit(1)
383
+
384
+ container: Optional[DockerContainerDto] = self.__thestage_api_client.get_container(
385
+ container_public_id=container_public_id,
323
386
  )
324
387
 
388
+ if not container:
389
+ typer.echo(f"Unexpected error: container '{container_public_id}' not found")
390
+ raise typer.Exit(1)
391
+
325
392
  if not container.mappings or not container.mappings.directory_mappings:
326
393
  typer.echo(__("Mapping folders not found"))
327
394
  raise typer.Exit(1)
328
395
 
329
396
  instance_path, container_path = self._get_new_path_from_mapping(
330
397
  directory_mapping=container.mappings.directory_mappings,
331
- destination_path=src_path,
398
+ destination_path=source_path,
332
399
  )
333
400
 
334
401
  if not instance_path and not container_path:
335
402
  typer.echo(__("Cannot find matching container volume mapping for specified file path"))
336
403
  raise typer.Exit(1)
337
404
 
405
+ username, ip_address, private_key_path = self.get_server_auth(
406
+ container=container,
407
+ username_param=username_param,
408
+ private_key_path_override=None,
409
+ )
410
+
411
+ copy_only_folder_contents=source_path.endswith("/")
412
+
338
413
  self.__remote_server_service.download_data_from_container(
339
414
  ip_address=ip_address,
340
415
  username=username,