meshagent-cli 0.34.0__tar.gz → 0.35.3__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 (81) hide show
  1. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/PKG-INFO +13 -13
  2. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/cli.py +2 -0
  3. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/containers.py +40 -104
  4. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/containers_test.py +55 -0
  5. meshagent_cli-0.35.3/meshagent/cli/image.py +1365 -0
  6. meshagent_cli-0.35.3/meshagent/cli/image_test.py +1439 -0
  7. meshagent_cli-0.35.3/meshagent/cli/oci_archive.py +1209 -0
  8. meshagent_cli-0.35.3/meshagent/cli/oci_archive_test.py +220 -0
  9. meshagent_cli-0.35.3/meshagent/cli/version.py +1 -0
  10. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent_cli.egg-info/PKG-INFO +13 -13
  11. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent_cli.egg-info/SOURCES.txt +4 -0
  12. meshagent_cli-0.35.3/meshagent_cli.egg-info/requires.txt +29 -0
  13. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/pyproject.toml +12 -12
  14. meshagent_cli-0.34.0/meshagent/cli/version.py +0 -1
  15. meshagent_cli-0.34.0/meshagent_cli.egg-info/requires.txt +0 -29
  16. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/README.md +0 -0
  17. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/__init__.py +0 -0
  18. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/agent.py +0 -0
  19. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/api_keys.py +0 -0
  20. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/async_typer.py +0 -0
  21. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/auth.py +0 -0
  22. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/auth_async.py +0 -0
  23. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/call.py +0 -0
  24. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/chatbot.py +0 -0
  25. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/cli_mcp.py +0 -0
  26. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/cli_secrets.py +0 -0
  27. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/cli_test.py +0 -0
  28. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/codex.py +0 -0
  29. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/common_options.py +0 -0
  30. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/database.py +0 -0
  31. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/developer.py +0 -0
  32. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/developer_test.py +0 -0
  33. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/helper.py +0 -0
  34. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/helper_test.py +0 -0
  35. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/helpers.py +0 -0
  36. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/host.py +0 -0
  37. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/mailbot.py +0 -0
  38. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/mailboxes.py +0 -0
  39. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/meeting_transcriber.py +0 -0
  40. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/memory.py +0 -0
  41. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/memory_test.py +0 -0
  42. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/messaging.py +0 -0
  43. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/multi.py +0 -0
  44. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/oauth2.py +0 -0
  45. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/participant_token.py +0 -0
  46. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/port.py +0 -0
  47. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/port_test.py +0 -0
  48. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/process.py +0 -0
  49. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/process_test.py +0 -0
  50. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/projects.py +0 -0
  51. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/queue.py +0 -0
  52. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/queue_test.py +0 -0
  53. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/room.py +0 -0
  54. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/room_services.py +0 -0
  55. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/rooms.py +0 -0
  56. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/routes.py +0 -0
  57. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/scheduled_tasks.py +0 -0
  58. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/scheduled_tasks_test.py +0 -0
  59. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/services.py +0 -0
  60. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/services_test.py +0 -0
  61. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/sessions.py +0 -0
  62. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/sessions_test.py +0 -0
  63. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/storage.py +0 -0
  64. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/storage_test.py +0 -0
  65. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/sync.py +0 -0
  66. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/sync_test.py +0 -0
  67. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/task_runner.py +0 -0
  68. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/test.py +0 -0
  69. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/tui/__init__.py +0 -0
  70. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/tui/setup.py +0 -0
  71. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/tui/setup_splash_frames.py +0 -0
  72. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/tui/setup_test.py +0 -0
  73. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/voicebot.py +0 -0
  74. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/webhook.py +0 -0
  75. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/webserver.py +0 -0
  76. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/webserver_test.py +0 -0
  77. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent/cli/worker.py +0 -0
  78. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent_cli.egg-info/dependency_links.txt +0 -0
  79. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent_cli.egg-info/entry_points.txt +0 -0
  80. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/meshagent_cli.egg-info/top_level.txt +0 -0
  81. {meshagent_cli-0.34.0 → meshagent_cli-0.35.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshagent-cli
3
- Version: 0.34.0
3
+ Version: 0.35.3
4
4
  Summary: CLI for Meshagent
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.meshagent.com
@@ -19,21 +19,21 @@ Requires-Dist: rich~=14.3.0
19
19
  Requires-Dist: textual<2.0,>=0.50
20
20
  Requires-Dist: prompt-toolkit~=3.0.52
21
21
  Provides-Extra: all
22
- Requires-Dist: meshagent-agents[all]~=0.34.0; extra == "all"
23
- Requires-Dist: meshagent-api[all]~=0.34.0; extra == "all"
24
- Requires-Dist: meshagent-computers~=0.34.0; extra == "all"
25
- Requires-Dist: meshagent-openai~=0.34.0; extra == "all"
26
- Requires-Dist: meshagent-anthropic~=0.34.0; extra == "all"
27
- Requires-Dist: meshagent-codex~=0.34.0; extra == "all"
28
- Requires-Dist: meshagent-mcp~=0.34.0; extra == "all"
29
- Requires-Dist: meshagent-tools~=0.34.0; extra == "all"
22
+ Requires-Dist: meshagent-agents[all]~=0.35.3; extra == "all"
23
+ Requires-Dist: meshagent-api[all]~=0.35.3; extra == "all"
24
+ Requires-Dist: meshagent-computers~=0.35.3; extra == "all"
25
+ Requires-Dist: meshagent-openai~=0.35.3; extra == "all"
26
+ Requires-Dist: meshagent-anthropic~=0.35.3; extra == "all"
27
+ Requires-Dist: meshagent-codex~=0.35.3; extra == "all"
28
+ Requires-Dist: meshagent-mcp~=0.35.3; extra == "all"
29
+ Requires-Dist: meshagent-tools~=0.35.3; extra == "all"
30
30
  Requires-Dist: supabase-auth~=2.28.0; extra == "all"
31
31
  Requires-Dist: prompt-toolkit~=3.0.52; extra == "all"
32
32
  Provides-Extra: mcp-service
33
- Requires-Dist: meshagent-agents[all]~=0.34.0; extra == "mcp-service"
34
- Requires-Dist: meshagent-api~=0.34.0; extra == "mcp-service"
35
- Requires-Dist: meshagent-mcp~=0.34.0; extra == "mcp-service"
36
- Requires-Dist: meshagent-tools~=0.34.0; extra == "mcp-service"
33
+ Requires-Dist: meshagent-agents[all]~=0.35.3; extra == "mcp-service"
34
+ Requires-Dist: meshagent-api~=0.35.3; extra == "mcp-service"
35
+ Requires-Dist: meshagent-mcp~=0.35.3; extra == "mcp-service"
36
+ Requires-Dist: meshagent-tools~=0.35.3; extra == "mcp-service"
37
37
  Requires-Dist: supabase-auth~=2.28.0; extra == "mcp-service"
38
38
 
39
39
  # [Meshagent](https://www.meshagent.com)
@@ -29,6 +29,7 @@ from meshagent.cli import helpers
29
29
  from meshagent.cli import meeting_transcriber
30
30
  from meshagent.cli import rooms
31
31
  from meshagent.cli import room
32
+ from meshagent.cli import image
32
33
  from meshagent.cli import port
33
34
  from meshagent.cli import webserver
34
35
  from meshagent.cli import codex
@@ -99,6 +100,7 @@ app.add_typer(task_runner.app, name="task-runner")
99
100
  app.add_typer(worker.app, name="worker")
100
101
 
101
102
  app.add_typer(room.app, name="room")
103
+ app.add_typer(image.app, name="image")
102
104
 
103
105
 
104
106
  def _run_async(coro):
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
  import asyncio
5
5
  import io
6
6
  import os
7
+ import re
7
8
  import tarfile
8
9
  import time
9
10
  from pathlib import Path
@@ -45,6 +46,9 @@ import sys
45
46
 
46
47
  app = async_typer.AsyncTyper(help="Manage containers and images inside a room")
47
48
  _LOG_STREAM_SETTLE_TIMEOUT_SECONDS = 1.0
49
+ _CRI_LOG_LINE_PATTERN = re.compile(
50
+ r"^(?P<timestamp>\S+)\s+(?P<stream>stdout|stderr)\s+(?P<flags>[FP])\s(?P<message>.*)$"
51
+ )
48
52
 
49
53
  # -------------------------
50
54
  # Helpers
@@ -189,6 +193,24 @@ async def _stream_container_job_logs_and_wait_for_exit(
189
193
  return exit_code
190
194
 
191
195
 
196
+ async def _stream_build_job_logs_and_wait_for_exit(
197
+ *,
198
+ client: RoomClient,
199
+ build_id: str,
200
+ ) -> int:
201
+ stream = client.containers.get_build_logs(build_id=build_id, follow=True)
202
+ try:
203
+ exit_code = await _drain_stream_plain(stream, show_progress=False)
204
+ except Exception:
205
+ await asyncio.gather(stream.cancel(), return_exceptions=True)
206
+ raise
207
+
208
+ if exit_code is None:
209
+ raise RuntimeError("build log stream closed before an exit code was returned")
210
+
211
+ return exit_code
212
+
213
+
192
214
  class DockerIgnore:
193
215
  def __init__(self, dockerignore_path: str):
194
216
  """
@@ -253,11 +275,27 @@ async def _make_targz_from_dir(path: Path) -> bytes:
253
275
 
254
276
 
255
277
  async def _drain_stream_plain(stream, *, show_progress: bool = True):
278
+ def _format_line(line: str) -> str:
279
+ has_newline = line.endswith("\n")
280
+ line_without_newline = line[:-1] if has_newline else line
281
+ match = _CRI_LOG_LINE_PATTERN.match(line_without_newline)
282
+ if match is None:
283
+ return line
284
+
285
+ message = match.group("message")
286
+ if has_newline:
287
+ return f"{message}\n"
288
+ return message
289
+
256
290
  async def _logs():
257
291
  async for line in stream.logs():
258
- # Server emits plain lines; print as-is
259
292
  if line is not None:
260
- sys.stdout.write(line if line.endswith("\n") else f"{line}\n")
293
+ formatted_line = _format_line(line)
294
+ sys.stdout.write(
295
+ formatted_line
296
+ if formatted_line.endswith("\n")
297
+ else f"{formatted_line}\n"
298
+ )
261
299
  sys.stdout.flush()
262
300
 
263
301
  async def _prog():
@@ -778,108 +816,6 @@ app.add_typer(images_app, name="image")
778
816
  app.add_typer(images_app, name="images", hidden=True)
779
817
 
780
818
 
781
- @images_app.async_command("build", help="Build a container image inside a room.")
782
- async def build_container(
783
- *,
784
- project_id: ProjectIdOption,
785
- room: RoomOption,
786
- tag: Annotated[
787
- str, typer.Option(..., help="Image tag to build, e.g. repo/name:tag")
788
- ],
789
- context_path: Annotated[
790
- str,
791
- typer.Option(
792
- ...,
793
- "--context-path",
794
- help="Build context path inside one of the mounted paths (absolute path)",
795
- ),
796
- ],
797
- dockerfile_path: Annotated[
798
- Optional[str],
799
- typer.Option(
800
- "--dockerfile-path",
801
- help="Optional Dockerfile path inside one of the mounted paths (absolute path)",
802
- ),
803
- ] = None,
804
- mount_room_path: Annotated[
805
- List[str],
806
- typer.Option(
807
- "--mount-room-path",
808
- help=(
809
- "Room storage mount '<source>:<mount>[:ro|rw]'. "
810
- "Example '/src:/workspace'"
811
- ),
812
- ),
813
- ] = [],
814
- mount_project_path: Annotated[
815
- List[str],
816
- typer.Option(
817
- "--mount-project-path",
818
- help=(
819
- "Project storage mount '<source>:<mount>[:ro|rw]'. "
820
- "Example '/shared:/project:ro'"
821
- ),
822
- ),
823
- ] = [],
824
- mount_image: Annotated[
825
- List[str],
826
- typer.Option(
827
- "--mount-image",
828
- help=(
829
- "Image mount '<image>=<mount>[:ro|rw]'. "
830
- "Example 'alpine:latest=/toolchain:ro'"
831
- ),
832
- ),
833
- ] = [],
834
- private: Annotated[
835
- bool,
836
- typer.Option(
837
- "--private/--public",
838
- help="Whether the build container is private to the participant",
839
- ),
840
- ] = False,
841
- cred: Annotated[
842
- List[str],
843
- typer.Option(
844
- "--cred",
845
- help="Docker creds (username,password) or (registry,username,password)",
846
- ),
847
- ] = [],
848
- ):
849
- mount_spec = _parse_image_operation_mounts(
850
- mount_room_path=mount_room_path,
851
- mount_project_path=mount_project_path,
852
- mount_image=mount_image,
853
- )
854
-
855
- if not context_path.startswith("/"):
856
- raise typer.BadParameter("--context-path must be an absolute path")
857
- if dockerfile_path is not None and not dockerfile_path.startswith("/"):
858
- raise typer.BadParameter("--dockerfile-path must be an absolute path")
859
-
860
- account_client, client = await _with_client(
861
- project_id=project_id,
862
- room=room,
863
- )
864
- try:
865
- container_id = await client.containers.build(
866
- tag=tag,
867
- mounts=[mount_spec],
868
- context_path=context_path,
869
- dockerfile_path=dockerfile_path,
870
- private=private,
871
- credentials=_parse_creds(cred),
872
- )
873
- exit_code = await _stream_container_job_logs_and_wait_for_exit(
874
- client=client, container_id=container_id
875
- )
876
- if exit_code != 0:
877
- raise typer.Exit(code=exit_code)
878
- finally:
879
- await client.__aexit__(None, None, None)
880
- await account_client.close()
881
-
882
-
883
819
  @images_app.async_command("list", help="List container images available in a room.")
884
820
  async def images_list(
885
821
  *,
@@ -56,6 +56,21 @@ class _FakeBuildStream:
56
56
  return _done().__await__()
57
57
 
58
58
 
59
+ class _FakeBuildContainers:
60
+ def __init__(self, *, stream: _FakeBuildStream) -> None:
61
+ self._stream = stream
62
+ self.build_log_calls: list[tuple[str, bool]] = []
63
+
64
+ def get_build_logs(self, *, build_id: str, follow: bool = True) -> _FakeBuildStream:
65
+ self.build_log_calls.append((build_id, follow))
66
+ return self._stream
67
+
68
+
69
+ class _FakeBuildClient:
70
+ def __init__(self, *, stream: _FakeBuildStream) -> None:
71
+ self.containers = _FakeBuildContainers(stream=stream)
72
+
73
+
59
74
  @pytest.mark.asyncio
60
75
  async def test_stream_container_job_logs_and_wait_for_exit_cancels_follow_stream(
61
76
  monkeypatch: pytest.MonkeyPatch,
@@ -123,3 +138,43 @@ async def test_drain_stream_plain_does_not_double_space_newline_terminated_logs(
123
138
 
124
139
  assert result == "image-1"
125
140
  assert capsys.readouterr().out == "step 1\nstep 2\n"
141
+
142
+
143
+ @pytest.mark.asyncio
144
+ async def test_drain_stream_plain_strips_cri_log_prefixes(
145
+ capsys: pytest.CaptureFixture[str],
146
+ ) -> None:
147
+ stream = _FakeBuildStream(
148
+ lines=[
149
+ "2026-03-30T04:21:56.562896627Z stderr F step 1\n",
150
+ "2026-03-30T04:21:57.000000000Z stdout F step 2\n",
151
+ ]
152
+ )
153
+
154
+ result = await containers._drain_stream_plain(stream, show_progress=False)
155
+
156
+ assert result == "image-1"
157
+ assert capsys.readouterr().out == "step 1\nstep 2\n"
158
+
159
+
160
+ @pytest.mark.asyncio
161
+ async def test_stream_build_job_logs_and_wait_for_exit_uses_build_log_stream(
162
+ monkeypatch: pytest.MonkeyPatch,
163
+ ) -> None:
164
+ stream = _FakeBuildStream(lines=["line 1\n"], result=0)
165
+ client = _FakeBuildClient(stream=stream)
166
+
167
+ async def _fake_drain(log_stream, *, show_progress: bool):
168
+ assert log_stream is stream
169
+ assert show_progress is False
170
+ return await log_stream
171
+
172
+ monkeypatch.setattr(containers, "_drain_stream_plain", _fake_drain)
173
+
174
+ exit_code = await containers._stream_build_job_logs_and_wait_for_exit(
175
+ client=client,
176
+ build_id="build-1",
177
+ )
178
+
179
+ assert exit_code == 0
180
+ assert client.containers.build_log_calls == [("build-1", True)]