agentbox-sandbox 0.4.0__py3-none-any.whl

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.
agentbox/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Self-hosted code execution sandbox for AI agents."""
2
+
3
+ __version__ = "0.4.0"
File without changes
agentbox/api/app.py ADDED
@@ -0,0 +1,85 @@
1
+ """FastAPI HTTP API."""
2
+
3
+ import subprocess
4
+
5
+ from fastapi import FastAPI, HTTPException
6
+ from pydantic import BaseModel, Field
7
+
8
+ from agentbox import __version__
9
+ from agentbox.config import Settings
10
+ from agentbox.sandbox.runner import SubprocessSandbox
11
+
12
+ settings = Settings()
13
+ sandbox = SubprocessSandbox(
14
+ timeout_seconds=settings.default_timeout_seconds,
15
+ max_output_bytes=settings.max_output_bytes,
16
+ deny_egress=settings.sandbox_backend != "unrestricted",
17
+ snapshot_dir=settings.snapshot_dir,
18
+ default_memory_mb=settings.default_memory_mb,
19
+ )
20
+
21
+ app = FastAPI(title="agentbox", version=__version__)
22
+
23
+
24
+ class ResourceLimits(BaseModel):
25
+ timeout_seconds: int | None = Field(default=None, ge=1, le=300)
26
+ max_output_bytes: int | None = Field(default=None, ge=1024)
27
+ memory_mb: int | None = Field(default=None, ge=16, le=8192)
28
+
29
+
30
+ class RunRequest(BaseModel):
31
+ code: str = Field(..., min_length=1, max_length=100_000)
32
+ language: str = "python"
33
+ limits: ResourceLimits | None = None
34
+ snapshot: bool = False
35
+ snapshot_id: str | None = None
36
+
37
+
38
+ class RunResponse(BaseModel):
39
+ stdout: str
40
+ stderr: str
41
+ exit_code: int
42
+ duration_ms: int
43
+ language: str
44
+ backend: str
45
+ snapshot_id: str | None = None
46
+
47
+
48
+ @app.get("/health")
49
+ def health() -> dict[str, str]:
50
+ return {"status": "ok", "version": __version__, "backend": settings.sandbox_backend}
51
+
52
+
53
+ @app.post("/v1/run", response_model=RunResponse)
54
+ def run_code(req: RunRequest) -> RunResponse:
55
+ language = req.language.lower()
56
+ if language not in {"python", "javascript", "node"}:
57
+ raise HTTPException(status_code=400, detail=f"Unsupported language: {req.language}")
58
+ timeout = (
59
+ req.limits.timeout_seconds
60
+ if req.limits and req.limits.timeout_seconds
61
+ else settings.default_timeout_seconds
62
+ )
63
+ memory_mb = req.limits.memory_mb if req.limits else None
64
+ try:
65
+ result = sandbox.run(
66
+ req.code,
67
+ language=language,
68
+ timeout_seconds=timeout,
69
+ snapshot_id=req.snapshot_id,
70
+ persist_snapshot=req.snapshot,
71
+ memory_mb=memory_mb,
72
+ )
73
+ except subprocess.TimeoutExpired as exc:
74
+ raise HTTPException(status_code=408, detail="Execution timed out") from exc
75
+ except ValueError as exc:
76
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
77
+ return RunResponse(
78
+ stdout=result.stdout,
79
+ stderr=result.stderr,
80
+ exit_code=result.exit_code,
81
+ duration_ms=result.duration_ms,
82
+ language=language,
83
+ backend=settings.sandbox_backend,
84
+ snapshot_id=result.snapshot_id,
85
+ )
agentbox/cli.py ADDED
@@ -0,0 +1,35 @@
1
+ """CLI entry point."""
2
+
3
+ import click
4
+ import uvicorn
5
+
6
+ from agentbox import __version__
7
+ from agentbox.config import Settings
8
+
9
+
10
+ @click.group()
11
+ @click.version_option(__version__)
12
+ def main() -> None:
13
+ """Self-hosted code execution sandbox for AI agents."""
14
+
15
+
16
+ @main.command("serve")
17
+ @click.option("--host", default=None)
18
+ @click.option("--port", default=None, type=int)
19
+ def serve(host: str | None, port: int | None) -> None:
20
+ settings = Settings()
21
+ uvicorn.run(
22
+ "agentbox.api.app:app",
23
+ host=host or settings.host,
24
+ port=port or settings.port,
25
+ reload=False,
26
+ )
27
+
28
+
29
+ @main.command("health")
30
+ def health_cmd() -> None:
31
+ click.echo(f"agentbox {__version__} OK")
32
+
33
+
34
+ if __name__ == "__main__":
35
+ main()
agentbox/config.py ADDED
@@ -0,0 +1,16 @@
1
+ """Configuration."""
2
+
3
+ from pydantic_settings import BaseSettings, SettingsConfigDict
4
+
5
+
6
+ class Settings(BaseSettings):
7
+ model_config = SettingsConfigDict(env_prefix="AGENTBOX_", env_file=".env", extra="ignore")
8
+
9
+ host: str = "0.0.0.0"
10
+ port: int = 8080
11
+ log_level: str = "INFO"
12
+ default_timeout_seconds: int = 30
13
+ max_output_bytes: int = 1_048_576
14
+ sandbox_backend: str = "subprocess" # subprocess | gvisor (future)
15
+ snapshot_dir: str = "/tmp/agentbox-snapshots"
16
+ default_memory_mb: int | None = None
File without changes
@@ -0,0 +1,134 @@
1
+ """Sandbox execution backends."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import resource
7
+ import shutil
8
+ import subprocess
9
+ import tempfile
10
+ import time
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+ from typing import Protocol
14
+
15
+ from agentbox.sandbox.snapshots import restore_snapshot, save_snapshot
16
+
17
+
18
+ @dataclass
19
+ class RunResult:
20
+ stdout: str
21
+ stderr: str
22
+ exit_code: int
23
+ duration_ms: int
24
+ snapshot_id: str | None = None
25
+
26
+
27
+ class Sandbox(Protocol):
28
+ def run(self, code: str, language: str, timeout_seconds: int | None = None) -> RunResult:
29
+ ...
30
+
31
+
32
+ class SubprocessSandbox:
33
+ """MVP sandbox: isolated temp directory + subprocess timeout + optional memory cap.
34
+
35
+ ponytail: not production isolation — upgrade path is gVisor/docker backend.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ timeout_seconds: int = 30,
41
+ max_output_bytes: int = 1_048_576,
42
+ deny_egress: bool = False,
43
+ snapshot_dir: str | Path | None = None,
44
+ default_memory_mb: int | None = None,
45
+ ) -> None:
46
+ self.timeout_seconds = timeout_seconds
47
+ self.max_output_bytes = max_output_bytes
48
+ self.deny_egress = deny_egress
49
+ self.snapshot_dir = Path(snapshot_dir) if snapshot_dir else Path("/tmp/agentbox-snapshots")
50
+ self.default_memory_mb = default_memory_mb
51
+
52
+ def run(
53
+ self,
54
+ code: str,
55
+ language: str = "python",
56
+ timeout_seconds: int | None = None,
57
+ snapshot_id: str | None = None,
58
+ persist_snapshot: bool = False,
59
+ memory_mb: int | None = None,
60
+ ) -> RunResult:
61
+ timeout = timeout_seconds or self.timeout_seconds
62
+ mem = memory_mb if memory_mb is not None else self.default_memory_mb
63
+ start = time.monotonic()
64
+ env = self._env()
65
+ with tempfile.TemporaryDirectory(prefix="agentbox-") as tmp:
66
+ workspace = Path(tmp)
67
+ if snapshot_id:
68
+ try:
69
+ restore_snapshot(snapshot_id, workspace, self.snapshot_dir)
70
+ except FileNotFoundError as exc:
71
+ raise ValueError(str(exc)) from exc
72
+ if language == "python":
73
+ script = workspace / "main.py"
74
+ script.write_text(code, encoding="utf-8")
75
+ argv = ["python3", str(script)]
76
+ elif language in {"javascript", "node"}:
77
+ script = workspace / "main.js"
78
+ script.write_text(code, encoding="utf-8")
79
+ node = shutil.which("node") or "node"
80
+ argv = [node, str(script)]
81
+ else:
82
+ raise ValueError(f"Unsupported language: {language}")
83
+ preexec = _memory_preexec(mem) if mem else None
84
+ try:
85
+ proc = subprocess.run(
86
+ argv,
87
+ capture_output=True,
88
+ text=True,
89
+ timeout=timeout,
90
+ cwd=tmp,
91
+ env=env,
92
+ check=False,
93
+ preexec_fn=preexec,
94
+ )
95
+ except FileNotFoundError as exc:
96
+ raise ValueError(f"Runtime not found: {exc}") from exc
97
+ new_snapshot = save_snapshot(workspace, self.snapshot_dir) if persist_snapshot else None
98
+ duration = int((time.monotonic() - start) * 1000)
99
+ return RunResult(
100
+ stdout=proc.stdout[: self.max_output_bytes],
101
+ stderr=proc.stderr[: self.max_output_bytes],
102
+ exit_code=proc.returncode,
103
+ duration_ms=duration,
104
+ snapshot_id=new_snapshot,
105
+ )
106
+
107
+ def run_python(self, code: str) -> RunResult:
108
+ return self.run(code, "python")
109
+
110
+ def _env(self) -> dict[str, str]:
111
+ env = os.environ.copy()
112
+ if not self.deny_egress:
113
+ return env
114
+ for key in list(env):
115
+ upper = key.upper()
116
+ if any(token in upper for token in ("TOKEN", "SECRET", "PASSWORD", "API_KEY")):
117
+ env.pop(key, None)
118
+ env.pop("AWS_SECRET_ACCESS_KEY", None)
119
+ env.pop("OPENAI_API_KEY", None)
120
+ return env
121
+
122
+
123
+ def _memory_preexec(memory_mb: int):
124
+ """Return a preexec_fn that caps address space for the child process."""
125
+
126
+ bytes_limit = max(1, memory_mb) * 1024 * 1024
127
+
128
+ def _set() -> None:
129
+ soft, hard = resource.getrlimit(resource.RLIMIT_AS)
130
+ # Keep within existing hard limit when the OS publishes one.
131
+ cap = bytes_limit if hard == resource.RLIM_INFINITY else min(bytes_limit, hard)
132
+ resource.setrlimit(resource.RLIMIT_AS, (cap, hard if hard != resource.RLIM_INFINITY else cap))
133
+
134
+ return _set
@@ -0,0 +1,30 @@
1
+ """Workspace snapshot save/restore for stateful agent runs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import tarfile
6
+ import uuid
7
+ from pathlib import Path
8
+
9
+
10
+ def save_snapshot(workspace: Path, snapshot_dir: Path) -> str:
11
+ snapshot_dir.mkdir(parents=True, exist_ok=True)
12
+ snapshot_id = uuid.uuid4().hex
13
+ archive = snapshot_dir / f"{snapshot_id}.tar.gz"
14
+ with tarfile.open(archive, "w:gz") as tar:
15
+ for path in workspace.rglob("*"):
16
+ if not path.is_file() or path.name in {"main.py", "main.js"}:
17
+ continue
18
+ tar.add(path, arcname=path.relative_to(workspace))
19
+ return snapshot_id
20
+
21
+
22
+ def restore_snapshot(snapshot_id: str, workspace: Path, snapshot_dir: Path) -> None:
23
+ archive = snapshot_dir / f"{snapshot_id}.tar.gz"
24
+ if not archive.exists():
25
+ raise FileNotFoundError(f"Unknown snapshot: {snapshot_id}")
26
+ with tarfile.open(archive, "r:gz") as tar:
27
+ try:
28
+ tar.extractall(workspace, filter="data")
29
+ except TypeError:
30
+ tar.extractall(workspace)
File without changes
agentbox/sdk/client.py ADDED
@@ -0,0 +1,57 @@
1
+ """Python SDK client."""
2
+
3
+ from typing import Any, cast
4
+
5
+ import httpx
6
+
7
+
8
+ class AgentboxClient:
9
+ def __init__(self, base_url: str = "http://localhost:8080") -> None:
10
+ self.base_url = base_url.rstrip("/")
11
+ self._client = httpx.Client(base_url=self.base_url, timeout=60.0)
12
+
13
+ def health(self) -> dict[str, str]:
14
+ resp = self._client.get("/health")
15
+ self._raise_for_status(resp)
16
+ return cast(dict[str, str], resp.json())
17
+
18
+ def run(
19
+ self,
20
+ code: str,
21
+ language: str = "python",
22
+ timeout_seconds: int | None = None,
23
+ snapshot: bool = False,
24
+ snapshot_id: str | None = None,
25
+ memory_mb: int | None = None,
26
+ ) -> dict[str, object]:
27
+ payload: dict[str, object] = {"code": code, "language": language}
28
+ limits: dict[str, object] = {}
29
+ if timeout_seconds is not None:
30
+ limits["timeout_seconds"] = timeout_seconds
31
+ if memory_mb is not None:
32
+ limits["memory_mb"] = memory_mb
33
+ if limits:
34
+ payload["limits"] = limits
35
+ if snapshot:
36
+ payload["snapshot"] = True
37
+ if snapshot_id:
38
+ payload["snapshot_id"] = snapshot_id
39
+ resp = self._client.post("/v1/run", json=payload)
40
+ self._raise_for_status(resp)
41
+ return cast(dict[str, object], resp.json())
42
+
43
+ def close(self) -> None:
44
+ self._client.close()
45
+
46
+ @staticmethod
47
+ def _raise_for_status(resp: httpx.Response) -> None:
48
+ if resp.status_code < 400:
49
+ return
50
+ detail: object = resp.text
51
+ try:
52
+ body: Any = resp.json()
53
+ except ValueError:
54
+ body = None
55
+ if isinstance(body, dict):
56
+ detail = body.get("detail", body)
57
+ raise RuntimeError(f"agentbox HTTP {resp.status_code}: {detail}")
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.5
2
+ Name: agentbox-sandbox
3
+ Version: 0.4.0
4
+ Summary: Self-hosted code execution sandbox for AI agents
5
+ Project-URL: Homepage, https://github.com/yashshah9/agentbox
6
+ Project-URL: Repository, https://github.com/yashshah9/agentbox
7
+ Project-URL: Issues, https://github.com/yashshah9/agentbox/issues
8
+ Author-email: Yash Shah <yash376351@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: fastapi>=0.111
13
+ Requires-Dist: pydantic-settings>=2.2
14
+ Requires-Dist: pydantic>=2.6
15
+ Requires-Dist: structlog>=24.1
16
+ Requires-Dist: uvicorn[standard]>=0.29
17
+ Provides-Extra: dev
18
+ Requires-Dist: httpx>=0.27; extra == 'dev'
19
+ Requires-Dist: mypy>=1.9; extra == 'dev'
20
+ Requires-Dist: pytest>=8.0; extra == 'dev'
21
+ Requires-Dist: ruff>=0.4; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # agentbox
25
+
26
+ Self-hosted **code execution sandbox** for AI agents — one `docker compose up` gives you an HTTP API for running untrusted code in isolated environments.
27
+
28
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
29
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
30
+ [![CI](https://github.com/yashshah9/agentbox/actions/workflows/ci.yml/badge.svg)](https://github.com/yashshah9/agentbox/actions/workflows/ci.yml)
31
+
32
+ > **Status:** v0.4 — Python + Node subprocess sandbox, timeouts, `limits.memory_mb` via `RLIMIT_AS`, TypeScript client, workspace snapshots.
33
+
34
+ ## 60-second try
35
+
36
+ ```bash
37
+ docker compose up agentbox # API on :8080
38
+ # in another shell:
39
+ curl -s http://localhost:8080/health
40
+ curl -s -X POST http://localhost:8080/v1/run \
41
+ -H 'Content-Type: application/json' \
42
+ -d '{"code":"print(sum(range(10)))"}'
43
+ docker compose run --rm test # pytest
44
+ ```
45
+
46
+ ## Why this vs alternatives
47
+
48
+ | Approach | Strength | Gap |
49
+ |----------|----------|-----|
50
+ | **agentbox** | Self-hosted HTTP API + SDK, one compose file | Subprocess isolation today, not gVisor |
51
+ | Hosted sandboxes (E2B, etc.) | Strong isolation, managed | Per-second cost; data leaves your network |
52
+ | Raw `docker exec` | Familiar | No agent-oriented API / snapshots / limits |
53
+ | YOLO in the agent process | Zero infra | Full host compromise risk |
54
+
55
+ ## Problem
56
+
57
+ Every agent that writes and runs code needs a safe execution environment. Teams either YOLO in shared containers or pay per-second for hosted sandboxes. Self-hosting gVisor/Firecracker is weeks of work.
58
+
59
+ ## Key features (v0.4)
60
+
61
+ - HTTP API: `POST /v1/run` executes Python or JavaScript
62
+ - Per-request `limits.timeout_seconds` (HTTP 408 on timeout)
63
+ - Per-request `limits.memory_mb` (sets `RLIMIT_AS` in the child process; 16–8192)
64
+ - Workspace snapshots: `"snapshot": true` then `"snapshot_id"`
65
+ - Python SDK + TypeScript client (`sdk/ts/client.ts`)
66
+ - Docker image includes Node.js for the JS runtime
67
+ - Credential stripping when the backend is not `unrestricted`
68
+
69
+ ## Architecture
70
+
71
+ ```
72
+ Agent / SDK
73
+ └── POST /v1/run
74
+ └── SubprocessSandbox (MVP)
75
+ └── (next) gVisor / Docker backend
76
+ ```
77
+
78
+ | Component | Technology | Why |
79
+ |-----------|------------|-----|
80
+ | API | FastAPI | Async-ready, OpenAPI docs, widely adopted |
81
+ | Server | uvicorn | Standard ASGI server |
82
+ | Config | pydantic-settings | Typed env config |
83
+ | Tests | pytest + httpx TestClient | Fast API testing |
84
+
85
+ ## Installation
86
+
87
+ ```bash
88
+ pip install agentbox-sandbox
89
+ pip install -e ".[dev]"
90
+ ```
91
+
92
+ ## Usage
93
+
94
+ ### Start server
95
+
96
+ ```bash
97
+ agentbox serve
98
+ # or
99
+ docker compose up agentbox
100
+ ```
101
+
102
+ ### Run code
103
+
104
+ ```bash
105
+ curl -X POST http://localhost:8080/v1/run \
106
+ -H 'Content-Type: application/json' \
107
+ -d '{"code": "print(sum(range(10)))"}'
108
+
109
+ curl -X POST http://localhost:8080/v1/run \
110
+ -H 'Content-Type: application/json' \
111
+ -d '{"code": "x = bytearray(10**9)", "limits": {"memory_mb": 64, "timeout_seconds": 5}}'
112
+ ```
113
+
114
+ ### Python SDK
115
+
116
+ ```python
117
+ from agentbox.sdk.client import AgentboxClient
118
+
119
+ client = AgentboxClient("http://localhost:8080")
120
+ print(client.health())
121
+ print(client.run("print('hello')"))
122
+ print(client.run("console.log('hello')", language="javascript", timeout_seconds=5))
123
+ print(client.run("x = bytearray(10**8)", memory_mb=64))
124
+ snap = client.run("open('memo.txt','w').write('kept')", snapshot=True)
125
+ print(client.run("print(open('memo.txt').read())", snapshot_id=snap["snapshot_id"]))
126
+ client.close()
127
+ ```
128
+
129
+ ## Docker
130
+
131
+ ```bash
132
+ docker compose up agentbox # start API on :8080
133
+ docker compose run --rm test # run unit tests
134
+ ```
135
+
136
+ ## Configuration
137
+
138
+ | Variable | Default | Description |
139
+ |----------|---------|-------------|
140
+ | `AGENTBOX_HOST` | `0.0.0.0` | Bind host |
141
+ | `AGENTBOX_PORT` | `8080` | Bind port |
142
+ | `AGENTBOX_DEFAULT_TIMEOUT_SECONDS` | `30` | Execution timeout |
143
+ | `AGENTBOX_DEFAULT_MEMORY_MB` | unset | Optional default `RLIMIT_AS` cap |
144
+ | `AGENTBOX_SANDBOX_BACKEND` | `subprocess` | Backend selector |
145
+ | `AGENTBOX_SNAPSHOT_DIR` | `/tmp/agentbox-snapshots` | Workspace snapshot store |
146
+
147
+ ## Running tests
148
+
149
+ ```bash
150
+ pytest tests/ -v
151
+ ```
152
+
153
+ ## Roadmap
154
+
155
+ - [x] Node.js runtime + TypeScript client + per-run timeout
156
+ - [x] Filesystem snapshot/restore (tar workspaces)
157
+ - [x] `limits.memory_mb` via `RLIMIT_AS`
158
+ - [ ] gVisor runsc backend with warm pool
159
+ - [ ] Default-deny egress with allowlists (kernel netns)
160
+
161
+ ## License
162
+
163
+ MIT
164
+
165
+ ## Known limitations (v0.4)
166
+
167
+ - Subprocess sandbox only — **not production-grade isolation**
168
+ - `RLIMIT_AS` is a soft address-space cap, not a cgroup memory controller
169
+ - Credential stripping is not a network namespace
170
+ - Single-node, no warm pool
171
+ - TypeScript client is source-only (not published to npm)
@@ -0,0 +1,15 @@
1
+ agentbox/__init__.py,sha256=C9wcnsFp66q8zF3X6RphAKjn3WWeyuKYHWmVVzyH6qQ,79
2
+ agentbox/cli.py,sha256=6XP3uw0BU5G2Z4DzyqOO5SdCae-uf8LR0HTeKYrJ_mM,733
3
+ agentbox/config.py,sha256=T00q3OOB1IPn22Dpz3UnePjWi0rvBPFuZV1UxeFtOIM,527
4
+ agentbox/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ agentbox/api/app.py,sha256=YqtF0Jd5rocakzJm72ZgUKRyNx-O2riGxHI0yWEW0rU,2643
6
+ agentbox/sandbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ agentbox/sandbox/runner.py,sha256=LYK6ODx53B6o5yVco9pIkYMRr0tbWrsNuT3SZWEaYXk,4638
8
+ agentbox/sandbox/snapshots.py,sha256=J4G2xprZvFWlDAjUPCYFQC3On60tMF2roAohm56Ldds,1045
9
+ agentbox/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ agentbox/sdk/client.py,sha256=6mO8WFMmtWkA4UYBnELznFJgsv75mEmn2HMwzGFht4M,1822
11
+ agentbox_sandbox-0.4.0.dist-info/METADATA,sha256=EmZZn31IqhZufyKsZUgCHoSog-iR3GUMctSpLF5cUe4,5573
12
+ agentbox_sandbox-0.4.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
13
+ agentbox_sandbox-0.4.0.dist-info/entry_points.txt,sha256=u_320LVs7Vndr2ti0gFQXhR8o9kkjW0LmNjLzEXLr50,47
14
+ agentbox_sandbox-0.4.0.dist-info/licenses/LICENSE,sha256=_oD5FzhsdoFIbsjVU91FFzguT7fXSNwL-z-dZJxLBzU,1078
15
+ agentbox_sandbox-0.4.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ agentbox = agentbox.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 agentbox contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.