agentpad 0.1.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.
agentpad/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ from agentpad.run_log import export_run_log_json, export_run_log_markdown
2
+ from agentpad.runtime import Runtime
3
+ from agentpad.types import FileChange, RunLogEntry, RunResult, SerializedRuntime
4
+
5
+ __all__ = [
6
+ "Runtime",
7
+ "RunResult",
8
+ "RunLogEntry",
9
+ "FileChange",
10
+ "SerializedRuntime",
11
+ "export_run_log_json",
12
+ "export_run_log_markdown",
13
+ ]
agentpad/engines.py ADDED
@@ -0,0 +1,127 @@
1
+ from __future__ import annotations
2
+
3
+ import shutil
4
+ import subprocess
5
+ import sys
6
+ import time
7
+ from pathlib import Path
8
+
9
+ from agentpad.types import RunResult
10
+
11
+
12
+ def _truncate(stdout: str, stderr: str, max_bytes: int | None) -> tuple[str, str, bool]:
13
+ if max_bytes is None or max_bytes <= 0:
14
+ return stdout, stderr, False
15
+ if len(stdout) + len(stderr) <= max_bytes:
16
+ return stdout, stderr, False
17
+ budget = max_bytes
18
+ if len(stdout) > budget:
19
+ return stdout[:budget] + "\n...[truncated]", "", True
20
+ budget -= len(stdout)
21
+ err = stderr[:budget] + "\n...[truncated]" if len(stderr) > budget else stderr
22
+ return stdout, err, True
23
+
24
+
25
+ def run_subprocess(
26
+ argv: list[str],
27
+ cwd: str,
28
+ env: dict[str, str] | None,
29
+ timeout_s: float,
30
+ max_output_bytes: int | None,
31
+ ) -> RunResult:
32
+ start = time.perf_counter()
33
+ try:
34
+ p = subprocess.run(
35
+ argv,
36
+ cwd=cwd,
37
+ env={**__import__("os").environ, **(env or {})},
38
+ capture_output=True,
39
+ text=True,
40
+ timeout=timeout_s,
41
+ )
42
+ out, err, trunc = _truncate(p.stdout or "", p.stderr or "", max_output_bytes)
43
+ return RunResult(
44
+ stdout=out,
45
+ stderr=err,
46
+ exit_code=p.returncode,
47
+ duration_ms=(time.perf_counter() - start) * 1000,
48
+ files=[],
49
+ truncated=trunc,
50
+ )
51
+ except subprocess.TimeoutExpired as e:
52
+ out, err, trunc = _truncate(
53
+ (e.stdout or "") if isinstance(e.stdout, str) else "",
54
+ (e.stderr or "") if isinstance(e.stderr, str) else "",
55
+ max_output_bytes,
56
+ )
57
+ return RunResult(
58
+ stdout=out,
59
+ stderr=err or "timeout",
60
+ exit_code=124,
61
+ duration_ms=(time.perf_counter() - start) * 1000,
62
+ files=[],
63
+ truncated=trunc,
64
+ )
65
+ except FileNotFoundError as e:
66
+ return RunResult(
67
+ stdout="",
68
+ stderr=str(e),
69
+ exit_code=127,
70
+ duration_ms=(time.perf_counter() - start) * 1000,
71
+ files=[],
72
+ truncated=False,
73
+ )
74
+
75
+
76
+ def run_bash(code: str, cwd: str, env: dict[str, str] | None, timeout_s: float, max_out: int | None) -> RunResult:
77
+ bash = shutil.which("bash") or "bash"
78
+ return run_subprocess([bash, "-c", code], cwd, env, timeout_s, max_out)
79
+
80
+
81
+ def run_python(code: str, cwd: str, env: dict[str, str] | None, timeout_s: float, max_out: int | None) -> RunResult:
82
+ py = shutil.which("python3") or shutil.which("python") or "python3"
83
+ return run_subprocess([py, "-c", code], cwd, env, timeout_s, max_out)
84
+
85
+
86
+ def run_javascript(code: str, cwd: str, env: dict[str, str] | None, timeout_s: float, max_out: int | None) -> RunResult:
87
+ node = shutil.which("node") or "node"
88
+ return run_subprocess([node, "-e", code], cwd, env, timeout_s, max_out)
89
+
90
+
91
+ def run_sql(code: str, cwd: str, env: dict[str, str] | None, timeout_s: float, max_out: int | None) -> RunResult:
92
+ import sqlite3
93
+
94
+ _ = cwd, env, timeout_s # SQL runs in-memory; cwd reserved for attach extensions
95
+ start = time.perf_counter()
96
+ try:
97
+ con = sqlite3.connect(":memory:")
98
+ cur = con.cursor()
99
+ parts = [s.strip() for s in code.split(";") if s.strip()]
100
+ out_lines: list[str] = []
101
+ for stmt in parts:
102
+ cur.execute(stmt)
103
+ if cur.description:
104
+ rows = cur.fetchall()
105
+ out_lines.extend(str(r) for r in rows)
106
+ else:
107
+ con.commit()
108
+ con.close()
109
+ out = "\n".join(out_lines) + ("\n" if out_lines else "")
110
+ out, err, trunc = _truncate(out, "", max_out)
111
+ return RunResult(
112
+ stdout=out,
113
+ stderr=err,
114
+ exit_code=0,
115
+ duration_ms=(time.perf_counter() - start) * 1000,
116
+ files=[],
117
+ truncated=trunc,
118
+ )
119
+ except Exception as e: # noqa: BLE001
120
+ return RunResult(
121
+ stdout="",
122
+ stderr=str(e),
123
+ exit_code=1,
124
+ duration_ms=(time.perf_counter() - start) * 1000,
125
+ files=[],
126
+ truncated=False,
127
+ )
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ import fnmatch
4
+ from pathlib import Path
5
+
6
+
7
+ def _excluded(rel: str, exclude_globs: list[str]) -> bool:
8
+ parts = rel.replace("\\", "/").split("/")
9
+ if "node_modules" in parts or ".git" in parts:
10
+ return True
11
+ posix = rel.replace("\\", "/")
12
+ return any(fnmatch.fnmatch(posix, g) for g in exclude_globs)
13
+
14
+
15
+ def list_matching_rel_paths(
16
+ root: str | Path,
17
+ include_globs: list[str],
18
+ exclude_globs: list[str] | None = None,
19
+ ) -> list[str]:
20
+ exclude_globs = exclude_globs or []
21
+ rootp = Path(root).resolve()
22
+ rels: list[str] = []
23
+ for p in rootp.rglob("*"):
24
+ if not p.is_file():
25
+ continue
26
+ rel = str(p.relative_to(rootp)).replace("\\", "/")
27
+ if _excluded(rel, exclude_globs):
28
+ continue
29
+ posix = rel
30
+ if not include_globs:
31
+ rels.append(rel)
32
+ continue
33
+ if any(fnmatch.fnmatch(posix, g) for g in include_globs):
34
+ rels.append(rel)
35
+ return rels
agentpad/run_log.py ADDED
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from datetime import datetime, timezone
5
+
6
+ from agentpad.types import RunLogEntry
7
+
8
+
9
+ def export_run_log_json(entries: list[RunLogEntry], *, pretty: bool = True) -> str:
10
+ payload = [
11
+ {
12
+ "id": e.id,
13
+ "at": e.at,
14
+ "language": e.language,
15
+ "code": e.code,
16
+ "cwd": e.cwd,
17
+ "exitCode": e.exit_code,
18
+ "durationMs": e.duration_ms,
19
+ "stdout": e.stdout,
20
+ "stderr": e.stderr,
21
+ "truncated": e.truncated,
22
+ "files": [{"path": f.path, "type": f.type, "size": f.size} for f in e.files],
23
+ }
24
+ for e in entries
25
+ ]
26
+ return json.dumps(payload, indent=2 if pretty else None)
27
+
28
+
29
+ def export_run_log_markdown(entries: list[RunLogEntry]) -> str:
30
+ lines: list[str] = ["# agentpad session log", ""]
31
+ for e in entries:
32
+ ts = datetime.fromtimestamp(e.at / 1000, tz=timezone.utc).isoformat()
33
+ lines.append(f"## {e.id} — {e.language} @ {ts}")
34
+ lines.append("")
35
+ lines.append(
36
+ f"- **exit:** {e.exit_code} · **duration:** {e.duration_ms}ms · **truncated:** {e.truncated}"
37
+ )
38
+ lines.append(f"- **cwd:** `{e.cwd}`")
39
+ if e.files:
40
+ parts = [f"{f.type}:{f.path}" for f in e.files]
41
+ lines.append(f"- **files:** {', '.join(parts)}")
42
+ lines.extend(["", "### code", "", "```", e.code, "```", "", "### stdout", "", "```", e.stdout or "(empty)", "```"])
43
+ lines.extend(["", "### stderr", "", "```", e.stderr or "(empty)", "```", ""])
44
+ return "\n".join(lines)
agentpad/runtime.py ADDED
@@ -0,0 +1,230 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import shutil
5
+ import tempfile
6
+ import time
7
+ from collections.abc import Callable
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from agentpad.engines import run_bash, run_javascript, run_python, run_sql
12
+ from agentpad.extensions.globs import list_matching_rel_paths
13
+ from agentpad.types import FileChange, Language, RunLogEntry, RunResult, SerializedRuntime
14
+
15
+
16
+ def _cp_tree(src: Path, dest: Path) -> None:
17
+ if dest.exists():
18
+ shutil.rmtree(dest)
19
+ shutil.copytree(src, dest, dirs_exist_ok=True)
20
+
21
+
22
+ def _merge_overlay_to_root(work: Path, root: Path) -> None:
23
+ for p in work.rglob("*"):
24
+ if p.is_file():
25
+ rel = p.relative_to(work)
26
+ out = root / rel
27
+ out.parent.mkdir(parents=True, exist_ok=True)
28
+ shutil.copy2(p, out)
29
+
30
+
31
+ def _snapshot_tree(root: Path, rels: list[str]) -> dict[str, tuple[float, int]]:
32
+ m: dict[str, tuple[float, int]] = {}
33
+ for rel in rels:
34
+ p = root / rel
35
+ if p.is_file():
36
+ st = p.stat()
37
+ m[rel] = (st.st_mtime, st.st_size)
38
+ return m
39
+
40
+
41
+ def _diff_tree(root: Path, before: dict[str, tuple[float, int]], rels: list[str]) -> list[FileChange]:
42
+ changes: list[FileChange] = []
43
+ for rel in rels:
44
+ p = root / rel
45
+ if not p.is_file():
46
+ if rel in before:
47
+ changes.append(FileChange(path=rel, type="deleted", size=0))
48
+ continue
49
+ st = p.stat()
50
+ prev = before.get(rel)
51
+ if prev is None:
52
+ changes.append(FileChange(path=rel, type="created", size=st.st_size))
53
+ elif prev[0] != st.st_mtime or prev[1] != st.st_size:
54
+ changes.append(FileChange(path=rel, type="modified", size=st.st_size))
55
+ return changes
56
+
57
+
58
+ class Runtime:
59
+ def __init__(
60
+ self,
61
+ root: str | Path,
62
+ *,
63
+ readonly: bool = False,
64
+ overlay: bool = False,
65
+ limits: dict[str, Any] | None = None,
66
+ files: dict[str, str] | None = None,
67
+ include_globs: list[str] | None = None,
68
+ exclude_globs: list[str] | None = None,
69
+ run_log: bool = True,
70
+ run_log_max_entries: int = 200,
71
+ on_run: Callable[[RunLogEntry], None] | None = None,
72
+ ) -> None:
73
+ self.root = Path(root).resolve()
74
+ self._readonly = readonly
75
+ self._overlay = overlay
76
+ self._limits = limits or {}
77
+ self._include = include_globs
78
+ self._exclude = exclude_globs or ["**/node_modules/**", "**/.git/**"]
79
+ self._owns_workdir = overlay
80
+ self._run_log_enabled = run_log
81
+ self._run_log_max = max(1, int(run_log_max_entries))
82
+ self._on_run = on_run
83
+ self._run_log: list[RunLogEntry] = []
84
+ self._log_seq = 0
85
+ if overlay:
86
+ self._workdir = Path(tempfile.mkdtemp(prefix="agentpad-"))
87
+ _cp_tree(self.root, self._workdir)
88
+ else:
89
+ self._workdir = self.root
90
+ if files:
91
+ for rel, content in files.items():
92
+ p = self._workdir / rel
93
+ p.parent.mkdir(parents=True, exist_ok=True)
94
+ p.write_text(content, encoding="utf8")
95
+
96
+ @property
97
+ def effective_root(self) -> Path:
98
+ return self._workdir
99
+
100
+ def _timeout_s(self) -> float:
101
+ return float(self._limits.get("timeout_ms", 30_000)) / 1000.0
102
+
103
+ def _max_out(self) -> int | None:
104
+ return self._limits.get("max_output_bytes")
105
+
106
+ def _glob_scope(self) -> list[str]:
107
+ inc = self._include or ["**/*"]
108
+ return list_matching_rel_paths(self._workdir, inc, self._exclude)
109
+
110
+ def run(
111
+ self,
112
+ language: Language,
113
+ code: str,
114
+ *,
115
+ cwd: str | None = None,
116
+ env: dict[str, str] | None = None,
117
+ ) -> RunResult:
118
+ wd = (self._workdir / (cwd or ".")).resolve()
119
+ if not str(wd).startswith(str(self._workdir.resolve())):
120
+ raise ValueError("cwd escapes workspace")
121
+ timeout_s = self._timeout_s()
122
+ max_out = self._max_out()
123
+ before = _snapshot_tree(self._workdir, self._glob_scope())
124
+ if language == "bash":
125
+ r = run_bash(code, str(wd), env, timeout_s, max_out)
126
+ elif language == "python":
127
+ r = run_python(code, str(wd), env, timeout_s, max_out)
128
+ elif language == "javascript":
129
+ r = run_javascript(code, str(wd), env, timeout_s, max_out)
130
+ elif language == "sql":
131
+ r = run_sql(code, str(wd), env, timeout_s, max_out)
132
+ else:
133
+ raise ValueError(f"Unknown language: {language}")
134
+ after_rels = list_matching_rel_paths(self._workdir, ["**/*"], self._exclude)
135
+ r.files = _diff_tree(self._workdir, before, after_rels)
136
+ if self._run_log_enabled:
137
+ self._log_seq += 1
138
+ entry = RunLogEntry(
139
+ id=f"run-{self._log_seq}",
140
+ at=time.time() * 1000,
141
+ language=language,
142
+ code=code,
143
+ cwd=str(wd),
144
+ exit_code=r.exit_code,
145
+ duration_ms=r.duration_ms,
146
+ stdout=r.stdout,
147
+ stderr=r.stderr,
148
+ truncated=r.truncated,
149
+ files=list(r.files),
150
+ )
151
+ self._run_log.append(entry)
152
+ while len(self._run_log) > self._run_log_max:
153
+ self._run_log.pop(0)
154
+ if self._on_run:
155
+ self._on_run(entry)
156
+ return r
157
+
158
+ def get_run_log(self) -> list[RunLogEntry]:
159
+ return list(self._run_log)
160
+
161
+ def clear_run_log(self) -> None:
162
+ self._run_log.clear()
163
+
164
+ def apply(self) -> None:
165
+ if not self._overlay:
166
+ raise RuntimeError("apply() only valid in overlay mode")
167
+ _merge_overlay_to_root(self._workdir, self.root)
168
+
169
+ def close(self) -> None:
170
+ if self._owns_workdir and self._workdir.exists():
171
+ shutil.rmtree(self._workdir, ignore_errors=True)
172
+
173
+ def serialize(self) -> SerializedRuntime:
174
+ writes: dict[str, str] = {}
175
+ if self._overlay:
176
+ for p in self._workdir.rglob("*"):
177
+ if p.is_file():
178
+ rel = str(p.relative_to(self._workdir)).replace("\\", "/")
179
+ writes[rel] = p.read_text(encoding="utf8", errors="replace")
180
+ return SerializedRuntime(
181
+ version=1,
182
+ root=str(self.root),
183
+ readonly=self._readonly,
184
+ overlay=self._overlay,
185
+ overlay_writes=writes,
186
+ )
187
+
188
+ @staticmethod
189
+ def deserialize(data: SerializedRuntime) -> Runtime:
190
+ if data.version != 1:
191
+ raise ValueError("Unsupported version")
192
+ return Runtime(
193
+ data.root,
194
+ overlay=data.overlay,
195
+ readonly=data.readonly,
196
+ files=data.overlay_writes if data.overlay and data.overlay_writes else None,
197
+ )
198
+
199
+ def fs_write(self, rel: str, content: str) -> None:
200
+ if self._readonly:
201
+ raise OSError("read-only")
202
+ p = self._workdir / rel
203
+ p.parent.mkdir(parents=True, exist_ok=True)
204
+ p.write_text(content, encoding="utf8")
205
+
206
+ def fs_read(self, rel: str) -> str:
207
+ return (self._workdir / rel).read_text(encoding="utf8")
208
+
209
+ def as_openai_tool(self) -> dict[str, Any]:
210
+ return {
211
+ "type": "function",
212
+ "function": {
213
+ "name": "execute_code",
214
+ "description": "Execute code in workspace (bash, python, javascript, sql).",
215
+ "parameters": {
216
+ "type": "object",
217
+ "properties": {
218
+ "language": {
219
+ "type": "string",
220
+ "enum": ["bash", "python", "javascript", "sql"],
221
+ },
222
+ "code": {"type": "string"},
223
+ },
224
+ "required": ["language", "code"],
225
+ },
226
+ },
227
+ }
228
+
229
+ def execute_tool_call(self, args: dict[str, Any]) -> RunResult:
230
+ return self.run(args["language"], args["code"])
agentpad/types.py ADDED
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Literal, Optional
5
+
6
+ Language = Literal["bash", "python", "javascript", "sql"]
7
+
8
+
9
+ @dataclass
10
+ class FileChange:
11
+ path: str
12
+ type: Literal["created", "modified", "deleted"]
13
+ size: int
14
+
15
+
16
+ @dataclass
17
+ class RunResult:
18
+ stdout: str
19
+ stderr: str
20
+ exit_code: int
21
+ duration_ms: float
22
+ files: list[FileChange] = field(default_factory=list)
23
+ truncated: bool = False
24
+
25
+
26
+ @dataclass
27
+ class RunLogEntry:
28
+ id: str
29
+ at: float
30
+ language: Language
31
+ code: str
32
+ cwd: str
33
+ exit_code: int
34
+ duration_ms: float
35
+ stdout: str
36
+ stderr: str
37
+ truncated: bool
38
+ files: list[FileChange]
39
+
40
+
41
+ @dataclass
42
+ class SerializedRuntime:
43
+ version: int
44
+ root: str
45
+ readonly: bool
46
+ overlay: bool
47
+ overlay_writes: dict[str, str]
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentpad
3
+ Version: 0.1.0
4
+ Summary: Multi-language code execution against a real directory for AI agents
5
+ Project-URL: Homepage, https://slaps.dev/agentpad
6
+ Project-URL: Documentation, https://slaps.dev/docs/agentpad/
7
+ Project-URL: Repository, https://github.com/vgulerianb/agentpad
8
+ Project-URL: Bug Tracker, https://github.com/vgulerianb/agentpad/issues
9
+ Author: slaps.dev
10
+ License-Expression: Apache-2.0
11
+ Keywords: agents,ai,bash,ci,code-execution,nodejs,openai,python,sandbox,sqlite,subprocess,tool-calling
12
+ Requires-Python: >=3.10
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0; extra == 'dev'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # agentpad
18
+
19
+ Execute **bash**, **Python**, **JavaScript** (Node), and **SQL** against a **real project directory**—with optional overlay, read-only mode, file-change tracking, and a session run log. Built for **AI agents**, CI, and local tooling.
20
+
21
+ Same design in **TypeScript** (npm) and **Python** (PyPI / local install).
22
+
23
+ ---
24
+
25
+ ## Install
26
+
27
+ ### JavaScript / TypeScript (npm)
28
+
29
+ ```bash
30
+ npm install agentpad
31
+ ```
32
+
33
+ Requires **Node.js ≥ 18**. For SQL on Node, the **`sqlite3`** binary must be on your `PATH`.
34
+
35
+ ### Python
36
+
37
+ ```bash
38
+ pip install agentpad
39
+ ```
40
+
41
+ From a clone (editable):
42
+
43
+ ```bash
44
+ pip install -e ./python
45
+ ```
46
+
47
+ Requires **Python ≥ 3.10**. For SQL in Python, the runtime uses **stdlib `sqlite3`** (no `sqlite3` CLI required).
48
+
49
+ ---
50
+
51
+ ## Quick start
52
+
53
+ ### TypeScript
54
+
55
+ ```ts
56
+ import { Runtime } from "agentpad";
57
+
58
+ const rt = new Runtime("./my-project");
59
+ const r = await rt.run("python", 'print(open("README.md").read()[:80])');
60
+ console.log(r.stdout);
61
+ console.log(r.files); // file changes under the workspace
62
+ rt.close();
63
+ ```
64
+
65
+ ### Python
66
+
67
+ ```python
68
+ from agentpad import Runtime
69
+
70
+ rt = Runtime("./my-project")
71
+ r = rt.run("python", "print(1 + 1)")
72
+ print(r.stdout)
73
+ rt.close()
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Features
79
+
80
+ | Capability | Summary |
81
+ |------------|---------|
82
+ | **Languages** | `bash`, `python`, `javascript`, `sql` |
83
+ | **Workspace modes** | Normal, **read-only** (`readonly: true`), or **overlay** (temp copy + `apply()` back to disk) |
84
+ | **File tracking** | `includeGlobs` / `excludeGlobs` (minimatch) scope which paths appear in `RunResult.files` |
85
+ | **Limits** | `timeoutMs`, `maxOutputBytes` per run or via `limits` on the runtime |
86
+ | **Session log** | `getRunLog()`, `clearRunLog()`, `exportRunLogJSON` / `exportRunLogMarkdown`, optional `onRun` |
87
+ | **OpenAI tools** | `asOpenAITool()` + `executeToolCall({ language, code })` (TS); `as_openai_tool` / `execute_tool_call` (Python) |
88
+ | **Persistence** | `serialize()` / `Runtime.deserialize()` for overlay state |
89
+
90
+ ---
91
+
92
+ ## Documentation
93
+
94
+ Hosted on **[slaps.dev](https://slaps.dev)**:
95
+
96
+ | Doc | Contents |
97
+ |-----|----------|
98
+ | [Overview](https://slaps.dev/docs/agentpad/) | Product summary and doc index |
99
+ | [Getting started](https://slaps.dev/docs/agentpad/getting-started) | Install, first runs, overlay, read-only |
100
+ | [Use cases](https://slaps.dev/docs/agentpad/use-cases) | Agents, testing patterns, stubfetch |
101
+ | [Configuration](https://slaps.dev/docs/agentpad/configuration) | `RuntimeOptions`, `RunOptions`, globs, limits, run log |
102
+ | [API reference](https://slaps.dev/docs/agentpad/api-reference) | `Runtime`, types, filesystem adapters, exports |
103
+ | [Security](https://slaps.dev/docs/agentpad/security) | Threat model, workspace boundaries, subprocess behavior |
104
+ | [Python notes](https://slaps.dev/docs/agentpad/python) | Node vs Python differences |
105
+
106
+ ---
107
+
108
+ ## Source
109
+
110
+ Package and issue tracker: **[github.com/vgulerianb/agentpad](https://github.com/vgulerianb/agentpad)** (canonical repo). The [slaps.dev](https://github.com/vgulerianb/slaps.dev) monorepo may carry a **vendored copy** of this tree for the website and doc sync scripts—it is not the package’s primary `repository` URL on npm/PyPI.
111
+
112
+ ## License
113
+
114
+ Apache-2.0
@@ -0,0 +1,9 @@
1
+ agentpad/__init__.py,sha256=QGcgtf8UV9Q5RrREEo6URdkV-OZ9SgwRgfTaxsQqMVU,359
2
+ agentpad/engines.py,sha256=GBLB9w-ystjCXO8wrcPquRHqly15GuQmlbaUW1tHR_s,4190
3
+ agentpad/run_log.py,sha256=j7nXM2DnKuxbaSDRNrD6GAYX9u2U5aZvINVFJMfBF0M,1619
4
+ agentpad/runtime.py,sha256=ElDo5tnRcaZHImMvVlvhbikFmysWl1xSjHES4b-t6-g,8065
5
+ agentpad/types.py,sha256=AkwwJgrs-hYOR3KccHE4aL-cL9bf-lrrk351sMOAYHw,845
6
+ agentpad/extensions/globs.py,sha256=llBOKing9Y6lSGG7Keo8bPQAeKepUUqhJI51nxZuHzo,1008
7
+ agentpad-0.1.0.dist-info/METADATA,sha256=uRl_9s-5aKpqeYlUWhR5p7dBzYPs1WlcxffyQLsk0k4,3826
8
+ agentpad-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
9
+ agentpad-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any