Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/bashkit-js/__test__/runtime-compat/vfs.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ describe("VFS API", () => {
assert.ok(entries.some((e) => e.name === "fsapi.txt"));
});

it("mountReal and unmount", () => {
it("mount and unmount", () => {
const bash = new Bash();
// Mount /tmp as a real filesystem at /host-tmp
bash.mountReal("/tmp", "/host-tmp", true);
// Mount /tmp as a real filesystem at /host-tmp (read-only by default)
bash.mount("/tmp", "/host-tmp");
// The mount should be accessible
const r = bash.executeSync("ls /host-tmp 2>/dev/null; echo status=$?");
assert.ok(r.stdout.includes("status=0"));
Expand Down
44 changes: 21 additions & 23 deletions crates/bashkit-js/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,8 @@ pub struct MountConfig {
pub host_path: String,
/// VFS path where mount appears (defaults to host_path).
pub vfs_path: Option<String>,
/// If true, mount is read-only (default: true).
pub read_only: Option<bool>,
/// If true, mount is read-write (default: false → read-only).
pub writable: Option<bool>,
}

/// Options for creating a Bash or BashTool instance.
Expand Down Expand Up @@ -444,7 +444,7 @@ pub struct BashOptions {
/// Files to mount in the virtual filesystem.
/// Keys are absolute paths, values are file content strings.
pub files: Option<HashMap<String, String>>,
/// Real filesystem mounts. Each entry: { hostPath, vfsPath?, readOnly? }
/// Real filesystem mounts. Each entry: { hostPath, vfsPath?, writable? }
pub mounts: Option<Vec<MountConfig>>,
/// Enable embedded Python execution (`python`/`python3` builtins).
pub python: Option<bool>,
Expand Down Expand Up @@ -849,21 +849,20 @@ impl Bash {

/// Mount a host directory into the VFS at runtime.
///
/// `readOnly` defaults to true when omitted.
/// Read-only by default; pass `writable: true` to enable writes.
#[napi]
pub fn mount_real(
pub fn mount(
&self,
host_path: String,
vfs_path: String,
read_only: Option<bool>,
writable: Option<bool>,
) -> napi::Result<()> {
block_on_with(&self.state, |s| async move {
let bash = s.inner.lock().await;
let ro = read_only.unwrap_or(true);
let mode = if ro {
bashkit::RealFsMode::ReadOnly
} else {
let mode = if writable.unwrap_or(false) {
bashkit::RealFsMode::ReadWrite
} else {
bashkit::RealFsMode::ReadOnly
};
let real_backend = bashkit::RealFs::new(&host_path, mode)
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
Expand Down Expand Up @@ -1222,21 +1221,20 @@ impl BashTool {

/// Mount a host directory into the VFS at runtime.
///
/// `readOnly` defaults to true when omitted.
/// Read-only by default; pass `writable: true` to enable writes.
#[napi]
pub fn mount_real(
pub fn mount(
&self,
host_path: String,
vfs_path: String,
read_only: Option<bool>,
writable: Option<bool>,
) -> napi::Result<()> {
block_on_with(&self.state, |s| async move {
let bash = s.inner.lock().await;
let ro = read_only.unwrap_or(true);
let mode = if ro {
bashkit::RealFsMode::ReadOnly
} else {
let mode = if writable.unwrap_or(false) {
bashkit::RealFsMode::ReadWrite
} else {
bashkit::RealFsMode::ReadOnly
};
let real_backend = bashkit::RealFs::new(&host_path, mode)
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
Expand Down Expand Up @@ -1621,12 +1619,12 @@ fn build_bash_from_state(state: &SharedState, files: Option<&HashMap<String, Str
// Apply real filesystem mounts
if let Some(ref mounts) = state.mounts {
for m in mounts {
let read_only = m.read_only.unwrap_or(true);
builder = match (read_only, &m.vfs_path) {
(true, None) => builder.mount_real_readonly(&m.host_path),
(true, Some(vfs)) => builder.mount_real_readonly_at(&m.host_path, vfs),
(false, None) => builder.mount_real_readwrite(&m.host_path),
(false, Some(vfs)) => builder.mount_real_readwrite_at(&m.host_path, vfs),
let writable = m.writable.unwrap_or(false);
builder = match (writable, &m.vfs_path) {
(false, None) => builder.mount_real_readonly(&m.host_path),
(false, Some(vfs)) => builder.mount_real_readonly_at(&m.host_path, vfs),
(true, None) => builder.mount_real_readwrite(&m.host_path),
(true, Some(vfs)) => builder.mount_real_readwrite_at(&m.host_path, vfs),
};
}
}
Expand Down
18 changes: 9 additions & 9 deletions crates/bashkit-js/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ export interface BashOptions {
* ```typescript
* const bash = new Bash({
* mounts: [
* { path: "/docs", root: "/real/path/to/docs", readOnly: true },
* { path: "/docs", root: "/real/path/to/docs" },
* ],
* });
* ```
*/
mounts?: Array<{ path: string; root: string; readOnly?: boolean }>;
mounts?: Array<{ path: string; root: string; writable?: boolean }>;
/**
* Enable embedded Python execution (`python`/`python3` builtins).
*
Expand Down Expand Up @@ -152,7 +152,7 @@ function toNativeOptions(
mounts: options?.mounts?.map((m) => ({
hostPath: m.root,
vfsPath: m.path,
readOnly: m.readOnly,
writable: m.writable,
})),
python: options?.python,
externalFunctions: options?.externalFunctions,
Expand Down Expand Up @@ -421,9 +421,9 @@ export class Bash {
return this.native.fs();
}

/** Mount a host directory into the VFS. readOnly defaults to true. */
mountReal(hostPath: string, vfsPath: string, readOnly?: boolean): void {
this.native.mountReal(hostPath, vfsPath, readOnly);
/** Mount a host directory into the VFS. Read-only by default; pass writable: true to enable writes. */
mount(hostPath: string, vfsPath: string, writable?: boolean): void {
this.native.mount(hostPath, vfsPath, writable);
}

/** Unmount a previously mounted filesystem. */
Expand Down Expand Up @@ -653,9 +653,9 @@ export class BashTool {
return this.native.fs();
}

/** Mount a host directory into the VFS. readOnly defaults to true. */
mountReal(hostPath: string, vfsPath: string, readOnly?: boolean): void {
this.native.mountReal(hostPath, vfsPath, readOnly);
/** Mount a host directory into the VFS. Read-only by default; pass writable: true to enable writes. */
mount(hostPath: string, vfsPath: string, writable?: boolean): void {
this.native.mount(hostPath, vfsPath, writable);
}

/** Unmount a previously mounted filesystem. */
Expand Down
26 changes: 16 additions & 10 deletions crates/bashkit-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,28 +99,34 @@ fs.symlink("/data/link", "/data/blob.bin")
fs.chmod("/data/blob.bin", 0o644)
```

### Files and Mounts

```python
from bashkit import Bash, FileSystem

# Text files (in-memory, writable)
bash = Bash(files={"/config/app.conf": "debug=true\n"})

# Real filesystem mounts (read-only by default)
bash = Bash(mounts=[
{"host_path": "/path/to/data", "vfs_path": "/data"},
{"host_path": "/path/to/workspace", "vfs_path": "/workspace", "writable": True},
])
```

### Live Mounts

```python
from bashkit import Bash, FileSystem

bash = Bash()
workspace = FileSystem.real("/path/to/workspace", readwrite=True)
workspace = FileSystem.real("/path/to/workspace", writable=True)
bash.mount("/workspace", workspace)

bash.execute_sync("echo 'hello' > /workspace/demo.txt")
bash.unmount("/workspace")
```

`Bash` and `BashTool` also still support constructor-time mounts:

- `mount_text=[("/path", "content")]`
- `mount_readonly_text=[("/path", "content")]`
- `mount_real_readonly=["/host/path"]`
- `mount_real_readonly_at=[("/host/path", "/vfs/path")]`
- `mount_real_readwrite=["/host/path"]`
- `mount_real_readwrite_at=[("/host/path", "/vfs/path")]`

### BashTool — Convenience Wrapper for AI Agents

`BashTool` is a convenience wrapper specifically designed for AI agents. It wraps `Bash` and adds contract metadata (`description`, Markdown `help`, `system_prompt`, JSON schemas) needed by tool-use protocols. Use this when integrating with LangChain, PydanticAI, or similar agent frameworks.
Expand Down
20 changes: 7 additions & 13 deletions crates/bashkit-python/bashkit/_bashkit.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class FileSystem:

def __init__(self) -> None: ...
@staticmethod
def real(host_path: str, readwrite: bool = False) -> FileSystem: ...
def real(host_path: str, writable: bool = False) -> FileSystem: ...
def read_file(self, path: str) -> bytes: ...
def write_file(self, path: str, content: bytes) -> None: ...
def append_file(self, path: str, content: bytes) -> None: ...
Expand Down Expand Up @@ -61,15 +61,12 @@ class Bash:
hostname: str | None = None,
max_commands: int | None = None,
max_loop_iterations: int | None = None,
max_memory: int | None = None,
python: bool = False,
external_functions: list[str] | None = None,
external_handler: ExternalHandler | None = None,
mount_text: list[tuple[str, str]] | None = None,
mount_readonly_text: list[tuple[str, str]] | None = None,
mount_real_readonly: list[str] | None = None,
mount_real_readonly_at: list[tuple[str, str]] | None = None,
mount_real_readwrite: list[str] | None = None,
mount_real_readwrite_at: list[tuple[str, str]] | None = None,
files: dict[str, str] | None = None,
mounts: list[dict[str, Any]] | None = None,
) -> None: ...
async def execute(self, commands: str) -> ExecResult: ...
def execute_sync(self, commands: str) -> ExecResult: ...
Expand Down Expand Up @@ -122,12 +119,9 @@ class BashTool:
hostname: str | None = None,
max_commands: int | None = None,
max_loop_iterations: int | None = None,
mount_text: list[tuple[str, str]] | None = None,
mount_readonly_text: list[tuple[str, str]] | None = None,
mount_real_readonly: list[str] | None = None,
mount_real_readonly_at: list[tuple[str, str]] | None = None,
mount_real_readwrite: list[str] | None = None,
mount_real_readwrite_at: list[tuple[str, str]] | None = None,
max_memory: int | None = None,
files: dict[str, str] | None = None,
mounts: list[dict[str, Any]] | None = None,
) -> None: ...
async def execute(self, commands: str) -> ExecResult: ...
def execute_sync(self, commands: str) -> ExecResult: ...
Expand Down
Loading
Loading