meshagent-cli 0.45.7__tar.gz → 0.46.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. {meshagent_cli-0.45.7/meshagent_cli.egg-info → meshagent_cli-0.46.0}/PKG-INFO +17 -17
  2. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/containers.py +5 -1
  3. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/containers_test.py +58 -6
  4. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/image.py +28 -49
  5. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/image_test.py +32 -88
  6. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/room_services.py +27 -4
  7. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/room_services_test.py +62 -2
  8. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/sessions.py +10 -1
  9. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/sessions_test.py +83 -0
  10. meshagent_cli-0.46.0/meshagent/cli/version.py +1 -0
  11. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0/meshagent_cli.egg-info}/PKG-INFO +17 -17
  12. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent_cli.egg-info/requires.txt +16 -16
  13. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/pyproject.toml +16 -16
  14. meshagent_cli-0.45.7/meshagent/cli/version.py +0 -1
  15. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/LICENSE +0 -0
  16. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/README.md +0 -0
  17. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/__init__.py +0 -0
  18. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/_filedrop.py +0 -0
  19. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/agent.py +0 -0
  20. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/agent_cli_options_test.py +0 -0
  21. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/agent_package_cli.py +0 -0
  22. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/agent_package_cli_test.py +0 -0
  23. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/agents.py +0 -0
  24. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/agents_test.py +0 -0
  25. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/ask.py +0 -0
  26. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/ask_test.py +0 -0
  27. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/async_typer.py +0 -0
  28. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/async_typer_test.py +0 -0
  29. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/auth.py +0 -0
  30. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/auth_async.py +0 -0
  31. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/auth_async_test.py +0 -0
  32. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/auth_test.py +0 -0
  33. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/call.py +0 -0
  34. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/chatbot.py +0 -0
  35. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/cli.py +0 -0
  36. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/cli_mcp.py +0 -0
  37. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/cli_test.py +0 -0
  38. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/common_options.py +0 -0
  39. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/config.py +0 -0
  40. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/config_test.py +0 -0
  41. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create.py +0 -0
  42. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create_project_templates/__init__.py +0 -0
  43. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create_project_templates/python/backend-agent/server.py +0 -0
  44. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create_project_templates/python/contact-form/server.py +0 -0
  45. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create_project_templates/python/task-queue-dashboard/server.py +0 -0
  46. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create_project_templates/python/webserver/server.py +0 -0
  47. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create_test.py +0 -0
  48. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/dataset.py +0 -0
  49. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/dataset_test.py +0 -0
  50. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/developer.py +0 -0
  51. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/developer_test.py +0 -0
  52. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/doctor.py +0 -0
  53. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/doctor_dockerfiles/__init__.py +0 -0
  54. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/doctor_templates/__init__.py +0 -0
  55. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/doctor_test.py +0 -0
  56. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/feeds.py +0 -0
  57. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/helper.py +0 -0
  58. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/helper_test.py +0 -0
  59. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/helpers.py +0 -0
  60. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/host.py +0 -0
  61. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/iam.py +0 -0
  62. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/iam_test.py +0 -0
  63. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/launch.py +0 -0
  64. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/launch_test.py +0 -0
  65. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/llm.py +0 -0
  66. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/llm_test.py +0 -0
  67. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/local_settings.py +0 -0
  68. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/local_settings_test.py +0 -0
  69. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/mailbot.py +0 -0
  70. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/mailbot_test.py +0 -0
  71. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/mailboxes.py +0 -0
  72. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/meeting_transcriber.py +0 -0
  73. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/memory.py +0 -0
  74. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/memory_test.py +0 -0
  75. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/meshagent_images.py +0 -0
  76. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/meshagent_images_test.py +0 -0
  77. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/messaging.py +0 -0
  78. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/messaging_test.py +0 -0
  79. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/multi.py +0 -0
  80. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/oci_archive.py +0 -0
  81. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/oci_archive_test.py +0 -0
  82. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/participant_token.py +0 -0
  83. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/port.py +0 -0
  84. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/port_test.py +0 -0
  85. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/preamble_rules.py +0 -0
  86. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/preamble_rules_test.py +0 -0
  87. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/process.py +0 -0
  88. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/process_live_test.py +0 -0
  89. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/process_test.py +0 -0
  90. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/projects.py +0 -0
  91. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/projects_test.py +0 -0
  92. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/queue.py +0 -0
  93. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/queue_test.py +0 -0
  94. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/registry.py +0 -0
  95. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/registry_test.py +0 -0
  96. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/room.py +0 -0
  97. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/room_connect.py +0 -0
  98. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/room_connect_test.py +0 -0
  99. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/rooms.py +0 -0
  100. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/rooms_test.py +0 -0
  101. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/root_commands.py +0 -0
  102. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/root_commands_test.py +0 -0
  103. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/routes.py +0 -0
  104. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/scheduled_tasks.py +0 -0
  105. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/scheduled_tasks_test.py +0 -0
  106. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/secrets.py +0 -0
  107. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/secrets_test.py +0 -0
  108. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/service_accounts.py +0 -0
  109. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/service_accounts_test.py +0 -0
  110. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/services.py +0 -0
  111. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/services_test.py +0 -0
  112. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/storage.py +0 -0
  113. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/storage_test.py +0 -0
  114. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/subscriptions.py +0 -0
  115. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/sync.py +0 -0
  116. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/sync_test.py +0 -0
  117. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/task_runner.py +0 -0
  118. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/task_runner_test.py +0 -0
  119. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/test.py +0 -0
  120. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/testing.py +0 -0
  121. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/thread_inspect.py +0 -0
  122. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/thread_inspect_test.py +0 -0
  123. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/thread_sidebar.py +0 -0
  124. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tool_call_summary.py +0 -0
  125. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tool_call_summary_test.py +0 -0
  126. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tool_integrations.py +0 -0
  127. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tool_integrations_test.py +0 -0
  128. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/__init__.py +0 -0
  129. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/auth_switch.py +0 -0
  130. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/auth_switch_test.py +0 -0
  131. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/create.py +0 -0
  132. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/deploy_room.py +0 -0
  133. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/deploy_room_test.py +0 -0
  134. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/project_activate.py +0 -0
  135. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/project_activate_test.py +0 -0
  136. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/setup.py +0 -0
  137. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/setup_splash_frames.py +0 -0
  138. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/setup_test.py +0 -0
  139. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/version_check.py +0 -0
  140. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/version_check_test.py +0 -0
  141. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/voicebot.py +0 -0
  142. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/webserver.py +0 -0
  143. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/webserver_test.py +0 -0
  144. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/worker.py +0 -0
  145. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/worker_test.py +0 -0
  146. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent_cli.egg-info/SOURCES.txt +0 -0
  147. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent_cli.egg-info/dependency_links.txt +0 -0
  148. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent_cli.egg-info/entry_points.txt +0 -0
  149. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent_cli.egg-info/top_level.txt +0 -0
  150. {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshagent-cli
3
- Version: 0.45.7
3
+ Version: 0.46.0
4
4
  Summary: CLI for Meshagent
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.meshagent.com
@@ -22,8 +22,8 @@ Requires-Dist: openpyxl~=3.1
22
22
  Requires-Dist: xlsxwriter~=3.2
23
23
  Requires-Dist: pathspec<2,>=1.0.3
24
24
  Requires-Dist: zstandard~=0.25.0
25
- Requires-Dist: meshagent-llm-proxy==0.45.7
26
- Requires-Dist: meshagent-codex==0.45.7
25
+ Requires-Dist: meshagent-llm-proxy==0.46.0
26
+ Requires-Dist: meshagent-codex==0.46.0
27
27
  Requires-Dist: rich~=14.3.0
28
28
  Requires-Dist: sounddevice~=0.5
29
29
  Requires-Dist: textual<9.0,>=8.2.3
@@ -32,23 +32,23 @@ Requires-Dist: prompt-toolkit~=3.0.52
32
32
  Requires-Dist: ascii-magic~=2.3
33
33
  Requires-Dist: pillow~=11.3.0
34
34
  Provides-Extra: all
35
- Requires-Dist: meshagent-agents[all]==0.45.7; extra == "all"
36
- Requires-Dist: meshagent-api[all]==0.45.7; extra == "all"
37
- Requires-Dist: meshagent-commoncrawl==0.45.7; extra == "all"
38
- Requires-Dist: meshagent-scrapy==0.45.7; extra == "all"
39
- Requires-Dist: meshagent-computers==0.45.7; extra == "all"
40
- Requires-Dist: meshagent-openai==0.45.7; extra == "all"
41
- Requires-Dist: meshagent-anthropic==0.45.7; extra == "all"
42
- Requires-Dist: meshagent-otel==0.45.7; extra == "all"
43
- Requires-Dist: meshagent-mcp==0.45.7; extra == "all"
44
- Requires-Dist: meshagent-tools==0.45.7; extra == "all"
35
+ Requires-Dist: meshagent-agents[all]==0.46.0; extra == "all"
36
+ Requires-Dist: meshagent-api[all]==0.46.0; extra == "all"
37
+ Requires-Dist: meshagent-commoncrawl==0.46.0; extra == "all"
38
+ Requires-Dist: meshagent-scrapy==0.46.0; extra == "all"
39
+ Requires-Dist: meshagent-computers==0.46.0; extra == "all"
40
+ Requires-Dist: meshagent-openai==0.46.0; extra == "all"
41
+ Requires-Dist: meshagent-anthropic==0.46.0; extra == "all"
42
+ Requires-Dist: meshagent-otel==0.46.0; extra == "all"
43
+ Requires-Dist: meshagent-mcp==0.46.0; extra == "all"
44
+ Requires-Dist: meshagent-tools==0.46.0; extra == "all"
45
45
  Requires-Dist: supabase-auth~=2.28.0; extra == "all"
46
46
  Requires-Dist: prompt-toolkit~=3.0.52; extra == "all"
47
47
  Provides-Extra: mcp-service
48
- Requires-Dist: meshagent-agents[all]==0.45.7; extra == "mcp-service"
49
- Requires-Dist: meshagent-api==0.45.7; extra == "mcp-service"
50
- Requires-Dist: meshagent-mcp==0.45.7; extra == "mcp-service"
51
- Requires-Dist: meshagent-tools==0.45.7; extra == "mcp-service"
48
+ Requires-Dist: meshagent-agents[all]==0.46.0; extra == "mcp-service"
49
+ Requires-Dist: meshagent-api==0.46.0; extra == "mcp-service"
50
+ Requires-Dist: meshagent-mcp==0.46.0; extra == "mcp-service"
51
+ Requires-Dist: meshagent-tools==0.46.0; extra == "mcp-service"
52
52
  Requires-Dist: supabase-auth~=2.28.0; extra == "mcp-service"
53
53
  Dynamic: license-file
54
54
 
@@ -538,8 +538,12 @@ async def _with_client(
538
538
 
539
539
  connection = await account_client.connect_room(project_id=project_id, room=room)
540
540
 
541
+ room_url = connection.room_url
542
+ if os.getenv("MESHAGENT_ROOM_URL"):
543
+ room_url = websocket_room_url(room_name=room)
544
+
541
545
  proto = WebSocketClientProtocol(
542
- url=websocket_room_url(room_name=room),
546
+ url=room_url,
543
547
  token=connection.jwt,
544
548
  )
545
549
  client_cm = RoomClient(protocol_factory=proto.create_factory())
@@ -525,6 +525,7 @@ async def test_images_inspect_renders_detail_tables(
525
525
  async def test_with_client_uses_configured_websocket_room_url(
526
526
  monkeypatch: pytest.MonkeyPatch,
527
527
  ) -> None:
528
+ monkeypatch.delenv("MESHAGENT_ROOM_URL", raising=False)
528
529
  connection = SimpleNamespace(
529
530
  jwt="jwt-token",
530
531
  room_url="wss://room-router.meshagent.dev/custom/room-endpoint",
@@ -560,11 +561,6 @@ async def test_with_client_uses_configured_websocket_room_url(
560
561
  monkeypatch.setattr(containers, "get_client", _fake_get_client)
561
562
  monkeypatch.setattr(containers, "resolve_project_id", _fake_resolve_project_id)
562
563
  monkeypatch.setattr(containers, "resolve_room", lambda room: f"{room}-resolved")
563
- monkeypatch.setattr(
564
- containers,
565
- "websocket_room_url",
566
- lambda room_name: f"wss://configured-router/{room_name}",
567
- )
568
564
  monkeypatch.setattr(containers, "WebSocketClientProtocol", _FakeProtocol)
569
565
  monkeypatch.setattr(containers, "RoomClient", _FakeRoomClient)
570
566
 
@@ -579,7 +575,63 @@ async def test_with_client_uses_configured_websocket_room_url(
579
575
  ]
580
576
  assert protocol_calls == [
581
577
  {
582
- "url": "wss://configured-router/room-1-resolved",
578
+ "url": "wss://room-router.meshagent.dev/custom/room-endpoint",
579
+ "token": "jwt-token",
580
+ }
581
+ ]
582
+ assert isinstance(client, _FakeRoomClient)
583
+ assert client.enter_calls == 1
584
+
585
+
586
+ @pytest.mark.asyncio
587
+ async def test_with_client_allows_environment_room_url_override(
588
+ monkeypatch: pytest.MonkeyPatch,
589
+ ) -> None:
590
+ monkeypatch.setenv("MESHAGENT_ROOM_URL", "wss://override.meshagent.test")
591
+ connection = SimpleNamespace(
592
+ jwt="jwt-token",
593
+ room_url="wss://room-router.meshagent.dev/custom/room-endpoint",
594
+ )
595
+ account_client = _FakeConnectAccountClient(connection=connection)
596
+ protocol_calls: list[dict[str, str]] = []
597
+
598
+ class _FakeProtocol:
599
+ def __init__(self, *, url: str, token: str) -> None:
600
+ protocol_calls.append({"url": url, "token": token})
601
+
602
+ def create_factory(self):
603
+ return lambda: self
604
+
605
+ class _FakeRoomClient:
606
+ def __init__(self, *, protocol_factory) -> None:
607
+ self.protocol = protocol_factory()
608
+ self.enter_calls = 0
609
+
610
+ async def __aenter__(self) -> "_FakeRoomClient":
611
+ self.enter_calls += 1
612
+ return self
613
+
614
+ async def _fake_get_client() -> _FakeConnectAccountClient:
615
+ return account_client
616
+
617
+ async def _fake_resolve_project_id(*, project_id):
618
+ return project_id
619
+
620
+ monkeypatch.setattr(containers, "get_client", _fake_get_client)
621
+ monkeypatch.setattr(containers, "resolve_project_id", _fake_resolve_project_id)
622
+ monkeypatch.setattr(containers, "resolve_room", lambda room: room)
623
+ monkeypatch.setattr(containers, "WebSocketClientProtocol", _FakeProtocol)
624
+ monkeypatch.setattr(containers, "RoomClient", _FakeRoomClient)
625
+
626
+ returned_account_client, client = await containers._with_client(
627
+ project_id="project-1",
628
+ room="room-1",
629
+ )
630
+
631
+ assert returned_account_client is account_client
632
+ assert protocol_calls == [
633
+ {
634
+ "url": "wss://override.meshagent.test/rooms/room-1",
583
635
  "token": "jwt-token",
584
636
  }
585
637
  ]
@@ -15,14 +15,12 @@ from pathlib import Path, PurePosixPath
15
15
  from typing import Annotated, Literal, Optional, Protocol, TYPE_CHECKING
16
16
  from urllib.parse import urlparse
17
17
 
18
- from aiohttp import ClientTimeout
19
18
  import typer
20
19
  from pydantic import ValidationError
21
20
  from pydantic_yaml import parse_yaml_raw_as
22
21
  from rich import print
23
22
  import yaml
24
23
 
25
- from meshagent.api.http import new_client_session
26
24
  from meshagent.cli import async_typer
27
25
  from meshagent.cli.containers import (
28
26
  _drain_stream_plain,
@@ -70,6 +68,7 @@ from meshagent.api.room_server_client import (
70
68
  LogStream,
71
69
  PublishedBuildImage,
72
70
  RoomException,
71
+ ServicePortRuntimeState,
73
72
  ServiceRuntimeState,
74
73
  )
75
74
  from meshagent.api.room_ports import RESERVED_ROOM_SERVICE_PORTS
@@ -129,7 +128,6 @@ _CLIENT_CLOSE_TIMEOUT_SECONDS = 2.0
129
128
  _DEFAULT_CONTEXT_MOUNT_PATH = "/context"
130
129
  _DEFAULT_REPOSITORY_TOKEN_TTL_SECONDS = 3600
131
130
  _DEPLOY_CACHE_CLEANUP_TIMEOUT_SECONDS = 30.0
132
- _DEPLOY_LIVENESS_REQUEST_TIMEOUT_SECONDS = 2.0
133
131
  _DEPLOY_SERVICE_APPLY_TIMEOUT_SECONDS = 60.0
134
132
  _DEPLOY_SERVICE_RESTART_TIMEOUT_SECONDS = 60.0
135
133
  _DEPLOY_WAIT_TIMEOUT_SECONDS = 300.0
@@ -3216,37 +3214,6 @@ def _resolve_domain_liveness_path(
3216
3214
  return service_port.liveness
3217
3215
 
3218
3216
 
3219
- def _build_domain_liveness_url(*, domain: str, liveness_path: str) -> str:
3220
- parsed = urlparse(domain if "://" in domain else f"https://{domain}")
3221
- scheme = parsed.scheme or "https"
3222
- netloc = parsed.netloc
3223
- base_path = parsed.path
3224
- if netloc == "":
3225
- netloc = parsed.path
3226
- base_path = ""
3227
- if base_path != "/" and base_path.endswith("/"):
3228
- base_path = base_path.rstrip("/")
3229
- return f"{scheme}://{netloc}{base_path}{liveness_path}"
3230
-
3231
-
3232
- async def _probe_liveness_url(*, url: str) -> bool:
3233
- try:
3234
- async with new_client_session(
3235
- timeout=ClientTimeout(total=_DEPLOY_LIVENESS_REQUEST_TIMEOUT_SECONDS)
3236
- ) as session:
3237
- async with session.get(
3238
- url,
3239
- headers={
3240
- "Accept": "*/*",
3241
- "User-Agent": f"meshagent-cli/{__version__}",
3242
- },
3243
- ) as resp:
3244
- status_code = resp.status
3245
- except Exception:
3246
- return False
3247
- return 200 <= status_code < 400 or status_code in {401, 403}
3248
-
3249
-
3250
3217
  async def _find_room_service_by_name(
3251
3218
  *,
3252
3219
  account_client,
@@ -3580,10 +3547,23 @@ async def _get_service_runtime_state(
3580
3547
  client: RoomClient,
3581
3548
  service_id: str,
3582
3549
  ) -> ServiceRuntimeState | None:
3583
- service_list = await client.services.list_with_state()
3550
+ service_list = await client.services.list()
3584
3551
  return service_list.service_states.get(service_id)
3585
3552
 
3586
3553
 
3554
+ def _find_service_liveness_port(
3555
+ *,
3556
+ state: ServiceRuntimeState,
3557
+ liveness_path: str | None,
3558
+ ) -> ServicePortRuntimeState | None:
3559
+ if liveness_path is None:
3560
+ return None
3561
+ for port in state.status.ports:
3562
+ if port.liveness == liveness_path:
3563
+ return port
3564
+ return None
3565
+
3566
+
3587
3567
  async def _drain_deploy_log_stream_tui(
3588
3568
  stream: LogStream,
3589
3569
  *,
@@ -3699,33 +3679,28 @@ async def _wait_for_deployed_service_live(
3699
3679
  log_handler: Callable[[str], Awaitable[None]] | None = None,
3700
3680
  ) -> None:
3701
3681
  active_logs: _ActiveDeployLogStream | None = None
3702
- liveness_url = (
3703
- _build_domain_liveness_url(domain=domain, liveness_path=liveness_path)
3704
- if domain is not None and liveness_path is not None
3705
- else None
3706
- )
3707
3682
 
3708
3683
  await _emit_deploy_status(
3709
3684
  status_handler,
3710
3685
  rich_message=f"[cyan]Waiting for service to go live:[/] {service_name} ({service_id})",
3711
3686
  plain_message=f"Waiting for service to go live: {service_name} ({service_id})",
3712
3687
  )
3713
- if domain is not None and liveness_url is not None:
3688
+ if liveness_path is not None:
3714
3689
  await _emit_deploy_status(
3715
3690
  status_handler,
3716
- rich_message=f"[cyan]Waiting for liveness URL:[/] {liveness_url}",
3717
- plain_message=f"Waiting for liveness URL: {liveness_url}",
3691
+ rich_message=f"[cyan]Waiting for service liveness:[/] {liveness_path}",
3692
+ plain_message=f"Waiting for service liveness: {liveness_path}",
3718
3693
  )
3719
3694
  elif domain is not None:
3720
3695
  await _emit_deploy_status(
3721
3696
  status_handler,
3722
3697
  rich_message=(
3723
3698
  f"[yellow]Route created for {domain}, but the service has no HTTP "
3724
- "liveness path to probe.[/]"
3699
+ "liveness path to check.[/]"
3725
3700
  ),
3726
3701
  plain_message=(
3727
3702
  f"Route created for {domain}, but the service has no HTTP "
3728
- "liveness path to probe."
3703
+ "liveness path to check."
3729
3704
  ),
3730
3705
  )
3731
3706
 
@@ -3803,7 +3778,7 @@ async def _wait_for_deployed_service_live(
3803
3778
  await asyncio.sleep(_DEPLOY_WAIT_POLL_INTERVAL_SECONDS)
3804
3779
  continue
3805
3780
 
3806
- if liveness_url is None:
3781
+ if liveness_path is None:
3807
3782
  await _emit_deploy_status(
3808
3783
  status_handler,
3809
3784
  rich_message=f"[green]Service is live:[/] {service_name} ({service_id})",
@@ -3811,11 +3786,15 @@ async def _wait_for_deployed_service_live(
3811
3786
  )
3812
3787
  return
3813
3788
 
3814
- if await _probe_liveness_url(url=liveness_url):
3789
+ liveness_port = _find_service_liveness_port(
3790
+ state=state,
3791
+ liveness_path=liveness_path,
3792
+ )
3793
+ if liveness_port is not None and liveness_port.liveness_status == "ready":
3815
3794
  await _emit_deploy_status(
3816
3795
  status_handler,
3817
- rich_message=f"[green]Liveness URL responded:[/] {liveness_url}",
3818
- plain_message=f"Liveness URL responded: {liveness_url}",
3796
+ rich_message=f"[green]Service liveness is ready:[/] {liveness_path}",
3797
+ plain_message=f"Service liveness is ready: {liveness_path}",
3819
3798
  )
3820
3799
  return
3821
3800
 
@@ -38,6 +38,8 @@ from meshagent.cli import cli, image
38
38
  from meshagent.api.room_server_client import (
39
39
  PublishedBuildImage,
40
40
  RoomException,
41
+ ServicePortRuntimeState,
42
+ ServiceRuntimeStatus,
41
43
  ServiceRuntimeState,
42
44
  )
43
45
  from meshagent.api.specs.service import (
@@ -2274,7 +2276,7 @@ def test_parse_meshagent_token_scope_supports_presets_and_json() -> None:
2274
2276
  user_default = image._parse_meshagent_token_scope(value="userDefault")
2275
2277
  custom = image._parse_meshagent_token_scope(value='{"queues":{"send":["jobs"]}}')
2276
2278
 
2277
- assert user_default.secrets is not None
2279
+ assert user_default.secrets is None
2278
2280
  assert user_default.admin is None
2279
2281
  assert custom.queues is not None
2280
2282
  assert custom.queues.send == ["jobs"]
@@ -2335,71 +2337,7 @@ async def test_validate_deploy_environment_secrets_requires_run_as() -> None:
2335
2337
 
2336
2338
 
2337
2339
  @pytest.mark.asyncio
2338
- @pytest.mark.parametrize("status_code", [200, 302, 401, 403])
2339
- async def test_probe_liveness_url_accepts_reachable_statuses(
2340
- monkeypatch: pytest.MonkeyPatch,
2341
- status_code: int,
2342
- ) -> None:
2343
- class _FakeResponse:
2344
- status = status_code
2345
-
2346
- async def __aenter__(self):
2347
- return self
2348
-
2349
- async def __aexit__(self, exc_type, exc, tb) -> None:
2350
- del exc_type, exc, tb
2351
-
2352
- class _FakeSession:
2353
- async def __aenter__(self):
2354
- return self
2355
-
2356
- async def __aexit__(self, exc_type, exc, tb) -> None:
2357
- del exc_type, exc, tb
2358
-
2359
- def get(self, *args, **kwargs):
2360
- del args, kwargs
2361
- return _FakeResponse()
2362
-
2363
- monkeypatch.setattr(
2364
- image, "new_client_session", lambda *args, **kwargs: _FakeSession()
2365
- )
2366
-
2367
- assert await image._probe_liveness_url(url="https://sheets.meshagent.dev/") is True
2368
-
2369
-
2370
- @pytest.mark.asyncio
2371
- async def test_probe_liveness_url_rejects_server_errors(
2372
- monkeypatch: pytest.MonkeyPatch,
2373
- ) -> None:
2374
- class _FakeResponse:
2375
- status = 502
2376
-
2377
- async def __aenter__(self):
2378
- return self
2379
-
2380
- async def __aexit__(self, exc_type, exc, tb) -> None:
2381
- del exc_type, exc, tb
2382
-
2383
- class _FakeSession:
2384
- async def __aenter__(self):
2385
- return self
2386
-
2387
- async def __aexit__(self, exc_type, exc, tb) -> None:
2388
- del exc_type, exc, tb
2389
-
2390
- def get(self, *args, **kwargs):
2391
- del args, kwargs
2392
- return _FakeResponse()
2393
-
2394
- monkeypatch.setattr(
2395
- image, "new_client_session", lambda *args, **kwargs: _FakeSession()
2396
- )
2397
-
2398
- assert await image._probe_liveness_url(url="https://sheets.meshagent.dev/") is False
2399
-
2400
-
2401
- @pytest.mark.asyncio
2402
- async def test_wait_for_deployed_service_live_streams_logs_and_checks_liveness(
2340
+ async def test_wait_for_deployed_service_live_streams_logs_and_checks_service_liveness(
2403
2341
  monkeypatch: pytest.MonkeyPatch,
2404
2342
  _stub_deploy_wait: dict[str, object],
2405
2343
  ) -> None:
@@ -2407,7 +2345,6 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_liveness(
2407
2345
  "prints": [],
2408
2346
  "started_logs": [],
2409
2347
  "stopped_logs": [],
2410
- "probe_urls": [],
2411
2348
  }
2412
2349
  states = [
2413
2350
  ServiceRuntimeState(service_id="service-1", state="starting"),
@@ -2420,11 +2357,29 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_liveness(
2420
2357
  service_id="service-1",
2421
2358
  state="running",
2422
2359
  container_id="container-1",
2360
+ status=ServiceRuntimeStatus(
2361
+ ports=[
2362
+ ServicePortRuntimeState(
2363
+ num=8000,
2364
+ liveness="/ready",
2365
+ liveness_status="not_ready",
2366
+ )
2367
+ ]
2368
+ ),
2423
2369
  ),
2424
2370
  ServiceRuntimeState(
2425
2371
  service_id="service-1",
2426
2372
  state="running",
2427
2373
  container_id="container-1",
2374
+ status=ServiceRuntimeStatus(
2375
+ ports=[
2376
+ ServicePortRuntimeState(
2377
+ num=8000,
2378
+ liveness="/ready",
2379
+ liveness_status="ready",
2380
+ )
2381
+ ]
2382
+ ),
2428
2383
  ),
2429
2384
  ]
2430
2385
  fake_active_logs = SimpleNamespace(container_id="container-1")
@@ -2434,7 +2389,7 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_liveness(
2434
2389
  self._runtime_states = runtime_states
2435
2390
  self._index = 0
2436
2391
 
2437
- async def list_with_state(self):
2392
+ async def list(self):
2438
2393
  state = self._runtime_states[
2439
2394
  min(self._index, len(self._runtime_states) - 1)
2440
2395
  ]
@@ -2454,19 +2409,12 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_liveness(
2454
2409
  async def _fake_stop_deploy_log_stream(*, active_logs) -> None:
2455
2410
  captured["stopped_logs"].append(active_logs)
2456
2411
 
2457
- probe_results = iter([False, True])
2458
-
2459
- async def _fake_probe_liveness_url(*, url: str) -> bool:
2460
- captured["probe_urls"].append(url)
2461
- return next(probe_results)
2462
-
2463
2412
  monkeypatch.setattr(image.asyncio, "to_thread", _fake_to_thread)
2464
2413
  monkeypatch.setattr(image, "_DEPLOY_WAIT_POLL_INTERVAL_SECONDS", 0)
2465
2414
  monkeypatch.setattr(
2466
2415
  image, "_start_deploy_log_stream", _fake_start_deploy_log_stream
2467
2416
  )
2468
2417
  monkeypatch.setattr(image, "_stop_deploy_log_stream", _fake_stop_deploy_log_stream)
2469
- monkeypatch.setattr(image, "_probe_liveness_url", _fake_probe_liveness_url)
2470
2418
  monkeypatch.setattr(
2471
2419
  image,
2472
2420
  "print",
@@ -2487,11 +2435,7 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_liveness(
2487
2435
 
2488
2436
  assert captured["started_logs"] == ["container-1"]
2489
2437
  assert captured["stopped_logs"] == [fake_active_logs]
2490
- assert captured["probe_urls"] == [
2491
- "https://app.meshagent.app/ready",
2492
- "https://app.meshagent.app/ready",
2493
- ]
2494
- assert any("Liveness URL responded" in message for message in captured["prints"])
2438
+ assert any("Service liveness is ready" in message for message in captured["prints"])
2495
2439
 
2496
2440
 
2497
2441
  @pytest.mark.asyncio
@@ -2525,7 +2469,7 @@ async def test_wait_for_deployed_service_live_exits_when_container_restarts(
2525
2469
  self._runtime_states = runtime_states
2526
2470
  self._index = 0
2527
2471
 
2528
- async def list_with_state(self):
2472
+ async def list(self):
2529
2473
  state = self._runtime_states[
2530
2474
  min(self._index, len(self._runtime_states) - 1)
2531
2475
  ]
@@ -2832,7 +2776,7 @@ async def test_deploy_image_creates_room_service_with_mounts_env_secret_and_toke
2832
2776
  async def restart(self, *, service_id: str) -> None:
2833
2777
  captured["restarted_service_id"] = service_id
2834
2778
 
2835
- async def list_with_state(self):
2779
+ async def list(self):
2836
2780
  return SimpleNamespace(
2837
2781
  service_states={
2838
2782
  "service-1": ServiceRuntimeState(
@@ -4944,7 +4888,7 @@ async def test_deploy_image_updates_existing_service_route_and_replaces_environm
4944
4888
  async def restart(self, *, service_id: str) -> None:
4945
4889
  captured["restarted_service_id"] = service_id
4946
4890
 
4947
- async def list_with_state(self):
4891
+ async def list(self):
4948
4892
  return SimpleNamespace(
4949
4893
  service_states={
4950
4894
  "service-1": ServiceRuntimeState(
@@ -5048,7 +4992,7 @@ async def test_deploy_image_updates_existing_service_route_and_replaces_environm
5048
4992
  assert env_by_name["MESHAGENT_TOKEN"].token.role == "agent"
5049
4993
  assert env_by_name["MESHAGENT_TOKEN"].token.api is not None
5050
4994
  assert env_by_name["MESHAGENT_TOKEN"].token.api.admin is not None
5051
- assert env_by_name["MESHAGENT_TOKEN"].token.api.secrets is not None
4995
+ assert env_by_name["MESHAGENT_TOKEN"].token.api.secrets is None
5052
4996
  assert env_by_name["MESHAGENT_TOKEN"].token.api.tunnels is not None
5053
4997
  for env_name in ("OPENAI_API_KEY", "ANTHROPIC_API_KEY"):
5054
4998
  assert env_by_name[env_name].token is not None
@@ -5098,7 +5042,7 @@ async def test_deploy_image_sets_cookie_validation_on_private_published_ports(
5098
5042
  async def restart(self, *, service_id: str) -> None:
5099
5043
  captured["restarted_service_id"] = service_id
5100
5044
 
5101
- async def list_with_state(self):
5045
+ async def list(self):
5102
5046
  return SimpleNamespace(
5103
5047
  service_states={
5104
5048
  "service-1": ServiceRuntimeState(
@@ -5223,7 +5167,7 @@ async def test_deploy_image_preserves_existing_liveness_when_default_is_used(
5223
5167
  async def restart(self, *, service_id: str) -> None:
5224
5168
  captured["restarted_service_id"] = service_id
5225
5169
 
5226
- async def list_with_state(self):
5170
+ async def list(self):
5227
5171
  return SimpleNamespace(
5228
5172
  service_states={
5229
5173
  "service-1": ServiceRuntimeState(
@@ -5325,7 +5269,7 @@ async def test_deploy_image_liveness_flag_overrides_http_ports_only(
5325
5269
  async def restart(self, *, service_id: str) -> None:
5326
5270
  captured["restarted_service_id"] = service_id
5327
5271
 
5328
- async def list_with_state(self):
5272
+ async def list(self):
5329
5273
  return SimpleNamespace(
5330
5274
  service_states={
5331
5275
  "service-1": ServiceRuntimeState(
@@ -5417,7 +5361,7 @@ async def test_deploy_image_domain_requires_exactly_one_published_port(
5417
5361
  )
5418
5362
 
5419
5363
  class _FakeServices:
5420
- async def list_with_state(self):
5364
+ async def list(self):
5421
5365
  return SimpleNamespace(
5422
5366
  service_states={
5423
5367
  "service-1": ServiceRuntimeState(
@@ -9,7 +9,12 @@ from meshagent.cli.helper import (
9
9
  )
10
10
  from meshagent.api import RoomClient, WebSocketClientProtocol
11
11
  from meshagent.api.helpers import websocket_room_url
12
- from meshagent.api.room_server_client import ServicesClient, ListServicesResult
12
+ from meshagent.api.room_server_client import (
13
+ ListServicesResult,
14
+ ServicePortRuntimeState,
15
+ ServiceRuntimeState,
16
+ ServicesClient,
17
+ )
13
18
  from meshagent.api.specs.service import ServiceSpec
14
19
  from datetime import datetime
15
20
  from typing import Annotated, Any, Optional
@@ -25,6 +30,22 @@ def _format_unix_timestamp(value: float | None) -> str | None:
25
30
  return datetime.fromtimestamp(value).isoformat()
26
31
 
27
32
 
33
+ def _format_port_status(port: ServicePortRuntimeState) -> str:
34
+ if port.liveness_status == "not_configured":
35
+ status = "no liveness"
36
+ elif port.liveness_status == "ready":
37
+ status = "ready"
38
+ else:
39
+ status = "not ready"
40
+ return f"{port.num}: {status}"
41
+
42
+
43
+ def _format_service_ports(state: ServiceRuntimeState | None) -> str | None:
44
+ if state is None:
45
+ return None
46
+ return ", ".join(_format_port_status(port) for port in state.status.ports)
47
+
48
+
28
49
  async def _connect_services_client(
29
50
  *, project_id: str | None, room: str | None
30
51
  ) -> tuple[Any, RoomClient, ServicesClient]:
@@ -74,7 +95,7 @@ async def room_services_list_command(
74
95
  )
75
96
  try:
76
97
  print("[bold green]Fetching services...[/bold green]")
77
- result: ListServicesResult = await services_client.list_with_state()
98
+ result: ListServicesResult = await services_client.list()
78
99
  services: list[ServiceSpec] = result.services
79
100
 
80
101
  rows = []
@@ -86,6 +107,7 @@ async def room_services_list_command(
86
107
  "name": svc.metadata.name,
87
108
  "image": svc.container.image if svc.container is not None else None,
88
109
  "state": state.state if state is not None else None,
110
+ "ports": _format_service_ports(state),
89
111
  "container_id": state.container_id if state is not None else None,
90
112
  "restart_scheduled_at": _format_unix_timestamp(
91
113
  state.restart_scheduled_at if state is not None else None
@@ -121,6 +143,7 @@ async def room_services_list_command(
121
143
  "name",
122
144
  "image",
123
145
  "state",
146
+ "ports",
124
147
  "container_id",
125
148
  "started_at",
126
149
  "restart_scheduled_at",
@@ -157,7 +180,7 @@ async def room_services_describe_command(
157
180
  project_id=project_id, room=room
158
181
  )
159
182
  try:
160
- result: ListServicesResult = await services_client.list_with_state()
183
+ result: ListServicesResult = await services_client.list()
161
184
  service = _find_service(
162
185
  services=result.services, service_id=service_id, name=name
163
186
  )
@@ -282,7 +305,7 @@ async def room_services_restart_command(
282
305
  ).create_factory()
283
306
  ) as client:
284
307
  services_client = ServicesClient(room=client)
285
- result: ListServicesResult = await services_client.list_with_state()
308
+ result: ListServicesResult = await services_client.list()
286
309
  services: list[ServiceSpec] = result.services
287
310
 
288
311
  service: ServiceSpec | None = None