axio-tools-docker 0.7.0__tar.gz → 0.8.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 (28) hide show
  1. axio_tools_docker-0.8.0/PKG-INFO +194 -0
  2. axio_tools_docker-0.8.0/README.md +180 -0
  3. {axio_tools_docker-0.7.0 → axio_tools_docker-0.8.0}/pyproject.toml +3 -7
  4. axio_tools_docker-0.8.0/src/axio_tools_docker/__init__.py +5 -0
  5. axio_tools_docker-0.8.0/src/axio_tools_docker/py.typed +0 -0
  6. axio_tools_docker-0.8.0/src/axio_tools_docker/sandbox.py +549 -0
  7. axio_tools_docker-0.8.0/tests/test_integration.py +423 -0
  8. axio_tools_docker-0.8.0/tests/test_sandbox.py +911 -0
  9. axio_tools_docker-0.7.0/PKG-INFO +0 -142
  10. axio_tools_docker-0.7.0/README.md +0 -129
  11. axio_tools_docker-0.7.0/src/axio_tools_docker/__init__.py +0 -7
  12. axio_tools_docker-0.7.0/src/axio_tools_docker/config.py +0 -46
  13. axio_tools_docker-0.7.0/src/axio_tools_docker/handler.py +0 -73
  14. axio_tools_docker-0.7.0/src/axio_tools_docker/manager.py +0 -158
  15. axio_tools_docker-0.7.0/src/axio_tools_docker/plugin.py +0 -87
  16. axio_tools_docker-0.7.0/src/axio_tools_docker/settings.py +0 -5
  17. axio_tools_docker-0.7.0/src/axio_tools_docker/tui/__init__.py +0 -15
  18. axio_tools_docker-0.7.0/src/axio_tools_docker/tui/docker.py +0 -152
  19. axio_tools_docker-0.7.0/tests/test_config.py +0 -54
  20. axio_tools_docker-0.7.0/tests/test_handler.py +0 -100
  21. axio_tools_docker-0.7.0/tests/test_manager.py +0 -258
  22. axio_tools_docker-0.7.0/tests/test_plugin.py +0 -85
  23. {axio_tools_docker-0.7.0 → axio_tools_docker-0.8.0}/.github/workflows/publish.yml +0 -0
  24. {axio_tools_docker-0.7.0 → axio_tools_docker-0.8.0}/.github/workflows/tests.yml +0 -0
  25. {axio_tools_docker-0.7.0 → axio_tools_docker-0.8.0}/.gitignore +0 -0
  26. {axio_tools_docker-0.7.0 → axio_tools_docker-0.8.0}/LICENSE +0 -0
  27. {axio_tools_docker-0.7.0 → axio_tools_docker-0.8.0}/Makefile +0 -0
  28. {axio_tools_docker-0.7.0 → axio_tools_docker-0.8.0}/uv.lock +0 -0
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: axio-tools-docker
3
+ Version: 0.8.0
4
+ Summary: Docker sandbox tools for Axio
5
+ Project-URL: Homepage, https://github.com/mosquito/axio-agent
6
+ Project-URL: Repository, https://github.com/mosquito/axio-agent
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: agent,ai,docker,llm,sandbox,tools
10
+ Requires-Python: >=3.12
11
+ Requires-Dist: aiodocker>=0.26
12
+ Requires-Dist: axio
13
+ Description-Content-Type: text/markdown
14
+
15
+ # axio-tools-docker
16
+
17
+ [![PyPI](https://img.shields.io/pypi/v/axio-tools-docker)](https://pypi.org/project/axio-tools-docker/)
18
+ [![Python](https://img.shields.io/pypi/pyversions/axio-tools-docker)](https://pypi.org/project/axio-tools-docker/)
19
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
20
+
21
+ Docker sandbox tools for [axio](https://github.com/mosquito/axio-agent).
22
+
23
+ `DockerSandbox` is an async context manager that spins up an isolated Docker
24
+ container on entry and removes it on exit. Inside the context it exposes six
25
+ `axio` tools — the same `shell`, `write_file`, `read_file`, `list_files`,
26
+ `run_python`, and `patch_file` as `axio-tools-local`, but every operation runs
27
+ inside the container, never on the host.
28
+
29
+ ## Requirements
30
+
31
+ Docker must be installed and running:
32
+
33
+ ```bash
34
+ docker info # should succeed
35
+ ```
36
+
37
+ The package talks to the Docker Engine API directly via
38
+ [aiodocker](https://aiodocker.readthedocs.io/) — the `docker` CLI is not
39
+ required.
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install axio-tools-docker
45
+ ```
46
+
47
+ ## Quick start
48
+
49
+ <!-- name: test_readme_usage; mark: docker -->
50
+ ```python
51
+ import asyncio
52
+ from axio.agent import Agent
53
+ from axio.context import MemoryContextStore
54
+ from axio.testing import StubTransport, make_text_response
55
+ from axio_tools_docker import DockerSandbox
56
+
57
+ async def main() -> None:
58
+ transport = StubTransport([make_text_response("Done.")])
59
+ async with DockerSandbox(image="python:3.12-alpine") as sandbox:
60
+ agent = Agent(
61
+ system="You are a coding assistant. Use the sandbox tools to run code safely.",
62
+ tools=sandbox.tools,
63
+ transport=transport,
64
+ )
65
+ ctx = MemoryContextStore()
66
+ result = await agent.run("Print hello from Python.", ctx)
67
+ print(result)
68
+
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ ## Sandbox tools
73
+
74
+ These mirror `axio-tools-local` exactly — same names and field schemas:
75
+
76
+ | Tool | Description |
77
+ |---|---|
78
+ | `shell` | Run a shell command; returns stdout + stderr. Supports `timeout`, `cwd`, `stdin`. |
79
+ | `write_file` | Create or overwrite a file. Parent directories are created automatically. |
80
+ | `read_file` | Read a file with optional `start_line`/`end_line`, `line_numbers`, `max_chars`. |
81
+ | `list_files` | List a directory; directories first with a trailing `/`. |
82
+ | `run_python` | Execute a Python snippet in a subprocess. Supports `timeout`, `cwd`, `stdin`. |
83
+ | `patch_file` | Replace lines `from_line`..`to_line` (1-indexed, inclusive). `to_line = from_line - 1` inserts. |
84
+
85
+ ## Container lifecycle
86
+
87
+ The container is created on `__aenter__` and removed on `__aexit__` (`docker rm -f`).
88
+ Cleanup runs even when the body raises an exception:
89
+
90
+ ```python
91
+ async def run(ctx):
92
+ async with DockerSandbox(image="alpine:latest") as sandbox:
93
+ await agent.run("...", ctx)
94
+ # container removed here (unless remove=False)
95
+ ```
96
+
97
+ The image is pulled automatically if not present locally. If the Docker daemon
98
+ is unreachable, `__aenter__` raises immediately:
99
+
100
+ ```
101
+ RuntimeError: Docker daemon not available at 'unix:///var/run/docker.sock': ...
102
+ ```
103
+
104
+ The running container's ID is available as `sandbox.container_id` inside the
105
+ `async with` block.
106
+
107
+ ## Named containers and reuse
108
+
109
+ Pass `name=` to give the container a fixed name. If a running container with
110
+ that name already exists, the sandbox attaches to it instead of creating a new
111
+ one, and never removes it on exit — regardless of `remove`:
112
+
113
+ ```python
114
+ import asyncio
115
+ from axio_tools_docker import DockerSandbox
116
+
117
+ async def first_session() -> None:
118
+ async with DockerSandbox(image="python:3.12-slim", name="my-sandbox", remove=False) as sb:
119
+ await sb.exec("pip install requests")
120
+
121
+ async def second_session() -> None:
122
+ async with DockerSandbox(name="my-sandbox") as sb:
123
+ result = await sb.exec("python3 -c 'import requests; print(requests.__version__)'")
124
+
125
+ asyncio.run(first_session())
126
+ asyncio.run(second_session())
127
+ ```
128
+
129
+ ## Configuration
130
+
131
+ <!-- name: test_readme_config -->
132
+ ```python
133
+ from axio_tools_docker import DockerSandbox
134
+
135
+ sandbox = DockerSandbox(
136
+ "unix:///var/run/docker.sock", # Docker daemon URL (positional)
137
+ image="python:3.12-slim",
138
+ memory="512m", # memory limit: "256m", "1g", …
139
+ cpus="2.0", # CPU limit
140
+ network=False, # False=none, True=default, str=explicit mode
141
+ workdir="/workspace",
142
+ volumes={"/workspace": "/tmp/host-dir"}, # {container_path: host_path}
143
+ env={"PYTHONPATH": "/app"},
144
+ user="nobody",
145
+ name="my-agent-sandbox",
146
+ remove=False,
147
+ read_only=True, # read-only root filesystem
148
+ shm_size="64m", # /dev/shm size
149
+ cap_add=["NET_ADMIN"], # add Linux capabilities
150
+ cap_drop=["ALL"], # drop Linux capabilities
151
+ privileged=False,
152
+ ulimits={"nofile": (1024, 65536), "nproc": 512},
153
+ tmpfs={"/tmp": "size=128m,mode=1777"},
154
+ ports={8080: 8080}, # {container_port: host_port}
155
+ platform="linux/amd64",
156
+ extra_hosts={"host.docker.internal": "host-gateway"},
157
+ devices=["/dev/net/tun", "/dev/sda:/dev/xvda:r"],
158
+ dns=["8.8.8.8", "1.1.1.1"],
159
+ )
160
+ ```
161
+
162
+ | Parameter | Type | Default | Description |
163
+ |-----------|------|---------|-------------|
164
+ | `url` | `str` | `"unix:///var/run/docker.sock"` | Docker daemon URL (unix socket or TCP). Positional. |
165
+ | `image` | `str` | `"python:latest"` | Container image. Pulled automatically if not present. |
166
+ | `memory` | `str` | `"256m"` | Memory limit. Accepts `k`/`m`/`g` suffixes. |
167
+ | `cpus` | `str` | `"1.0"` | CPU limit as a decimal string. |
168
+ | `network` | `bool \| str` | `False` | Network mode. `False` → `none`. `True` → Docker default. String → explicit `NetworkMode` (e.g. `"host"`, `"bridge"`, `"my-net"`). |
169
+ | `workdir` | `str` | `"/workspace"` | Working directory inside the container. |
170
+ | `volumes` | `dict[str, str]` | `{}` | Bind mounts as `{container_path: host_path}`. |
171
+ | `env` | `dict[str, str]` | `{}` | Environment variables passed to all commands. |
172
+ | `user` | `str` | `""` | User to run as (e.g. `"nobody"`, `"1000"`). |
173
+ | `name` | `str` | `""` | Container name. Attaches to existing container if found; creates new one otherwise. |
174
+ | `remove` | `bool` | `True` | Remove container on exit. No effect when attached to an existing container. |
175
+ | `read_only` | `bool` | `False` | Read-only root filesystem. Combine with `tmpfs` for writable scratch paths. |
176
+ | `shm_size` | `str` | `""` | `/dev/shm` size (e.g. `"64m"`). Useful for PyTorch / shared-memory IPC. |
177
+ | `cap_add` | `list[str]` | `[]` | Linux capabilities to add (e.g. `["NET_ADMIN", "SYS_PTRACE"]`). |
178
+ | `cap_drop` | `list[str]` | `[]` | Linux capabilities to drop (e.g. `["ALL"]`). |
179
+ | `privileged` | `bool` | `False` | Extended privileges — full capability set and device access. Use with care. |
180
+ | `ulimits` | `dict[str, int \| tuple[int, int]]` | `{}` | Resource limits. `{"nofile": 1024}` → soft=hard=1024. `{"nofile": (1024, 65536)}` → soft/hard split. |
181
+ | `tmpfs` | `dict[str, str]` | `{}` | Tmpfs mounts as `{path: options}` (e.g. `{"/tmp": "size=128m,mode=1777"}`). |
182
+ | `ports` | `dict[int, int]` | `{}` | Port bindings as `{container_port: host_port}`. Only meaningful when `network != False`. |
183
+ | `platform` | `str` | `""` | Platform override (e.g. `"linux/amd64"`, `"linux/arm64"`). |
184
+ | `extra_hosts` | `dict[str, str]` | `{}` | Extra `/etc/hosts` entries as `{hostname: ip}`. |
185
+ | `devices` | `list[str]` | `[]` | Host devices to expose. Format: `"/dev/sda"`, `"/dev/sda:/dev/xvda"`, `"/dev/sda:/dev/xvda:r"`. |
186
+ | `dns` | `list[str]` | `[]` | DNS servers (e.g. `["8.8.8.8"]`). Only meaningful when `network != False`. |
187
+
188
+ ## Part of the axio ecosystem
189
+
190
+ [axio](https://github.com/mosquito/axio-agent) · [axio-tools-local](https://github.com/mosquito/axio-agent) · [axio-tools-mcp](https://github.com/mosquito/axio-agent) · [axio-tui](https://github.com/mosquito/axio-agent)
191
+
192
+ ## License
193
+
194
+ MIT
@@ -0,0 +1,180 @@
1
+ # axio-tools-docker
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/axio-tools-docker)](https://pypi.org/project/axio-tools-docker/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/axio-tools-docker)](https://pypi.org/project/axio-tools-docker/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ Docker sandbox tools for [axio](https://github.com/mosquito/axio-agent).
8
+
9
+ `DockerSandbox` is an async context manager that spins up an isolated Docker
10
+ container on entry and removes it on exit. Inside the context it exposes six
11
+ `axio` tools — the same `shell`, `write_file`, `read_file`, `list_files`,
12
+ `run_python`, and `patch_file` as `axio-tools-local`, but every operation runs
13
+ inside the container, never on the host.
14
+
15
+ ## Requirements
16
+
17
+ Docker must be installed and running:
18
+
19
+ ```bash
20
+ docker info # should succeed
21
+ ```
22
+
23
+ The package talks to the Docker Engine API directly via
24
+ [aiodocker](https://aiodocker.readthedocs.io/) — the `docker` CLI is not
25
+ required.
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ pip install axio-tools-docker
31
+ ```
32
+
33
+ ## Quick start
34
+
35
+ <!-- name: test_readme_usage; mark: docker -->
36
+ ```python
37
+ import asyncio
38
+ from axio.agent import Agent
39
+ from axio.context import MemoryContextStore
40
+ from axio.testing import StubTransport, make_text_response
41
+ from axio_tools_docker import DockerSandbox
42
+
43
+ async def main() -> None:
44
+ transport = StubTransport([make_text_response("Done.")])
45
+ async with DockerSandbox(image="python:3.12-alpine") as sandbox:
46
+ agent = Agent(
47
+ system="You are a coding assistant. Use the sandbox tools to run code safely.",
48
+ tools=sandbox.tools,
49
+ transport=transport,
50
+ )
51
+ ctx = MemoryContextStore()
52
+ result = await agent.run("Print hello from Python.", ctx)
53
+ print(result)
54
+
55
+ asyncio.run(main())
56
+ ```
57
+
58
+ ## Sandbox tools
59
+
60
+ These mirror `axio-tools-local` exactly — same names and field schemas:
61
+
62
+ | Tool | Description |
63
+ |---|---|
64
+ | `shell` | Run a shell command; returns stdout + stderr. Supports `timeout`, `cwd`, `stdin`. |
65
+ | `write_file` | Create or overwrite a file. Parent directories are created automatically. |
66
+ | `read_file` | Read a file with optional `start_line`/`end_line`, `line_numbers`, `max_chars`. |
67
+ | `list_files` | List a directory; directories first with a trailing `/`. |
68
+ | `run_python` | Execute a Python snippet in a subprocess. Supports `timeout`, `cwd`, `stdin`. |
69
+ | `patch_file` | Replace lines `from_line`..`to_line` (1-indexed, inclusive). `to_line = from_line - 1` inserts. |
70
+
71
+ ## Container lifecycle
72
+
73
+ The container is created on `__aenter__` and removed on `__aexit__` (`docker rm -f`).
74
+ Cleanup runs even when the body raises an exception:
75
+
76
+ ```python
77
+ async def run(ctx):
78
+ async with DockerSandbox(image="alpine:latest") as sandbox:
79
+ await agent.run("...", ctx)
80
+ # container removed here (unless remove=False)
81
+ ```
82
+
83
+ The image is pulled automatically if not present locally. If the Docker daemon
84
+ is unreachable, `__aenter__` raises immediately:
85
+
86
+ ```
87
+ RuntimeError: Docker daemon not available at 'unix:///var/run/docker.sock': ...
88
+ ```
89
+
90
+ The running container's ID is available as `sandbox.container_id` inside the
91
+ `async with` block.
92
+
93
+ ## Named containers and reuse
94
+
95
+ Pass `name=` to give the container a fixed name. If a running container with
96
+ that name already exists, the sandbox attaches to it instead of creating a new
97
+ one, and never removes it on exit — regardless of `remove`:
98
+
99
+ ```python
100
+ import asyncio
101
+ from axio_tools_docker import DockerSandbox
102
+
103
+ async def first_session() -> None:
104
+ async with DockerSandbox(image="python:3.12-slim", name="my-sandbox", remove=False) as sb:
105
+ await sb.exec("pip install requests")
106
+
107
+ async def second_session() -> None:
108
+ async with DockerSandbox(name="my-sandbox") as sb:
109
+ result = await sb.exec("python3 -c 'import requests; print(requests.__version__)'")
110
+
111
+ asyncio.run(first_session())
112
+ asyncio.run(second_session())
113
+ ```
114
+
115
+ ## Configuration
116
+
117
+ <!-- name: test_readme_config -->
118
+ ```python
119
+ from axio_tools_docker import DockerSandbox
120
+
121
+ sandbox = DockerSandbox(
122
+ "unix:///var/run/docker.sock", # Docker daemon URL (positional)
123
+ image="python:3.12-slim",
124
+ memory="512m", # memory limit: "256m", "1g", …
125
+ cpus="2.0", # CPU limit
126
+ network=False, # False=none, True=default, str=explicit mode
127
+ workdir="/workspace",
128
+ volumes={"/workspace": "/tmp/host-dir"}, # {container_path: host_path}
129
+ env={"PYTHONPATH": "/app"},
130
+ user="nobody",
131
+ name="my-agent-sandbox",
132
+ remove=False,
133
+ read_only=True, # read-only root filesystem
134
+ shm_size="64m", # /dev/shm size
135
+ cap_add=["NET_ADMIN"], # add Linux capabilities
136
+ cap_drop=["ALL"], # drop Linux capabilities
137
+ privileged=False,
138
+ ulimits={"nofile": (1024, 65536), "nproc": 512},
139
+ tmpfs={"/tmp": "size=128m,mode=1777"},
140
+ ports={8080: 8080}, # {container_port: host_port}
141
+ platform="linux/amd64",
142
+ extra_hosts={"host.docker.internal": "host-gateway"},
143
+ devices=["/dev/net/tun", "/dev/sda:/dev/xvda:r"],
144
+ dns=["8.8.8.8", "1.1.1.1"],
145
+ )
146
+ ```
147
+
148
+ | Parameter | Type | Default | Description |
149
+ |-----------|------|---------|-------------|
150
+ | `url` | `str` | `"unix:///var/run/docker.sock"` | Docker daemon URL (unix socket or TCP). Positional. |
151
+ | `image` | `str` | `"python:latest"` | Container image. Pulled automatically if not present. |
152
+ | `memory` | `str` | `"256m"` | Memory limit. Accepts `k`/`m`/`g` suffixes. |
153
+ | `cpus` | `str` | `"1.0"` | CPU limit as a decimal string. |
154
+ | `network` | `bool \| str` | `False` | Network mode. `False` → `none`. `True` → Docker default. String → explicit `NetworkMode` (e.g. `"host"`, `"bridge"`, `"my-net"`). |
155
+ | `workdir` | `str` | `"/workspace"` | Working directory inside the container. |
156
+ | `volumes` | `dict[str, str]` | `{}` | Bind mounts as `{container_path: host_path}`. |
157
+ | `env` | `dict[str, str]` | `{}` | Environment variables passed to all commands. |
158
+ | `user` | `str` | `""` | User to run as (e.g. `"nobody"`, `"1000"`). |
159
+ | `name` | `str` | `""` | Container name. Attaches to existing container if found; creates new one otherwise. |
160
+ | `remove` | `bool` | `True` | Remove container on exit. No effect when attached to an existing container. |
161
+ | `read_only` | `bool` | `False` | Read-only root filesystem. Combine with `tmpfs` for writable scratch paths. |
162
+ | `shm_size` | `str` | `""` | `/dev/shm` size (e.g. `"64m"`). Useful for PyTorch / shared-memory IPC. |
163
+ | `cap_add` | `list[str]` | `[]` | Linux capabilities to add (e.g. `["NET_ADMIN", "SYS_PTRACE"]`). |
164
+ | `cap_drop` | `list[str]` | `[]` | Linux capabilities to drop (e.g. `["ALL"]`). |
165
+ | `privileged` | `bool` | `False` | Extended privileges — full capability set and device access. Use with care. |
166
+ | `ulimits` | `dict[str, int \| tuple[int, int]]` | `{}` | Resource limits. `{"nofile": 1024}` → soft=hard=1024. `{"nofile": (1024, 65536)}` → soft/hard split. |
167
+ | `tmpfs` | `dict[str, str]` | `{}` | Tmpfs mounts as `{path: options}` (e.g. `{"/tmp": "size=128m,mode=1777"}`). |
168
+ | `ports` | `dict[int, int]` | `{}` | Port bindings as `{container_port: host_port}`. Only meaningful when `network != False`. |
169
+ | `platform` | `str` | `""` | Platform override (e.g. `"linux/amd64"`, `"linux/arm64"`). |
170
+ | `extra_hosts` | `dict[str, str]` | `{}` | Extra `/etc/hosts` entries as `{hostname: ip}`. |
171
+ | `devices` | `list[str]` | `[]` | Host devices to expose. Format: `"/dev/sda"`, `"/dev/sda:/dev/xvda"`, `"/dev/sda:/dev/xvda:r"`. |
172
+ | `dns` | `list[str]` | `[]` | DNS servers (e.g. `["8.8.8.8"]`). Only meaningful when `network != False`. |
173
+
174
+ ## Part of the axio ecosystem
175
+
176
+ [axio](https://github.com/mosquito/axio-agent) · [axio-tools-local](https://github.com/mosquito/axio-agent) · [axio-tools-mcp](https://github.com/mosquito/axio-agent) · [axio-tui](https://github.com/mosquito/axio-agent)
177
+
178
+ ## License
179
+
180
+ MIT
@@ -1,16 +1,12 @@
1
1
  [project]
2
2
  name = "axio-tools-docker"
3
- version = "0.7.0"
3
+ version = "0.8.0"
4
4
  description = "Docker sandbox tools for Axio"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
7
7
  license = {text = "MIT"}
8
8
  keywords = ["llm", "agent", "ai", "docker", "sandbox", "tools"]
9
- dependencies = ["axio"]
10
-
11
- [project.entry-points."axio.tools.settings"]
12
- docker = "axio_tools_docker.plugin:DockerPlugin"
13
-
9
+ dependencies = ["axio", "aiodocker>=0.26"]
14
10
 
15
11
  [project.urls]
16
12
  Homepage = "https://github.com/mosquito/axio-agent"
@@ -45,6 +41,6 @@ dev = [
45
41
  "ruff>=0.9",
46
42
  "pytest-cov>=7.1.0",
47
43
  "markdown-pytest>=0.6.0",
48
- "textual>=2.1.0",
44
+ "aiodocker>=0.26",
49
45
  ]
50
46
 
@@ -0,0 +1,5 @@
1
+ """Docker sandbox tools for Axio."""
2
+
3
+ from .sandbox import DockerSandbox
4
+
5
+ __all__ = ["DockerSandbox"]
File without changes