bashkit 0.1.4__cp39-cp39-win_amd64.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.
bashkit/__init__.py ADDED
@@ -0,0 +1,33 @@
1
+ """
2
+ Bashkit Python Bindings
3
+
4
+ A sandboxed bash interpreter for AI agents with virtual filesystem.
5
+
6
+ Example:
7
+ >>> from bashkit import BashTool
8
+ >>> tool = BashTool()
9
+ >>> result = await tool.execute("echo 'Hello, World!'")
10
+ >>> print(result.stdout)
11
+ Hello, World!
12
+
13
+ For LangChain integration:
14
+ >>> from bashkit.langchain import create_bash_tool
15
+ >>> tool = create_bash_tool()
16
+
17
+ For Deep Agents integration:
18
+ >>> from bashkit.deepagents import create_bash_middleware
19
+ >>> middleware = create_bash_middleware()
20
+ """
21
+
22
+ from bashkit._bashkit import (
23
+ BashTool,
24
+ ExecResult,
25
+ create_langchain_tool_spec,
26
+ )
27
+
28
+ __version__ = "0.1.2"
29
+ __all__ = [
30
+ "BashTool",
31
+ "ExecResult",
32
+ "create_langchain_tool_spec",
33
+ ]
Binary file
bashkit/_bashkit.pyi ADDED
@@ -0,0 +1,101 @@
1
+ """Type stubs for bashkit_py native module."""
2
+
3
+ from typing import Optional
4
+
5
+ class ExecResult:
6
+ """Result from executing bash commands."""
7
+
8
+ stdout: str
9
+ stderr: str
10
+ exit_code: int
11
+ error: Optional[str]
12
+ success: bool
13
+
14
+ def to_dict(self) -> dict[str, any]: ...
15
+
16
+ class BashTool:
17
+ """Sandboxed bash interpreter for AI agents.
18
+
19
+ BashTool provides a safe execution environment for running bash commands
20
+ with a virtual filesystem. All file operations are contained within the
21
+ sandbox - no access to the real filesystem.
22
+
23
+ Example:
24
+ >>> tool = BashTool()
25
+ >>> result = await tool.execute("echo 'Hello!'")
26
+ >>> print(result.stdout)
27
+ Hello!
28
+ """
29
+
30
+ name: str
31
+ short_description: str
32
+ version: str
33
+
34
+ def __init__(
35
+ self,
36
+ username: Optional[str] = None,
37
+ hostname: Optional[str] = None,
38
+ max_commands: Optional[int] = None,
39
+ max_loop_iterations: Optional[int] = None,
40
+ ) -> None:
41
+ """Create a new BashTool instance.
42
+
43
+ Args:
44
+ username: Custom username for sandbox (default: "user")
45
+ hostname: Custom hostname for sandbox (default: "sandbox")
46
+ max_commands: Maximum commands to execute (default: 10000)
47
+ max_loop_iterations: Maximum loop iterations (default: 100000)
48
+ """
49
+ ...
50
+
51
+ async def execute(self, commands: str) -> ExecResult:
52
+ """Execute bash commands asynchronously.
53
+
54
+ Args:
55
+ commands: Bash commands to execute (like `bash -c "commands"`)
56
+
57
+ Returns:
58
+ ExecResult with stdout, stderr, exit_code
59
+ """
60
+ ...
61
+
62
+ def execute_sync(self, commands: str) -> ExecResult:
63
+ """Execute bash commands synchronously (blocking).
64
+
65
+ Note: Prefer `execute()` for async contexts. This method blocks.
66
+
67
+ Args:
68
+ commands: Bash commands to execute
69
+
70
+ Returns:
71
+ ExecResult with stdout, stderr, exit_code
72
+ """
73
+ ...
74
+
75
+ def description(self) -> str:
76
+ """Get the full description."""
77
+ ...
78
+
79
+ def help(self) -> str:
80
+ """Get LLM documentation."""
81
+ ...
82
+
83
+ def system_prompt(self) -> str:
84
+ """Get system prompt for LLMs."""
85
+ ...
86
+
87
+ def input_schema(self) -> str:
88
+ """Get JSON schema for input validation."""
89
+ ...
90
+
91
+ def output_schema(self) -> str:
92
+ """Get JSON schema for output."""
93
+ ...
94
+
95
+ def create_langchain_tool_spec() -> dict[str, any]:
96
+ """Create a LangChain-compatible tool specification.
97
+
98
+ Returns:
99
+ Dict with name, description, and args_schema
100
+ """
101
+ ...
bashkit/deepagents.py ADDED
@@ -0,0 +1,338 @@
1
+ """
2
+ Deep Agents integration for Bashkit.
3
+
4
+ Provides middleware and backend for Deep Agents using Bashkit's VFS:
5
+ - BashkitMiddleware: Adds `bash` tool via AgentMiddleware.tools
6
+ - BashkitBackend: SandboxBackendProtocol for execute/read_file/write_file/etc.
7
+
8
+ Use together for shared VFS:
9
+ >>> backend = BashkitBackend()
10
+ >>> middleware = backend.create_middleware() # shares VFS
11
+ >>> agent = create_deep_agent(backend=backend, middleware=[middleware])
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import uuid
17
+ from datetime import datetime, timezone
18
+ from typing import Optional, TYPE_CHECKING
19
+
20
+ from bashkit import BashTool as NativeBashTool
21
+
22
+ if TYPE_CHECKING:
23
+ pass
24
+
25
+ # Check for deepagents availability
26
+ try:
27
+ from deepagents.backends.protocol import (
28
+ SandboxBackendProtocol,
29
+ ExecuteResponse,
30
+ FileInfo,
31
+ GrepMatch,
32
+ EditResult,
33
+ WriteResult,
34
+ FileDownloadResponse,
35
+ FileUploadResponse,
36
+ )
37
+ from langchain.agents.middleware.types import AgentMiddleware
38
+ from langchain_core.tools import tool as langchain_tool
39
+
40
+ DEEPAGENTS_AVAILABLE = True
41
+ except ImportError:
42
+ DEEPAGENTS_AVAILABLE = False
43
+ SandboxBackendProtocol = object
44
+ AgentMiddleware = object
45
+
46
+
47
+ def _now_iso() -> str:
48
+ return datetime.now(timezone.utc).isoformat()
49
+
50
+
51
+ def _make_bash_tool(bash_instance: NativeBashTool):
52
+ """Create a bash tool function from a BashTool instance."""
53
+ # Use name and description from bashkit lib
54
+ tool_name = bash_instance.name
55
+ tool_description = bash_instance.description()
56
+
57
+ @langchain_tool(tool_name, description=tool_description)
58
+ def bashkit(command: str) -> str:
59
+ result = bash_instance.execute_sync(command)
60
+ output = result.stdout
61
+ if result.stderr:
62
+ output += f"\n{result.stderr}"
63
+ if result.exit_code != 0:
64
+ output += f"\n[Exit code: {result.exit_code}]"
65
+ return output.strip() if output else "[No output]"
66
+
67
+ return bashkit
68
+
69
+
70
+ if DEEPAGENTS_AVAILABLE:
71
+
72
+ class BashkitMiddleware(AgentMiddleware):
73
+ """Middleware that adds `bash` tool for shell execution in VFS.
74
+
75
+ Example standalone:
76
+ >>> middleware = BashkitMiddleware()
77
+ >>> agent = create_deep_agent(middleware=[middleware])
78
+
79
+ Example with shared VFS (recommended):
80
+ >>> backend = BashkitBackend()
81
+ >>> middleware = backend.create_middleware()
82
+ >>> agent = create_deep_agent(backend=backend, middleware=[middleware])
83
+ """
84
+
85
+ def __init__(
86
+ self,
87
+ bash_tool: Optional[NativeBashTool] = None,
88
+ username: Optional[str] = None,
89
+ hostname: Optional[str] = None,
90
+ max_commands: Optional[int] = None,
91
+ max_loop_iterations: Optional[int] = None,
92
+ ):
93
+ """Initialize middleware.
94
+
95
+ Args:
96
+ bash_tool: Existing BashTool to use (for shared VFS)
97
+ username: Username for new BashTool (ignored if bash_tool provided)
98
+ hostname: Hostname for new BashTool (ignored if bash_tool provided)
99
+ max_commands: Max commands (ignored if bash_tool provided)
100
+ max_loop_iterations: Max iterations (ignored if bash_tool provided)
101
+ """
102
+ if bash_tool is not None:
103
+ self._bash = bash_tool
104
+ self._owns_bash = False
105
+ else:
106
+ self._bash = NativeBashTool(
107
+ username=username,
108
+ hostname=hostname,
109
+ max_commands=max_commands,
110
+ max_loop_iterations=max_loop_iterations,
111
+ )
112
+ self._owns_bash = True
113
+
114
+ self._tools = [_make_bash_tool(self._bash)]
115
+
116
+ @property
117
+ def tools(self):
118
+ """Tools provided by this middleware."""
119
+ return self._tools
120
+
121
+ def execute_sync(self, command: str) -> str:
122
+ """Execute command synchronously (for setup scripts)."""
123
+ result = self._bash.execute_sync(command)
124
+ return result.stdout + (result.stderr or "")
125
+
126
+ def reset(self) -> None:
127
+ """Reset VFS to initial state."""
128
+ if self._owns_bash:
129
+ self._bash.reset()
130
+
131
+
132
+ class BashkitBackend(SandboxBackendProtocol):
133
+ """Backend implementing SandboxBackendProtocol with Bashkit VFS.
134
+
135
+ Provides execute, read_file, write_file, edit_file, ls, glob, grep
136
+ all operating on the same virtual filesystem.
137
+
138
+ Example:
139
+ >>> backend = BashkitBackend()
140
+ >>> agent = create_deep_agent(backend=backend)
141
+
142
+ With middleware for additional `bash` tool:
143
+ >>> backend = BashkitBackend()
144
+ >>> middleware = backend.create_middleware()
145
+ >>> agent = create_deep_agent(backend=backend, middleware=[middleware])
146
+ """
147
+
148
+ def __init__(
149
+ self,
150
+ username: Optional[str] = None,
151
+ hostname: Optional[str] = None,
152
+ max_commands: Optional[int] = None,
153
+ max_loop_iterations: Optional[int] = None,
154
+ ):
155
+ self._bash = NativeBashTool(
156
+ username=username,
157
+ hostname=hostname,
158
+ max_commands=max_commands,
159
+ max_loop_iterations=max_loop_iterations,
160
+ )
161
+ self._id = f"bashkit-{uuid.uuid4().hex[:8]}"
162
+
163
+ @property
164
+ def id(self) -> str:
165
+ return self._id
166
+
167
+ def create_middleware(self) -> BashkitMiddleware:
168
+ """Create middleware that shares this backend's VFS.
169
+
170
+ Returns:
171
+ BashkitMiddleware using same BashTool instance
172
+ """
173
+ return BashkitMiddleware(bash_tool=self._bash)
174
+
175
+ # === Shell Execution ===
176
+
177
+ def execute(self, command: str) -> ExecuteResponse:
178
+ result = self._bash.execute_sync(command)
179
+ output = result.stdout + (result.stderr or "")
180
+ return ExecuteResponse(output=output, exit_code=result.exit_code, truncated=False)
181
+
182
+ async def aexecute(self, command: str) -> ExecuteResponse:
183
+ return self.execute(command)
184
+
185
+ # === File Operations ===
186
+
187
+ def read(self, file_path: str, offset: int = 0, limit: int = 2000) -> str:
188
+ result = self._bash.execute_sync(f"cat {file_path}")
189
+ if result.exit_code != 0:
190
+ return f"Error: {result.stderr or 'File not found'}"
191
+ lines = result.stdout.splitlines()
192
+ selected = lines[offset:offset + limit]
193
+ return "\n".join(f"{i:6d}\t{line}" for i, line in enumerate(selected, start=offset + 1))
194
+
195
+ async def aread(self, file_path: str, offset: int = 0, limit: int = 2000) -> str:
196
+ return self.read(file_path, offset, limit)
197
+
198
+ def write(self, file_path: str, content: str) -> WriteResult:
199
+ cmd = f"cat > {file_path} << 'BASHKIT_EOF'\n{content}\nBASHKIT_EOF"
200
+ result = self._bash.execute_sync(cmd)
201
+ return WriteResult(error=result.stderr if result.exit_code != 0 else None, path=file_path)
202
+
203
+ async def awrite(self, file_path: str, content: str) -> WriteResult:
204
+ return self.write(file_path, content)
205
+
206
+ def edit(self, file_path: str, old_string: str, new_string: str, replace_all: bool = False) -> EditResult:
207
+ result = self._bash.execute_sync(f"cat {file_path}")
208
+ if result.exit_code != 0:
209
+ return EditResult(error=f"File not found: {file_path}")
210
+ content = result.stdout
211
+ count = content.count(old_string)
212
+ if count == 0:
213
+ return EditResult(error="old_string not found")
214
+ if count > 1 and not replace_all:
215
+ return EditResult(error=f"Found {count} times. Use replace_all=True")
216
+ new_content = content.replace(old_string, new_string) if replace_all else content.replace(old_string, new_string, 1)
217
+ wr = self.write(file_path, new_content)
218
+ return EditResult(error=wr.error, path=file_path)
219
+
220
+ async def aedit(self, file_path: str, old_string: str, new_string: str, replace_all: bool = False) -> EditResult:
221
+ return self.edit(file_path, old_string, new_string, replace_all)
222
+
223
+ # === File Discovery ===
224
+
225
+ def ls_info(self, path: str) -> list[FileInfo]:
226
+ result = self._bash.execute_sync(f"ls -la {path}")
227
+ if result.exit_code != 0:
228
+ return []
229
+ files = []
230
+ for line in result.stdout.splitlines():
231
+ parts = line.split()
232
+ if len(parts) < 9 or parts[0].startswith("total"):
233
+ continue
234
+ name = " ".join(parts[8:])
235
+ if name in (".", ".."):
236
+ continue
237
+ files.append(FileInfo(
238
+ path=f"{path.rstrip('/')}/{name}", name=name,
239
+ is_dir=parts[0].startswith("d"),
240
+ size=int(parts[4]) if parts[4].isdigit() else 0,
241
+ created_at=_now_iso(), modified_at=_now_iso(),
242
+ ))
243
+ return files
244
+
245
+ async def als_info(self, path: str) -> list[FileInfo]:
246
+ return self.ls_info(path)
247
+
248
+ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]:
249
+ name_pattern = pattern.replace("**/", "").replace("**", "*") if "**" in pattern else pattern
250
+ result = self._bash.execute_sync(f"find {path} -name '{name_pattern}' -type f")
251
+ if result.exit_code != 0:
252
+ return []
253
+ return [
254
+ FileInfo(path=p.strip(), name=p.strip().split("/")[-1], is_dir=False, size=0, created_at=_now_iso(), modified_at=_now_iso())
255
+ for p in result.stdout.splitlines() if p.strip()
256
+ ]
257
+
258
+ async def aglob_info(self, pattern: str, path: str = "/") -> list[FileInfo]:
259
+ return self.glob_info(pattern, path)
260
+
261
+ def grep_raw(self, pattern: str, path: str | None = None, glob: str | None = None) -> list[GrepMatch] | str:
262
+ cmd = f"grep -rn '{pattern}' {path}" if path else f"grep -rn '{pattern}' /home"
263
+ result = self._bash.execute_sync(cmd)
264
+ matches = []
265
+ for line in result.stdout.splitlines():
266
+ if ":" not in line:
267
+ continue
268
+ parts = line.split(":", 2)
269
+ if len(parts) >= 3:
270
+ try:
271
+ matches.append(GrepMatch(path=parts[0], line_number=int(parts[1]), content=parts[2]))
272
+ except ValueError:
273
+ continue
274
+ return matches
275
+
276
+ async def agrep_raw(self, pattern: str, path: str | None = None, glob: str | None = None) -> list[GrepMatch] | str:
277
+ return self.grep_raw(pattern, path, glob)
278
+
279
+ # === File Transfer ===
280
+
281
+ def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
282
+ responses = []
283
+ for p in paths:
284
+ result = self._bash.execute_sync(f"cat {p}")
285
+ if result.exit_code == 0:
286
+ responses.append(FileDownloadResponse(path=p, content=result.stdout.encode(), error=None))
287
+ else:
288
+ responses.append(FileDownloadResponse(path=p, content=None, error=result.stderr or "File not found"))
289
+ return responses
290
+
291
+ async def adownload_files(self, paths: list[str]) -> list[FileDownloadResponse]:
292
+ return self.download_files(paths)
293
+
294
+ def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
295
+ responses = []
296
+ for p, content in files:
297
+ try:
298
+ wr = self.write(p, content.decode("utf-8"))
299
+ responses.append(FileUploadResponse(path=p, error=None if wr.success else wr.error))
300
+ except UnicodeDecodeError:
301
+ responses.append(FileUploadResponse(path=p, error="Binary files not supported"))
302
+ return responses
303
+
304
+ async def aupload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
305
+ return self.upload_files(files)
306
+
307
+ # === Utility ===
308
+
309
+ def setup(self, script: str) -> str:
310
+ """Execute setup script."""
311
+ result = self._bash.execute_sync(script)
312
+ return result.stdout + (result.stderr or "")
313
+
314
+ def reset(self) -> None:
315
+ """Reset VFS."""
316
+ self._bash.reset()
317
+
318
+
319
+ def create_bash_middleware(**kwargs) -> "BashkitMiddleware":
320
+ """Create BashkitMiddleware for Deep Agents."""
321
+ if not DEEPAGENTS_AVAILABLE:
322
+ raise ImportError("deepagents required. Install: pip install 'bashkit[deepagents]'")
323
+ return BashkitMiddleware(**kwargs)
324
+
325
+
326
+ def create_bashkit_backend(**kwargs) -> "BashkitBackend":
327
+ """Create BashkitBackend for Deep Agents."""
328
+ if not DEEPAGENTS_AVAILABLE:
329
+ raise ImportError("deepagents required. Install: pip install 'bashkit[deepagents]'")
330
+ return BashkitBackend(**kwargs)
331
+
332
+
333
+ __all__ = [
334
+ "BashkitMiddleware",
335
+ "BashkitBackend",
336
+ "create_bash_middleware",
337
+ "create_bashkit_backend",
338
+ ]
bashkit/langchain.py ADDED
@@ -0,0 +1,167 @@
1
+ """
2
+ LangChain integration for Bashkit.
3
+
4
+ Provides a LangChain-compatible tool that wraps BashTool for use with
5
+ LangChain agents and chains.
6
+
7
+ Example:
8
+ >>> from bashkit.langchain import create_bash_tool
9
+ >>> from langchain.agents import create_agent
10
+ >>>
11
+ >>> tool = create_bash_tool()
12
+ >>> agent = create_agent(model="claude-sonnet-4-20250514", tools=[tool])
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ from typing import Optional, Type
19
+
20
+ try:
21
+ from langchain_core.tools import BaseTool, ToolException
22
+ from pydantic import BaseModel, Field, PrivateAttr
23
+
24
+ LANGCHAIN_AVAILABLE = True
25
+ except ImportError:
26
+ LANGCHAIN_AVAILABLE = False
27
+ BaseTool = object
28
+ BaseModel = object
29
+
30
+ def Field(*args, **kwargs):
31
+ return None
32
+
33
+ def PrivateAttr(*args, **kwargs):
34
+ return None
35
+
36
+
37
+ from bashkit import BashTool as NativeBashTool
38
+
39
+
40
+ class BashToolInput(BaseModel):
41
+ """Input schema for BashTool."""
42
+
43
+ commands: str = Field(
44
+ description="Bash commands to execute (like `bash -c 'commands'`)"
45
+ )
46
+
47
+
48
+ if LANGCHAIN_AVAILABLE:
49
+
50
+ class BashkitTool(BaseTool):
51
+ """LangChain tool wrapper for Bashkit sandboxed bash interpreter.
52
+
53
+ Example:
54
+ >>> tool = BashkitTool()
55
+ >>> result = tool.invoke({"commands": "echo 'Hello!'"})
56
+ >>> print(result) # Hello!
57
+ """
58
+
59
+ name: str = "" # Set in __init__ from bashkit
60
+ description: str = "" # Set in __init__ from bashkit
61
+ args_schema: Type[BaseModel] = BashToolInput
62
+ handle_tool_error: bool = True
63
+
64
+ # Internal state - use PrivateAttr for pydantic v2 compatibility
65
+ _bash_tool: NativeBashTool = PrivateAttr()
66
+
67
+ def __init__(
68
+ self,
69
+ username: Optional[str] = None,
70
+ hostname: Optional[str] = None,
71
+ max_commands: Optional[int] = None,
72
+ max_loop_iterations: Optional[int] = None,
73
+ **kwargs,
74
+ ):
75
+ """Initialize BashkitTool.
76
+
77
+ Args:
78
+ username: Custom username for sandbox
79
+ hostname: Custom hostname for sandbox
80
+ max_commands: Max commands to execute
81
+ max_loop_iterations: Max loop iterations
82
+ """
83
+ bash_tool = NativeBashTool(
84
+ username=username,
85
+ hostname=hostname,
86
+ max_commands=max_commands,
87
+ max_loop_iterations=max_loop_iterations,
88
+ )
89
+ # Use name and description from bashkit lib
90
+ kwargs["name"] = bash_tool.name
91
+ kwargs["description"] = bash_tool.description()
92
+ super().__init__(**kwargs)
93
+ object.__setattr__(self, "_bash_tool", bash_tool)
94
+
95
+ def _run(self, commands: str) -> str:
96
+ """Execute bash commands synchronously."""
97
+ result = self._bash_tool.execute_sync(commands)
98
+
99
+ if result.error:
100
+ raise ToolException(f"Execution error: {result.error}")
101
+
102
+ # Return combined output for the agent
103
+ output = result.stdout
104
+ if result.stderr:
105
+ output += f"\nSTDERR: {result.stderr}"
106
+ if result.exit_code != 0:
107
+ output += f"\n[Exit code: {result.exit_code}]"
108
+
109
+ return output
110
+
111
+ async def _arun(self, commands: str) -> str:
112
+ """Execute bash commands asynchronously."""
113
+ result = await self._bash_tool.execute(commands)
114
+
115
+ if result.error:
116
+ raise ToolException(f"Execution error: {result.error}")
117
+
118
+ # Return combined output for the agent
119
+ output = result.stdout
120
+ if result.stderr:
121
+ output += f"\nSTDERR: {result.stderr}"
122
+ if result.exit_code != 0:
123
+ output += f"\n[Exit code: {result.exit_code}]"
124
+
125
+ return output
126
+
127
+
128
+ def create_bash_tool(
129
+ username: Optional[str] = None,
130
+ hostname: Optional[str] = None,
131
+ max_commands: Optional[int] = None,
132
+ max_loop_iterations: Optional[int] = None,
133
+ ) -> "BashkitTool":
134
+ """Create a LangChain-compatible Bashkit tool.
135
+
136
+ Args:
137
+ username: Custom username for sandbox
138
+ hostname: Custom hostname for sandbox
139
+ max_commands: Max commands to execute
140
+ max_loop_iterations: Max loop iterations
141
+
142
+ Returns:
143
+ BashkitTool instance for use with LangChain agents
144
+
145
+ Raises:
146
+ ImportError: If langchain-core is not installed
147
+
148
+ Example:
149
+ >>> from bashkit.langchain import create_bash_tool
150
+ >>> tool = create_bash_tool()
151
+ >>> result = tool.invoke({"commands": "ls -la"})
152
+ """
153
+ if not LANGCHAIN_AVAILABLE:
154
+ raise ImportError(
155
+ "langchain-core is required for LangChain integration. "
156
+ "Install with: pip install 'bashkit[langchain]'"
157
+ )
158
+
159
+ return BashkitTool(
160
+ username=username,
161
+ hostname=hostname,
162
+ max_commands=max_commands,
163
+ max_loop_iterations=max_loop_iterations,
164
+ )
165
+
166
+
167
+ __all__ = ["BashkitTool", "BashToolInput", "create_bash_tool"]
bashkit/py.typed ADDED
File without changes
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.4
2
+ Name: bashkit
3
+ Version: 0.1.4
4
+ Classifier: Development Status :: 4 - Beta
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.9
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Rust
14
+ Classifier: Topic :: Software Development :: Interpreters
15
+ Classifier: Topic :: Security
16
+ Requires-Dist: deepagents>=0.3.11 ; extra == 'deepagents'
17
+ Requires-Dist: langchain-anthropic>=0.3 ; extra == 'deepagents'
18
+ Requires-Dist: pytest>=7.0 ; extra == 'dev'
19
+ Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
20
+ Requires-Dist: langchain-core>=0.3 ; extra == 'langchain'
21
+ Requires-Dist: langchain-anthropic>=0.3 ; extra == 'langchain'
22
+ Provides-Extra: deepagents
23
+ Provides-Extra: dev
24
+ Provides-Extra: langchain
25
+ Summary: Python bindings for Bashkit - a sandboxed bash interpreter for AI agents
26
+ Keywords: bash,sandbox,ai,agent,shell,interpreter
27
+ License: MIT
28
+ Requires-Python: >=3.9
29
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
30
+
31
+ # Bashkit Python Bindings
32
+
33
+ Python bindings for [Bashkit](https://github.com/everruns/bashkit) - a sandboxed bash interpreter for AI agents.
34
+
35
+ ## Features
36
+
37
+ - **Sandboxed execution**: All commands run in isolation with a virtual filesystem
38
+ - **68+ built-in commands**: echo, cat, grep, sed, awk, jq, curl, find, and more
39
+ - **Full bash syntax**: Variables, pipelines, redirects, loops, functions, arrays
40
+ - **Resource limits**: Protect against infinite loops and runaway scripts
41
+ - **LangChain integration**: Ready-to-use tool for LangChain agents
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ # From PyPI (when published)
47
+ pip install bashkit
48
+
49
+ # With LangChain support
50
+ pip install 'bashkit[langchain]'
51
+
52
+ # From source
53
+ pip install maturin
54
+ maturin develop
55
+ ```
56
+
57
+ ## Quick Start
58
+
59
+ ```python
60
+ import asyncio
61
+ from bashkit import BashTool
62
+
63
+ async def main():
64
+ tool = BashTool()
65
+
66
+ # Simple command
67
+ result = await tool.execute("echo 'Hello, World!'")
68
+ print(result.stdout) # Hello, World!
69
+
70
+ # Pipeline
71
+ result = await tool.execute("echo -e 'banana\\napple\\ncherry' | sort")
72
+ print(result.stdout) # apple\nbanana\ncherry
73
+
74
+ # Virtual filesystem
75
+ result = await tool.execute("""
76
+ echo 'data' > /tmp/file.txt
77
+ cat /tmp/file.txt
78
+ """)
79
+ print(result.stdout) # data
80
+
81
+ asyncio.run(main())
82
+ ```
83
+
84
+ ## LangChain Integration
85
+
86
+ ```python
87
+ from bashkit.langchain import create_bash_tool
88
+ from langchain.agents import create_agent
89
+
90
+ # Create tool
91
+ bash_tool = create_bash_tool()
92
+
93
+ # Create agent
94
+ agent = create_agent(
95
+ model="claude-sonnet-4-20250514",
96
+ tools=[bash_tool],
97
+ system_prompt="You are a helpful assistant with bash skills."
98
+ )
99
+
100
+ # Run
101
+ result = agent.invoke({
102
+ "messages": [{"role": "user", "content": "Create a file with today's date"}]
103
+ })
104
+ ```
105
+
106
+ ## Configuration
107
+
108
+ ```python
109
+ tool = BashTool(
110
+ username="agent", # Custom username (whoami)
111
+ hostname="sandbox", # Custom hostname
112
+ max_commands=1000, # Limit total commands
113
+ max_loop_iterations=10000, # Limit loop iterations
114
+ )
115
+ ```
116
+
117
+ ## Synchronous API
118
+
119
+ ```python
120
+ from bashkit import BashTool
121
+
122
+ tool = BashTool()
123
+ result = tool.execute_sync("echo 'Hello!'")
124
+ print(result.stdout)
125
+ ```
126
+
127
+ ## API Reference
128
+
129
+ ### BashTool
130
+
131
+ - `execute(commands: str) -> ExecResult`: Execute commands asynchronously
132
+ - `execute_sync(commands: str) -> ExecResult`: Execute commands synchronously
133
+ - `description() -> str`: Get tool description
134
+ - `help() -> str`: Get LLM documentation
135
+ - `input_schema() -> str`: Get JSON input schema
136
+ - `output_schema() -> str`: Get JSON output schema
137
+
138
+ ### ExecResult
139
+
140
+ - `stdout: str`: Standard output
141
+ - `stderr: str`: Standard error
142
+ - `exit_code: int`: Exit code (0 = success)
143
+ - `error: Optional[str]`: Error message if execution failed
144
+ - `success: bool`: True if exit_code == 0
145
+ - `to_dict() -> dict`: Convert to dictionary
146
+
147
+ ## License
148
+
149
+ MIT
150
+
@@ -0,0 +1,9 @@
1
+ bashkit\__init__.py,sha256=HROdnwXGjHVQ0y04XdXnrlIs7wPKyJrdgragm2yXU0Y,770
2
+ bashkit\_bashkit.cp39-win_amd64.pyd,sha256=gycW5oS4QTkcjqNXijtsuhrtT11R8S3hB9oFnQY5xQc,5978112
3
+ bashkit\_bashkit.pyi,sha256=wT_nNxhsaLWPSx6uoHEXF84j0uBpk-dMDu6Ffo07hu8,2740
4
+ bashkit\deepagents.py,sha256=mOOq40P1BAdfQohbdVpki_AkCOzPZMZjQpMzNQo2mTQ,13584
5
+ bashkit\langchain.py,sha256=F7xZVxK-qYFPRw4ebz9WuGUlwhHCEjMTPCNu_U3wheQ,5249
6
+ bashkit\py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ bashkit-0.1.4.dist-info\METADATA,sha256=Re1qoMU9C2iRDeflX4ZaIj0WnoE0qJYuugltFS585Dw,4227
8
+ bashkit-0.1.4.dist-info\WHEEL,sha256=H5klTgXu3iVXpFbMzUkXja9m3gL244ExCR0k1sRMImo,95
9
+ bashkit-0.1.4.dist-info\RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.11.5)
3
+ Root-Is-Purelib: false
4
+ Tag: cp39-cp39-win_amd64