mirror of
https://github.com/textcortex/claude-code-sandbox.git
synced 2025-12-23 07:07:34 +00:00
Add mounted folder mode support
Implement mounted folder mode to allow direct directory mounting instead of
copying files into containers. This enables better performance for large
datasets, support for non-git directories, and real-time file changes.
Features:
- Added --mount-folder and --mount-path CLI options
- Added useMountedFolder and mountedFolderPath config properties
- Made file copying conditional based on mode
- Skip git operations gracefully for non-git directories
- Added comprehensive documentation
Authored-By: Gregory Bensa <https://github.com/GregoryBensa>
Original commit: 13fc6ac543
This commit is contained in:
parent
61cfa97de5
commit
ddffc03b1f
5 changed files with 114 additions and 19 deletions
36
README.md
36
README.md
|
|
@ -88,6 +88,8 @@ Options:
|
|||
--no-web Disable web UI (use terminal attach)
|
||||
--no-push Disable automatic branch pushing
|
||||
--no-pr Disable automatic PR creation
|
||||
--mount-folder Enable mounted folder mode
|
||||
--mount-path <path> Specify custom mount path
|
||||
```
|
||||
|
||||
#### `claude-sandbox attach [container-id]`
|
||||
|
|
@ -219,6 +221,8 @@ Create a `claude-sandbox.config.json` file (see `claude-sandbox.config.example.j
|
|||
- `containerPrefix`: Custom prefix for container names
|
||||
- `claudeConfigPath`: Path to Claude configuration file
|
||||
- `dockerSocketPath`: Custom Docker/Podman socket path (auto-detected by default)
|
||||
- `useMountedFolder`: Enable mounted folder mode (default: false)
|
||||
- `mountedFolderPath`: Path to mount into the container (default: current working directory)
|
||||
|
||||
#### Mount Configuration
|
||||
|
||||
|
|
@ -237,6 +241,38 @@ Example use cases:
|
|||
|
||||
## Features
|
||||
|
||||
### Mounted Folder Mode
|
||||
|
||||
Claude Code Sandbox supports mounting directories directly into containers instead of copying files. This is useful for:
|
||||
|
||||
- **Large datasets**: Avoid copying overhead for large files
|
||||
- **Non-git directories**: Work with folders that aren't part of a git repository
|
||||
- **Real-time changes**: File changes sync instantly without copying delays
|
||||
- **Performance**: Direct filesystem access is faster than copying
|
||||
|
||||
#### Using Mounted Folder Mode
|
||||
|
||||
Via CLI:
|
||||
|
||||
```bash
|
||||
# Mount current directory
|
||||
claude-sandbox start --mount-folder
|
||||
|
||||
# Mount a specific directory
|
||||
claude-sandbox start --mount-folder --mount-path /path/to/directory
|
||||
```
|
||||
|
||||
Via configuration file:
|
||||
|
||||
```json
|
||||
{
|
||||
"useMountedFolder": true,
|
||||
"mountedFolderPath": "/path/to/directory"
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: In mounted folder mode, if the directory isn't a git repository, git operations will be skipped and git monitoring will be disabled. This allows Claude Code to work with any directory, not just git repositories.
|
||||
|
||||
### Podman Support
|
||||
|
||||
Claude Code Sandbox now supports Podman as an alternative to Docker. The tool automatically detects whether you're using Docker or Podman by checking for available socket paths:
|
||||
|
|
|
|||
16
src/cli.ts
16
src/cli.ts
|
|
@ -82,6 +82,8 @@ program
|
|||
"Start with 'claude' or 'bash' shell",
|
||||
/^(claude|bash)$/i,
|
||||
)
|
||||
.option("--mount-folder", "Enable mounted folder mode")
|
||||
.option("--mount-path <path>", "Specify custom mount path")
|
||||
.action(async (options) => {
|
||||
console.log(chalk.blue("🚀 Starting Claude Sandbox..."));
|
||||
|
||||
|
|
@ -90,6 +92,12 @@ program
|
|||
if (options.shell) {
|
||||
config.defaultShell = options.shell.toLowerCase();
|
||||
}
|
||||
if (options.mountFolder) {
|
||||
config.useMountedFolder = true;
|
||||
}
|
||||
if (options.mountPath) {
|
||||
config.mountedFolderPath = options.mountPath;
|
||||
}
|
||||
|
||||
const sandbox = new ClaudeSandbox(config);
|
||||
await sandbox.run();
|
||||
|
|
@ -125,6 +133,8 @@ program
|
|||
"Start with 'claude' or 'bash' shell",
|
||||
/^(claude|bash)$/i,
|
||||
)
|
||||
.option("--mount-folder", "Enable mounted folder mode")
|
||||
.option("--mount-path <path>", "Specify custom mount path")
|
||||
.action(async (options) => {
|
||||
console.log(chalk.blue("🚀 Starting new Claude Sandbox container..."));
|
||||
|
||||
|
|
@ -139,6 +149,12 @@ program
|
|||
if (options.shell) {
|
||||
config.defaultShell = options.shell.toLowerCase();
|
||||
}
|
||||
if (options.mountFolder) {
|
||||
config.useMountedFolder = true;
|
||||
}
|
||||
if (options.mountPath) {
|
||||
config.mountedFolderPath = options.mountPath;
|
||||
}
|
||||
|
||||
const sandbox = new ClaudeSandbox(config);
|
||||
await sandbox.run();
|
||||
|
|
|
|||
|
|
@ -25,19 +25,32 @@ export class ContainerManager {
|
|||
await container.start();
|
||||
console.log(chalk.green("✓ Container started"));
|
||||
|
||||
// Copy working directory into container
|
||||
console.log(chalk.blue("• Copying files into container..."));
|
||||
try {
|
||||
await this._copyWorkingDirectory(container, containerConfig.workDir);
|
||||
console.log(chalk.green("✓ Files copied"));
|
||||
// Copy working directory into container (unless using mounted folder mode)
|
||||
if (!this.config.useMountedFolder) {
|
||||
console.log(chalk.blue("• Copying files into container..."));
|
||||
try {
|
||||
await this._copyWorkingDirectory(container, containerConfig.workDir);
|
||||
console.log(chalk.green("✓ Files copied"));
|
||||
} catch (error) {
|
||||
console.error(chalk.red("✗ File copy failed:"), error);
|
||||
// Clean up container on failure
|
||||
await container.stop().catch(() => {});
|
||||
await container.remove().catch(() => {});
|
||||
this.containers.delete(container.id);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.blue("• Using mounted folder mode (skipping file copy)"));
|
||||
}
|
||||
|
||||
// Copy Claude configuration if it exists
|
||||
// Copy Claude configuration if it exists
|
||||
try {
|
||||
await this._copyClaudeConfig(container);
|
||||
|
||||
// Copy git configuration if it exists
|
||||
await this._copyGitConfig(container);
|
||||
} catch (error) {
|
||||
console.error(chalk.red("✗ File copy failed:"), error);
|
||||
console.error(chalk.red("✗ Configuration copy failed:"), error);
|
||||
// Clean up container on failure
|
||||
await container.stop().catch(() => {});
|
||||
await container.remove().catch(() => {});
|
||||
|
|
@ -414,9 +427,17 @@ exec claude --dangerously-skip-permissions' > /start-claude.sh && \\
|
|||
_workDir: string,
|
||||
_credentials: Credentials,
|
||||
): string[] {
|
||||
// NO MOUNTING workspace - we'll copy files instead
|
||||
const volumes: string[] = [];
|
||||
|
||||
// Mount workspace directly if in mounted folder mode
|
||||
if (this.config.useMountedFolder) {
|
||||
const mountPath = this.config.mountedFolderPath || process.cwd();
|
||||
console.log(
|
||||
chalk.blue(`✓ Mounting folder: ${mountPath} → /workspace`),
|
||||
);
|
||||
volumes.push(`${mountPath}:/workspace`);
|
||||
}
|
||||
|
||||
// NO SSH mounting - we'll use GitHub tokens instead
|
||||
|
||||
// Add custom volumes (legacy format)
|
||||
|
|
|
|||
42
src/index.ts
42
src/index.ts
|
|
@ -39,12 +39,28 @@ export class ClaudeSandbox {
|
|||
|
||||
async run(): Promise<void> {
|
||||
try {
|
||||
// Verify we're in a git repository
|
||||
await this.verifyGitRepo();
|
||||
// Verify we're in a git repository (unless using mounted folder mode)
|
||||
let isGitRepo = true;
|
||||
if (!this.config.useMountedFolder) {
|
||||
await this.verifyGitRepo();
|
||||
} else {
|
||||
// Check if it's a git repo, but don't fail if it's not
|
||||
isGitRepo = await this.git.checkIsRepo();
|
||||
if (!isGitRepo) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
"⚠ Not a git repository (OK for mounted folder mode)",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check current branch
|
||||
const currentBranch = await this.git.branchLocal();
|
||||
console.log(chalk.blue(`Current branch: ${currentBranch.current}`));
|
||||
// Check current branch (only if it's a git repo)
|
||||
let currentBranch = null;
|
||||
if (isGitRepo) {
|
||||
currentBranch = await this.git.branchLocal();
|
||||
console.log(chalk.blue(`Current branch: ${currentBranch.current}`));
|
||||
}
|
||||
|
||||
// Determine target branch based on config options (but don't checkout in host repo)
|
||||
let branchName = "";
|
||||
|
|
@ -148,13 +164,17 @@ export class ClaudeSandbox {
|
|||
chalk.green(`✓ Started container: ${containerId.substring(0, 12)}`),
|
||||
);
|
||||
|
||||
// Start monitoring for commits
|
||||
this.gitMonitor.on("commit", async (commit) => {
|
||||
await this.handleCommit(commit);
|
||||
});
|
||||
// Start monitoring for commits (only if it's a git repo)
|
||||
if (isGitRepo) {
|
||||
this.gitMonitor.on("commit", async (commit) => {
|
||||
await this.handleCommit(commit);
|
||||
});
|
||||
|
||||
await this.gitMonitor.start(branchName);
|
||||
console.log(chalk.blue("✓ Git monitoring started"));
|
||||
await this.gitMonitor.start(branchName);
|
||||
console.log(chalk.blue("✓ Git monitoring started"));
|
||||
} else {
|
||||
console.log(chalk.yellow("⚠ Skipping git monitoring (not a git repo)"));
|
||||
}
|
||||
|
||||
// Always launch web UI
|
||||
this.webServer = new WebUIServer(this.docker);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ export interface SandboxConfig {
|
|||
remoteBranch?: string;
|
||||
prNumber?: string;
|
||||
dockerSocketPath?: string;
|
||||
useMountedFolder?: boolean;
|
||||
mountedFolderPath?: string;
|
||||
}
|
||||
|
||||
export interface Credentials {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue