sandboxbox 2.0.6 ā 2.0.8
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.
- package/CLAUDE.md +205 -0
- package/Dockerfile.local-workspace +56 -0
- package/Dockerfile.simple +18 -0
- package/cli.js +192 -171
- package/file.txt +1 -0
- package/launch-sandboxbox.bat +75 -0
- package/launch-sandboxbox.sh +81 -0
- package/package.json +1 -1
- package/test-cross-platform.sh +78 -0
- package/test-merge-workflow.sh +58 -0
- package/test-sandbox-workflow.sh +45 -0
- package/utils/claude-workspace.js +77 -0
- package/utils/colors.js +15 -0
- package/utils/podman.js +116 -0
package/CLAUDE.md
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
# SandboxBox Technical Documentation
|
2
|
+
|
3
|
+
## Architecture Overview
|
4
|
+
SandboxBox provides portable containerized development environments using Podman with automatic WSL machine management and Claude Code integration.
|
5
|
+
|
6
|
+
## Core Components
|
7
|
+
|
8
|
+
### CLI (cli.js)
|
9
|
+
- Main entry point with automatic Podman machine management
|
10
|
+
- Commands: build, run, shell, version, help
|
11
|
+
- Auto-detects and starts Podman machine when needed
|
12
|
+
- Shell execution with Windows compatibility (`shell: process.platform === 'win32'`)
|
13
|
+
|
14
|
+
### Podman Downloader (scripts/download-podman.js)
|
15
|
+
- Cross-platform binary downloads from GitHub releases
|
16
|
+
- PowerShell ZIP extraction on Windows (no external dependencies)
|
17
|
+
- Automatic version detection and binary path resolution
|
18
|
+
|
19
|
+
### Container Images
|
20
|
+
- **sandboxbox-auth**: Full development environment with Claude Code
|
21
|
+
- **sandboxbox-local**: Local repository workspace (symlink approach)
|
22
|
+
|
23
|
+
## Windows Compatibility Fixes
|
24
|
+
|
25
|
+
### Critical PowerShell ZIP Extraction
|
26
|
+
```javascript
|
27
|
+
// scripts/download-podman.js:81
|
28
|
+
execSync(`powershell -Command "${psCommand}"`, {
|
29
|
+
stdio: 'pipe',
|
30
|
+
cwd: __dirname,
|
31
|
+
shell: true // REQUIRED for PowerShell commands
|
32
|
+
});
|
33
|
+
```
|
34
|
+
|
35
|
+
### Shell Execution Pattern
|
36
|
+
All `execSync()` calls must include:
|
37
|
+
```javascript
|
38
|
+
{
|
39
|
+
stdio: 'pipe',
|
40
|
+
shell: process.platform === 'win32' // Windows compatibility
|
41
|
+
}
|
42
|
+
```
|
43
|
+
|
44
|
+
### Auto Podman Machine Management
|
45
|
+
```javascript
|
46
|
+
// cli.js checkPodman() function
|
47
|
+
if (process.platform === 'win32' && isBundled) {
|
48
|
+
try {
|
49
|
+
execSync(`"${podmanPath}" info`, { ...execOptions, stdio: 'pipe' });
|
50
|
+
} catch (infoError) {
|
51
|
+
if (infoError.message.includes('Cannot connect to Podman')) {
|
52
|
+
// Auto-start existing machine
|
53
|
+
execSync(`"${podmanPath}" machine start`, { stdio: 'inherit' });
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
```
|
58
|
+
|
59
|
+
## Claude Code Integration
|
60
|
+
|
61
|
+
### Authentication Transfer
|
62
|
+
Mount complete Claude session data:
|
63
|
+
```bash
|
64
|
+
-v "$HOME/.claude:/root/.claude"
|
65
|
+
```
|
66
|
+
|
67
|
+
### Environment Variables
|
68
|
+
Key variables to transfer:
|
69
|
+
```bash
|
70
|
+
ANTHROPIC_AUTH_TOKEN
|
71
|
+
CLAUDECODE=1
|
72
|
+
ANTHROPIC_BASE_URL
|
73
|
+
```
|
74
|
+
|
75
|
+
### Git Identity Transfer
|
76
|
+
```bash
|
77
|
+
-v "$HOME/.gitconfig:/root/.gitconfig:ro"
|
78
|
+
-v "$HOME/.ssh:/root/.ssh:ro"
|
79
|
+
```
|
80
|
+
|
81
|
+
## Local Repository Workflow
|
82
|
+
|
83
|
+
### Architecture
|
84
|
+
- Container mounts local repo as `/project:rw`
|
85
|
+
- Creates symlink `/workspace/project` ā `/project`
|
86
|
+
- Works directly with local repository (no cloning needed)
|
87
|
+
- Changes persist to host folder automatically
|
88
|
+
|
89
|
+
### Container Command
|
90
|
+
```bash
|
91
|
+
podman run --rm \
|
92
|
+
-v "/path/to/repo:/project:rw" \
|
93
|
+
-v "$HOME/.claude:/root/.claude" \
|
94
|
+
-v "$HOME/.gitconfig:/root/.gitconfig:ro" \
|
95
|
+
-v "$HOME/.ssh:/root/.ssh" \
|
96
|
+
-e "ANTHROPIC_AUTH_TOKEN=..." \
|
97
|
+
-e "CLAUDECODE=1" \
|
98
|
+
sandboxbox-local:latest
|
99
|
+
```
|
100
|
+
|
101
|
+
### Dockerfile.local-workspace
|
102
|
+
```dockerfile
|
103
|
+
# Creates symlink to mounted repository
|
104
|
+
ln -sf "$REPO_PATH" "$WORKSPACE_DIR"
|
105
|
+
cd "$WORKSPACE_DIR"
|
106
|
+
exec claude # Changes save directly to local repo
|
107
|
+
```
|
108
|
+
|
109
|
+
## Complete Workflow Example
|
110
|
+
|
111
|
+
1. **Setup**: Build sandboxbox-local image
|
112
|
+
2. **Mount**: Local repository as `/project:rw`
|
113
|
+
3. **Auth Transfer**: Mount `.claude`, `.gitconfig`, `.ssh`
|
114
|
+
4. **Edit**: Claude Code modifies files in `/workspace/project` (symlink to `/project`)
|
115
|
+
5. **Commit**: Changes made directly to local repository
|
116
|
+
6. **Persist**: No additional push/pull needed - changes already in host folder
|
117
|
+
|
118
|
+
## Troubleshooting
|
119
|
+
|
120
|
+
### "unzip command not found"
|
121
|
+
**Solution**: Use PowerShell ZIP extraction with `shell: true`
|
122
|
+
|
123
|
+
### "Cannot connect to Podman"
|
124
|
+
**Solution**: Automatic machine start in checkPodman() function
|
125
|
+
|
126
|
+
### Build context issues
|
127
|
+
**Solution**: Use direct Podman build, then tag for SandboxBox
|
128
|
+
|
129
|
+
### Git identity errors
|
130
|
+
**Solution**: Mount `.gitconfig:ro` for user identity transfer
|
131
|
+
|
132
|
+
### Path resolution issues
|
133
|
+
**Solution**: Use explicit REPO_PATH environment variable
|
134
|
+
|
135
|
+
## Command Isolation Principles
|
136
|
+
|
137
|
+
### Critical Architecture Distinction
|
138
|
+
- **`run` command**: Creates isolated temporary environment - changes DO NOT affect host
|
139
|
+
- **`claude` command**: Mounts local repository directly - changes DO affect host
|
140
|
+
- **`shell` command**: Creates isolated temporary environment - changes DO NOT affect host
|
141
|
+
|
142
|
+
### Isolation Implementation (run/shell commands)
|
143
|
+
```javascript
|
144
|
+
// Creates temporary directory with copied project
|
145
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'sandboxbox-'));
|
146
|
+
const tempProjectDir = join(tempDir, projectName);
|
147
|
+
|
148
|
+
// Cross-platform file copying with hidden files (.git, etc.)
|
149
|
+
if (process.platform === 'win32') {
|
150
|
+
execSync(`powershell -Command "Copy-Item -Path '${projectDir}\\*' -Destination '${tempProjectDir}' -Recurse -Force"`, {
|
151
|
+
stdio: 'pipe',
|
152
|
+
shell: true
|
153
|
+
});
|
154
|
+
// Copy hidden files separately
|
155
|
+
execSync(`powershell -Command "Get-ChildItem -Path '${projectDir}' -Force -Name | Where-Object { $_ -like '.*' } | ForEach-Object { Copy-Item -Path (Join-Path '${projectDir}' $_) -Destination '${tempProjectDir}' -Recurse -Force }"`, {
|
156
|
+
stdio: 'pipe',
|
157
|
+
shell: true
|
158
|
+
});
|
159
|
+
} else {
|
160
|
+
execSync(`cp -r "${projectDir}"/.* "${tempProjectDir}/" 2>/dev/null || true`, {
|
161
|
+
stdio: 'pipe',
|
162
|
+
shell: true
|
163
|
+
});
|
164
|
+
execSync(`cp -r "${projectDir}"/* "${tempProjectDir}/"`, {
|
165
|
+
stdio: 'pipe',
|
166
|
+
shell: true
|
167
|
+
});
|
168
|
+
}
|
169
|
+
|
170
|
+
// Automatic cleanup on exit
|
171
|
+
const cleanup = () => {
|
172
|
+
try {
|
173
|
+
rmSync(tempDir, { recursive: true, force: true });
|
174
|
+
} catch (cleanupError) {
|
175
|
+
// Ignore cleanup errors
|
176
|
+
}
|
177
|
+
};
|
178
|
+
|
179
|
+
process.on('exit', cleanup);
|
180
|
+
process.on('SIGINT', () => { cleanup(); process.exit(130); });
|
181
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(143); });
|
182
|
+
```
|
183
|
+
|
184
|
+
### Container Naming and Cleanup
|
185
|
+
- Use random short names: `sandboxbox-run-${Math.random().toString(36).substr(2, 9)}`
|
186
|
+
- Force cleanup: `podman rm -f container-name`
|
187
|
+
- Automatic cleanup handlers for all exit scenarios
|
188
|
+
- Cross-platform signal handling (SIGINT, SIGTERM)
|
189
|
+
|
190
|
+
### Cross-Platform Path Handling
|
191
|
+
```javascript
|
192
|
+
// Normalize Windows paths for podman cp command
|
193
|
+
const normalizedProjectDir = projectDir.replace(/\\/g, '/');
|
194
|
+
```
|
195
|
+
|
196
|
+
## Version Management
|
197
|
+
- Publish new version when fixing critical Windows issues
|
198
|
+
- Clear npm cache: `npm cache clean --force`
|
199
|
+
- Use specific version: `npx sandboxbox@latest`
|
200
|
+
|
201
|
+
## File Cleanup Requirements
|
202
|
+
- All temporary containers auto-cleanup on exit
|
203
|
+
- All temporary directories auto-cleanup on exit
|
204
|
+
- Error handling for cleanup failures (ignore errors)
|
205
|
+
- Signal handlers ensure cleanup on interrupts
|
@@ -0,0 +1,56 @@
|
|
1
|
+
FROM node:20
|
2
|
+
|
3
|
+
# Install basic development tools
|
4
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
5
|
+
git \
|
6
|
+
curl \
|
7
|
+
bash \
|
8
|
+
sudo \
|
9
|
+
nano \
|
10
|
+
vim \
|
11
|
+
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
12
|
+
|
13
|
+
# Create workspace directory
|
14
|
+
WORKDIR /workspace
|
15
|
+
|
16
|
+
# Install Claude Code globally
|
17
|
+
RUN npm install -g @anthropic-ai/claude-code@latest
|
18
|
+
|
19
|
+
# Create startup script for local workspace (no cloning needed)
|
20
|
+
RUN echo '#!/bin/bash\n\
|
21
|
+
set -e\n\
|
22
|
+
\n\
|
23
|
+
# Get repository path from environment or default\n\
|
24
|
+
REPO_PATH=${REPO_PATH:-"/project"}\n\
|
25
|
+
WORKSPACE_DIR="/workspace/project"\n\
|
26
|
+
\n\
|
27
|
+
echo "š Starting SandboxBox with Claude Code..."\n\
|
28
|
+
echo "š Working with local repository: $REPO_PATH"\n\
|
29
|
+
echo "šÆ Workspace: $WORKSPACE_DIR"\n\
|
30
|
+
\n\
|
31
|
+
# Create symbolic link to the mounted repository\n\
|
32
|
+
if [ -d "$REPO_PATH" ] && [ -d "$REPO_PATH/.git" ]; then\n\
|
33
|
+
echo "š Creating workspace symlink to local repository..."\n\
|
34
|
+
ln -sf "$REPO_PATH" "$WORKSPACE_DIR"\n\
|
35
|
+
cd "$WORKSPACE_DIR"\n\
|
36
|
+
echo "ā
Workspace linked successfully!"\n\
|
37
|
+
echo "š Current status:"\n\
|
38
|
+
git status\n\
|
39
|
+
echo "š Repository contents:"\n\
|
40
|
+
ls -la\n\
|
41
|
+
echo ""\n\
|
42
|
+
echo "š§ Starting Claude Code..."\n\
|
43
|
+
echo "š” Tip: Changes will be saved directly to the local repository"\n\
|
44
|
+
echo "š” Use Ctrl+C to exit Claude Code"\n\
|
45
|
+
exec claude\n\
|
46
|
+
else\n\
|
47
|
+
echo "ā Error: $REPO_PATH is not a valid git repository"\n\
|
48
|
+
echo "Please ensure the path contains a .git directory"\n\
|
49
|
+
echo "Contents of $REPO_PATH:"\n\
|
50
|
+
ls -la "$REPO_PATH" 2>/dev/null || echo "Directory not accessible"\n\
|
51
|
+
exit 1\n\
|
52
|
+
fi' > /usr/local/bin/start-local-sandbox.sh && \
|
53
|
+
chmod +x /usr/local/bin/start-local-sandbox.sh
|
54
|
+
|
55
|
+
# Set default command
|
56
|
+
CMD ["/usr/local/bin/start-local-sandbox.sh"]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
FROM node:20
|
2
|
+
|
3
|
+
# Install basic development tools
|
4
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
5
|
+
git \
|
6
|
+
bash \
|
7
|
+
curl \
|
8
|
+
nano \
|
9
|
+
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
10
|
+
|
11
|
+
# Create workspace directory
|
12
|
+
WORKDIR /workspace
|
13
|
+
|
14
|
+
# Set up non-root user
|
15
|
+
USER node
|
16
|
+
|
17
|
+
# Install Claude Code
|
18
|
+
RUN npm install -g @anthropic-ai/claude-code@latest
|
package/cli.js
CHANGED
@@ -1,41 +1,25 @@
|
|
1
|
-
#!/usr/bin/env node
|
1
|
+
#!/usr/bin/env node
|
2
2
|
|
3
3
|
/**
|
4
4
|
* SandboxBox CLI - Portable Container Runner with Podman
|
5
5
|
*
|
6
|
-
* Cross-platform container runner using Podman
|
6
|
+
* Cross-platform container runner using Podman with Claude Code integration
|
7
7
|
* Works on Windows, macOS, and Linux
|
8
|
-
*
|
9
|
-
* Simple usage:
|
10
|
-
* npx sandboxbox build # Build container from Dockerfile
|
11
|
-
* npx sandboxbox run <project> # Run project in container
|
12
|
-
* npx sandboxbox shell <project> # Interactive shell
|
13
8
|
*/
|
14
9
|
|
15
|
-
import { readFileSync, existsSync } from 'fs';
|
10
|
+
import { readFileSync, existsSync, writeFileSync, mkdtempSync, rmSync } from 'fs';
|
16
11
|
import { execSync } from 'child_process';
|
12
|
+
import { resolve, dirname, join } from 'path';
|
17
13
|
import { fileURLToPath } from 'url';
|
18
|
-
import {
|
14
|
+
import { tmpdir } from 'os';
|
15
|
+
|
16
|
+
import { color } from './utils/colors.js';
|
17
|
+
import { checkPodman, getPodmanPath } from './utils/podman.js';
|
18
|
+
import { buildClaudeContainerCommand, createClaudeDockerfile } from './utils/claude-workspace.js';
|
19
19
|
|
20
20
|
const __filename = fileURLToPath(import.meta.url);
|
21
21
|
const __dirname = dirname(__filename);
|
22
22
|
|
23
|
-
// Colors for output
|
24
|
-
const colors = {
|
25
|
-
red: '\x1b[31m',
|
26
|
-
green: '\x1b[32m',
|
27
|
-
yellow: '\x1b[33m',
|
28
|
-
blue: '\x1b[34m',
|
29
|
-
magenta: '\x1b[35m',
|
30
|
-
cyan: '\x1b[36m',
|
31
|
-
white: '\x1b[37m',
|
32
|
-
reset: '\x1b[0m'
|
33
|
-
};
|
34
|
-
|
35
|
-
function color(colorName, text) {
|
36
|
-
return `${colors[colorName]}${text}${colors.reset}`;
|
37
|
-
}
|
38
|
-
|
39
23
|
function showBanner() {
|
40
24
|
console.log(color('cyan', 'š¦ SandboxBox - Portable Container Runner'));
|
41
25
|
console.log(color('cyan', 'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
@@ -47,161 +31,84 @@ function showHelp() {
|
|
47
31
|
console.log(' npx sandboxbox <command> [options]');
|
48
32
|
console.log('');
|
49
33
|
console.log(color('yellow', 'Commands:'));
|
50
|
-
console.log(' build [dockerfile] Build container from Dockerfile
|
34
|
+
console.log(' build [dockerfile] Build container from Dockerfile');
|
51
35
|
console.log(' run <project-dir> [cmd] Run project in container');
|
52
|
-
console.log(' shell <project-dir> Start interactive shell
|
36
|
+
console.log(' shell <project-dir> Start interactive shell');
|
37
|
+
console.log(' claude <project-dir> Start Claude Code with local repository');
|
53
38
|
console.log(' version Show version information');
|
54
39
|
console.log('');
|
55
40
|
console.log(color('yellow', 'Examples:'));
|
56
41
|
console.log(' npx sandboxbox build');
|
57
|
-
console.log(' npx sandboxbox
|
58
|
-
console.log(' npx sandboxbox run ./my-project');
|
42
|
+
console.log(' npx sandboxbox claude ./my-project');
|
59
43
|
console.log(' npx sandboxbox run ./my-project "npm test"');
|
60
44
|
console.log(' npx sandboxbox shell ./my-project');
|
61
45
|
console.log('');
|
62
46
|
console.log(color('yellow', 'Requirements:'));
|
63
|
-
console.log(' - Podman (
|
47
|
+
console.log(' - Podman (auto-downloaded if needed)');
|
64
48
|
console.log(' - Works on Windows, macOS, and Linux!');
|
65
49
|
console.log('');
|
66
|
-
console.log(color('magenta', 'š Fast startup ⢠True isolation ā¢
|
50
|
+
console.log(color('magenta', 'š Fast startup ⢠True isolation ⢠Claude Code integration'));
|
67
51
|
}
|
68
52
|
|
69
|
-
function
|
70
|
-
|
71
|
-
const
|
72
|
-
const arch = process.arch === 'arm64' ? 'arm64' : 'amd64';
|
73
|
-
let bundledPodman;
|
74
|
-
|
75
|
-
if (platform === 'win32') {
|
76
|
-
bundledPodman = resolve(__dirname, 'bin', 'podman.exe');
|
77
|
-
} else if (platform === 'darwin') {
|
78
|
-
bundledPodman = resolve(__dirname, 'bin', 'podman');
|
79
|
-
} else {
|
80
|
-
bundledPodman = resolve(__dirname, 'bin', `podman-remote-static-linux_${arch}`);
|
81
|
-
}
|
53
|
+
function buildClaudeContainer() {
|
54
|
+
const dockerfilePath = resolve(__dirname, 'Dockerfile.claude');
|
55
|
+
const dockerfileContent = createClaudeDockerfile();
|
82
56
|
|
83
|
-
|
84
|
-
|
57
|
+
writeFileSync(dockerfilePath, dockerfileContent);
|
58
|
+
console.log(color('blue', 'šļø Building Claude Code container...'));
|
59
|
+
|
60
|
+
const podmanPath = checkPodman();
|
61
|
+
if (!podmanPath) return false;
|
62
|
+
|
63
|
+
try {
|
64
|
+
execSync(`"${podmanPath}" build -f "${dockerfilePath}" -t sandboxbox-local:latest .`, {
|
65
|
+
stdio: 'inherit',
|
66
|
+
cwd: __dirname,
|
67
|
+
shell: process.platform === 'win32'
|
68
|
+
});
|
69
|
+
console.log(color('green', '\nā
Claude Code container built successfully!'));
|
70
|
+
return true;
|
71
|
+
} catch (error) {
|
72
|
+
console.log(color('red', `\nā Build failed: ${error.message}`));
|
73
|
+
return false;
|
85
74
|
}
|
86
|
-
// Fall back to system podman
|
87
|
-
return 'podman';
|
88
75
|
}
|
89
76
|
|
90
|
-
function
|
91
|
-
|
92
|
-
|
77
|
+
function runClaudeWorkspace(projectDir, command = 'claude') {
|
78
|
+
if (!existsSync(projectDir)) {
|
79
|
+
console.log(color('red', `ā Project directory not found: ${projectDir}`));
|
80
|
+
return false;
|
81
|
+
}
|
93
82
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
shell: process.platform === 'win32' // Use shell on Windows for better compatibility
|
100
|
-
};
|
101
|
-
|
102
|
-
const version = execSync(`"${podmanPath}" --version`, execOptions).trim();
|
103
|
-
console.log(color('green', `ā
${version}${isBundled ? ' (bundled)' : ''}`));
|
104
|
-
|
105
|
-
// Check if Podman machine is running (Windows only)
|
106
|
-
if (process.platform === 'win32' && isBundled) {
|
107
|
-
try {
|
108
|
-
execSync(`"${podmanPath}" info`, { ...execOptions, stdio: 'pipe' });
|
109
|
-
} catch (infoError) {
|
110
|
-
if (infoError.message.includes('Cannot connect to Podman')) {
|
111
|
-
console.log(color('yellow', '\nš§ Podman machine not running, auto-initializing...'));
|
83
|
+
if (!existsSync(resolve(projectDir, '.git'))) {
|
84
|
+
console.log(color('red', `ā Not a git repository: ${projectDir}`));
|
85
|
+
console.log(color('yellow', 'Please run this command in a git repository directory'));
|
86
|
+
return false;
|
87
|
+
}
|
112
88
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
execSync(`"${podmanPath}" machine start`, {
|
117
|
-
stdio: 'inherit',
|
118
|
-
cwd: __dirname,
|
119
|
-
shell: process.platform === 'win32'
|
120
|
-
});
|
121
|
-
console.log(color('green', '\nā
Podman machine started successfully in rootless mode!'));
|
122
|
-
} catch (startError) {
|
123
|
-
if (startError.message.includes('not found') || startError.message.includes('does not exist')) {
|
124
|
-
// Machine doesn't exist, initialize it
|
125
|
-
execSync(`"${podmanPath}" machine init`, {
|
126
|
-
stdio: 'inherit',
|
127
|
-
cwd: __dirname,
|
128
|
-
shell: process.platform === 'win32'
|
129
|
-
});
|
130
|
-
|
131
|
-
// Start the newly initialized machine
|
132
|
-
execSync(`"${podmanPath}" machine start`, {
|
133
|
-
stdio: 'inherit',
|
134
|
-
cwd: __dirname,
|
135
|
-
shell: process.platform === 'win32'
|
136
|
-
});
|
137
|
-
|
138
|
-
console.log(color('green', '\nā
Podman machine initialized and started successfully in rootless mode!'));
|
139
|
-
} else {
|
140
|
-
throw startError;
|
141
|
-
}
|
142
|
-
}
|
143
|
-
} catch (machineError) {
|
144
|
-
console.log(color('red', `\nā Failed to initialize Podman machine: ${machineError.message}`));
|
145
|
-
console.log(color('yellow', '\nš” Please run manually:'));
|
146
|
-
console.log(' podman machine init && podman machine start');
|
147
|
-
return null;
|
148
|
-
}
|
149
|
-
}
|
150
|
-
}
|
151
|
-
}
|
89
|
+
console.log(color('blue', 'š Starting Claude Code with local repository...'));
|
90
|
+
console.log(color('yellow', `Project: ${projectDir}`));
|
91
|
+
console.log(color('yellow', `Command: ${command}\n`));
|
152
92
|
|
153
|
-
|
154
|
-
|
155
|
-
// If no bundled Podman and system Podman not found, try to download
|
156
|
-
if (!isBundled) {
|
157
|
-
console.log(color('red', 'ā Podman not found'));
|
158
|
-
console.log(color('yellow', '\nš¦ Auto-downloading Podman...'));
|
93
|
+
const podmanPath = checkPodman();
|
94
|
+
if (!podmanPath) return false;
|
159
95
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
};
|
172
|
-
const version = execSync(`"${newPodmanPath}" --version`, execOptions).trim();
|
173
|
-
console.log(color('green', `\nā
${version} (auto-downloaded)`));
|
174
|
-
return newPodmanPath;
|
175
|
-
} catch (downloadError) {
|
176
|
-
console.log(color('red', `\nā Auto-download failed: ${downloadError.message}`));
|
177
|
-
console.log(color('yellow', '\nš” Please install Podman manually:'));
|
178
|
-
}
|
179
|
-
} else {
|
180
|
-
console.log(color('red', 'ā Podman not found'));
|
181
|
-
console.log(color('yellow', '\nš” Please install Podman manually:'));
|
182
|
-
}
|
183
|
-
|
184
|
-
console.log('');
|
185
|
-
if (process.platform === 'win32') {
|
186
|
-
console.log(color('cyan', ' Windows:'));
|
187
|
-
console.log(' winget install RedHat.Podman');
|
188
|
-
} else if (process.platform === 'darwin') {
|
189
|
-
console.log(color('cyan', ' macOS:'));
|
190
|
-
console.log(' brew install podman');
|
191
|
-
console.log(' podman machine init && podman machine start');
|
192
|
-
} else {
|
193
|
-
console.log(color('cyan', ' Linux:'));
|
194
|
-
console.log(' sudo apt-get install podman # Ubuntu/Debian');
|
195
|
-
console.log(' sudo dnf install podman # Fedora');
|
196
|
-
}
|
197
|
-
console.log('');
|
198
|
-
return null;
|
96
|
+
try {
|
97
|
+
const containerCommand = buildClaudeContainerCommand(projectDir, podmanPath, command);
|
98
|
+
execSync(containerCommand, {
|
99
|
+
stdio: 'inherit',
|
100
|
+
shell: process.platform === 'win32'
|
101
|
+
});
|
102
|
+
console.log(color('green', '\nā
Claude Code session completed!'));
|
103
|
+
return true;
|
104
|
+
} catch (error) {
|
105
|
+
console.log(color('red', `\nā Claude Code failed: ${error.message}`));
|
106
|
+
return false;
|
199
107
|
}
|
200
108
|
}
|
201
109
|
|
202
110
|
async function main() {
|
203
111
|
const args = process.argv.slice(2);
|
204
|
-
|
205
112
|
showBanner();
|
206
113
|
|
207
114
|
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
@@ -228,16 +135,12 @@ async function main() {
|
|
228
135
|
if (!buildPodman) process.exit(1);
|
229
136
|
|
230
137
|
try {
|
231
|
-
console.log('');
|
232
138
|
execSync(`"${buildPodman}" build -f "${dockerfilePath}" -t sandboxbox:latest .`, {
|
233
139
|
stdio: 'inherit',
|
234
|
-
cwd:
|
140
|
+
cwd: dirname(dockerfilePath),
|
235
141
|
shell: process.platform === 'win32'
|
236
142
|
});
|
237
|
-
console.log('');
|
238
|
-
console.log(color('green', 'ā
Container built successfully!'));
|
239
|
-
console.log(color('cyan', '\nš” Next steps:'));
|
240
|
-
console.log(' npx sandboxbox run ./my-project');
|
143
|
+
console.log(color('green', '\nā
Container built successfully!'));
|
241
144
|
} catch (error) {
|
242
145
|
console.log(color('red', `\nā Build failed: ${error.message}`));
|
243
146
|
process.exit(1);
|
@@ -259,21 +162,74 @@ async function main() {
|
|
259
162
|
process.exit(1);
|
260
163
|
}
|
261
164
|
|
262
|
-
console.log(color('blue', 'š Running project in container...'));
|
165
|
+
console.log(color('blue', 'š Running project in isolated container...'));
|
263
166
|
console.log(color('yellow', `Project: ${projectDir}`));
|
264
167
|
console.log(color('yellow', `Command: ${cmd}\n`));
|
168
|
+
console.log(color('cyan', 'š¦ Note: Changes will NOT affect host files (isolated environment)'));
|
265
169
|
|
266
170
|
const runPodman = checkPodman();
|
267
171
|
if (!runPodman) process.exit(1);
|
268
172
|
|
269
173
|
try {
|
270
|
-
|
271
|
-
|
174
|
+
// Create a temporary directory for isolation
|
175
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'sandboxbox-'));
|
176
|
+
const projectName = projectDir.split(/[\\\/]/).pop() || 'project';
|
177
|
+
const tempProjectDir = join(tempDir, projectName);
|
178
|
+
|
179
|
+
// Copy project to temporary directory (creates isolation)
|
180
|
+
// First create the directory
|
181
|
+
execSync(`mkdir -p "${tempProjectDir}"`, {
|
182
|
+
stdio: 'pipe',
|
183
|
+
shell: true
|
184
|
+
});
|
185
|
+
|
186
|
+
if (process.platform === 'win32') {
|
187
|
+
// Windows approach - include hidden files like .git
|
188
|
+
execSync(`powershell -Command "Copy-Item -Path '${projectDir}\\*' -Destination '${tempProjectDir}' -Recurse -Force -Exclude 'node_modules'"`, {
|
189
|
+
stdio: 'pipe',
|
190
|
+
shell: true
|
191
|
+
});
|
192
|
+
// Also copy hidden files separately
|
193
|
+
execSync(`powershell -Command "Get-ChildItem -Path '${projectDir}' -Force -Name | Where-Object { $_ -like '.*' } | ForEach-Object { Copy-Item -Path (Join-Path '${projectDir}' $_) -Destination '${tempProjectDir}' -Recurse -Force }"`, {
|
194
|
+
stdio: 'pipe',
|
195
|
+
shell: true
|
196
|
+
});
|
197
|
+
} else {
|
198
|
+
// Unix approach - include hidden files
|
199
|
+
execSync(`cp -r "${projectDir}"/.* "${tempProjectDir}/" 2>/dev/null || true`, {
|
200
|
+
stdio: 'pipe',
|
201
|
+
shell: true
|
202
|
+
});
|
203
|
+
execSync(`cp -r "${projectDir}"/* "${tempProjectDir}/"`, {
|
204
|
+
stdio: 'pipe',
|
205
|
+
shell: true
|
206
|
+
});
|
207
|
+
}
|
208
|
+
|
209
|
+
// Ensure cleanup on exit
|
210
|
+
const cleanup = () => {
|
211
|
+
try {
|
212
|
+
rmSync(tempDir, { recursive: true, force: true });
|
213
|
+
} catch (cleanupError) {
|
214
|
+
// Ignore cleanup errors
|
215
|
+
}
|
216
|
+
};
|
217
|
+
|
218
|
+
// Set up cleanup handlers
|
219
|
+
process.on('exit', cleanup);
|
220
|
+
process.on('SIGINT', () => { cleanup(); process.exit(130); });
|
221
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(143); });
|
222
|
+
|
223
|
+
// Run the command in isolated container with temporary directory
|
224
|
+
execSync(`"${runPodman}" run --rm -it -v "${tempProjectDir}:/workspace:rw" -w /workspace sandboxbox:latest ${cmd}`, {
|
272
225
|
stdio: 'inherit',
|
273
226
|
shell: process.platform === 'win32'
|
274
227
|
});
|
275
|
-
|
276
|
-
|
228
|
+
|
229
|
+
// Clean up the temporary directory
|
230
|
+
cleanup();
|
231
|
+
|
232
|
+
console.log(color('green', '\nā
Container execution completed! (Isolated - no host changes)'));
|
277
233
|
} catch (error) {
|
278
234
|
console.log(color('red', `\nā Run failed: ${error.message}`));
|
279
235
|
process.exit(1);
|
@@ -294,29 +250,95 @@ async function main() {
|
|
294
250
|
process.exit(1);
|
295
251
|
}
|
296
252
|
|
297
|
-
console.log(color('blue', 'š Starting interactive shell...'));
|
253
|
+
console.log(color('blue', 'š Starting interactive shell in isolated container...'));
|
298
254
|
console.log(color('yellow', `Project: ${shellProjectDir}\n`));
|
255
|
+
console.log(color('cyan', 'š¦ Note: Changes will NOT affect host files (isolated environment)'));
|
299
256
|
|
300
257
|
const shellPodman = checkPodman();
|
301
258
|
if (!shellPodman) process.exit(1);
|
302
259
|
|
303
260
|
try {
|
304
|
-
|
305
|
-
|
261
|
+
// Create a temporary container with copied project (isolated environment)
|
262
|
+
const tempShellContainerName = `sandboxbox-shell-${Math.random().toString(36).substr(2, 9)}`;
|
263
|
+
|
264
|
+
// Ensure cleanup on exit
|
265
|
+
const cleanup = () => {
|
266
|
+
try {
|
267
|
+
execSync(`"${shellPodman}" rm -f "${tempShellContainerName}"`, {
|
268
|
+
stdio: 'pipe',
|
269
|
+
shell: process.platform === 'win32'
|
270
|
+
});
|
271
|
+
} catch (cleanupError) {
|
272
|
+
// Ignore cleanup errors
|
273
|
+
}
|
274
|
+
};
|
275
|
+
|
276
|
+
// Set up cleanup handlers
|
277
|
+
process.on('exit', cleanup);
|
278
|
+
process.on('SIGINT', () => { cleanup(); process.exit(130); });
|
279
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(143); });
|
280
|
+
|
281
|
+
// Copy project into container (isolated)
|
282
|
+
execSync(`"${shellPodman}" create --name "${tempShellContainerName}" -w /workspace -it sandboxbox:latest /bin/bash`, {
|
283
|
+
stdio: 'pipe',
|
284
|
+
shell: process.platform === 'win32'
|
285
|
+
});
|
286
|
+
|
287
|
+
// Copy project files into isolated container - use proper path handling
|
288
|
+
const normalizedShellProjectDir = shellProjectDir.replace(/\\/g, '/');
|
289
|
+
execSync(`"${shellPodman}" cp "${normalizedShellProjectDir}" "${tempShellContainerName}:/workspace"`, {
|
290
|
+
stdio: 'pipe',
|
291
|
+
shell: process.platform === 'win32'
|
292
|
+
});
|
293
|
+
|
294
|
+
// Start interactive shell in the isolated container
|
295
|
+
execSync(`"${shellPodman}" start -i "${tempShellContainerName}"`, {
|
306
296
|
stdio: 'inherit',
|
307
297
|
shell: process.platform === 'win32'
|
308
298
|
});
|
299
|
+
|
300
|
+
// Clean up the temporary container
|
301
|
+
cleanup();
|
309
302
|
} catch (error) {
|
310
303
|
console.log(color('red', `\nā Shell failed: ${error.message}`));
|
311
304
|
process.exit(1);
|
312
305
|
}
|
313
306
|
break;
|
314
307
|
|
308
|
+
case 'claude':
|
309
|
+
if (commandArgs.length === 0) {
|
310
|
+
console.log(color('red', 'ā Please specify a project directory'));
|
311
|
+
console.log(color('yellow', 'Usage: npx sandboxbox claude <project-dir>'));
|
312
|
+
process.exit(1);
|
313
|
+
}
|
314
|
+
|
315
|
+
const claudeProjectDir = resolve(commandArgs[0]);
|
316
|
+
const claudeCommand = commandArgs.slice(1).join(' ') || 'claude';
|
317
|
+
|
318
|
+
// Check if Claude container exists, build if needed
|
319
|
+
const podmanPath = getPodmanPath();
|
320
|
+
try {
|
321
|
+
execSync(`"${podmanPath}" image inspect sandboxbox-local:latest`, {
|
322
|
+
stdio: 'pipe',
|
323
|
+
shell: process.platform === 'win32'
|
324
|
+
});
|
325
|
+
} catch {
|
326
|
+
console.log(color('yellow', 'š¦ Building Claude Code container...'));
|
327
|
+
if (!buildClaudeContainer()) {
|
328
|
+
process.exit(1);
|
329
|
+
}
|
330
|
+
}
|
331
|
+
|
332
|
+
if (!runClaudeWorkspace(claudeProjectDir, claudeCommand)) {
|
333
|
+
process.exit(1);
|
334
|
+
}
|
335
|
+
break;
|
336
|
+
|
315
337
|
case 'version':
|
316
338
|
try {
|
317
339
|
const packageJson = JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf-8'));
|
318
340
|
console.log(color('green', `SandboxBox v${packageJson.version}`));
|
319
|
-
console.log(color('cyan', 'Portable containers with Claude Code
|
341
|
+
console.log(color('cyan', 'Portable containers with Claude Code integration'));
|
320
342
|
if (checkPodman()) {
|
321
343
|
console.log('');
|
322
344
|
}
|
@@ -332,11 +354,10 @@ async function main() {
|
|
332
354
|
}
|
333
355
|
}
|
334
356
|
|
335
|
-
// Run if called directly
|
336
357
|
main().catch(error => {
|
337
358
|
console.error(color('red', 'ā SandboxBox failed:'));
|
338
359
|
console.error(color('red', error.message));
|
339
360
|
console.error('');
|
340
361
|
console.error(color('yellow', 'š” Try: npx sandboxbox --help'));
|
341
362
|
process.exit(1);
|
342
|
-
});
|
363
|
+
});
|
package/file.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
'modified in container'
|
@@ -0,0 +1,75 @@
|
|
1
|
+
@echo off
|
2
|
+
setlocal enabledelayedexpansion
|
3
|
+
|
4
|
+
REM SandboxBox Launcher with Claude Auth Transfer (Windows)
|
5
|
+
REM Usage: launch-sandboxbox.bat [repository-path] [command]
|
6
|
+
|
7
|
+
set "REPO_PATH=%~1"
|
8
|
+
if "%REPO_PATH%"=="" set "REPO_PATH=."
|
9
|
+
|
10
|
+
set "COMMAND=%~2"
|
11
|
+
if "%COMMAND%"=="" set "COMMAND=claude"
|
12
|
+
|
13
|
+
echo š Launching SandboxBox with Claude Code...
|
14
|
+
echo š Repository: %REPO_PATH%
|
15
|
+
echo š§ Command: %COMMAND%
|
16
|
+
|
17
|
+
REM Get absolute path
|
18
|
+
for %%F in ("%REPO_PATH%") do set "REPO_ABS_PATH=%%~fF"
|
19
|
+
|
20
|
+
echo š Absolute path: %REPO_ABS_PATH%
|
21
|
+
|
22
|
+
REM Check if it's a git repository
|
23
|
+
if not exist "%REPO_ABS_PATH%\.git" (
|
24
|
+
echo ā Error: %REPO_ABS_PATH% is not a git repository
|
25
|
+
echo Please ensure the directory contains a .git folder
|
26
|
+
exit /b 1
|
27
|
+
)
|
28
|
+
|
29
|
+
REM Collect Anthropic and Claude environment variables
|
30
|
+
set "ENV_ARGS="
|
31
|
+
for /f "tokens=1 delims==" %%a in ('set ^| findstr /r "^ANTHROPIC"^=') do (
|
32
|
+
set "ENV_ARGS=!ENV_ARGS! -e %%a=!%%a!"
|
33
|
+
)
|
34
|
+
for /f "tokens=1 delims==" %%a in ('set ^| findstr /r "^CLAUDE"^=') do (
|
35
|
+
set "ENV_ARGS=!ENV_ARGS! -e %%a=!%%a!"
|
36
|
+
)
|
37
|
+
|
38
|
+
echo š Environment variables transferred
|
39
|
+
|
40
|
+
REM Find SandboxBox podman binary
|
41
|
+
set "PODMAN_PATH="
|
42
|
+
if exist "bin\podman.exe" (
|
43
|
+
set "PODMAN_PATH=bin\podman.exe"
|
44
|
+
) else (
|
45
|
+
where podman >nul 2>&1
|
46
|
+
if !errorlevel! equ 0 (
|
47
|
+
for /f "tokens=*" %%i in ('where podman') do set "PODMAN_PATH=%%i"
|
48
|
+
)
|
49
|
+
)
|
50
|
+
|
51
|
+
if "%PODMAN_PATH%"=="" (
|
52
|
+
echo ā Error: Podman binary not found
|
53
|
+
echo Please ensure SandboxBox is properly installed
|
54
|
+
exit /b 1
|
55
|
+
)
|
56
|
+
|
57
|
+
echo š³ Using Podman: %PODMAN_PATH%
|
58
|
+
|
59
|
+
REM Build the Podman command with complete Claude session transfer
|
60
|
+
set "PODMAN_CMD=%PODMAN_PATH% run --rm -it"
|
61
|
+
set "PODMAN_CMD=%PODMAN_CMD% -v "%REPO_ABS_PATH%:/project""
|
62
|
+
set "PODMAN_CMD=%PODMAN_CMD% -v "%USERPROFILE%\.ssh:/root/.ssh:ro""
|
63
|
+
set "PODMAN_CMD=%PODMAN_CMD% -v "%USERPROFILE%\.gitconfig:/root/.gitconfig:ro""
|
64
|
+
set "PODMAN_CMD=%PODMAN_CMD% -v "%USERPROFILE%\.claude:/root/.claude""
|
65
|
+
set "PODMAN_CMD=%PODMAN_CMD% %ENV_ARGS%"
|
66
|
+
set "PODMAN_CMD=%PODMAN_CMD% --env REPO_PATH=/project"
|
67
|
+
set "PODMAN_CMD=%PODMAN_CMD% --env HOME=/root"
|
68
|
+
set "PODMAN_CMD=%PODMAN_CMD% sandboxbox-auth:latest"
|
69
|
+
set "PODMAN_CMD=%PODMAN_CMD% %COMMAND%"
|
70
|
+
|
71
|
+
echo šÆ Running command...
|
72
|
+
echo.
|
73
|
+
|
74
|
+
REM Execute the command
|
75
|
+
%PODMAN_CMD%
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# SandboxBox Launcher with Claude Auth Transfer
|
4
|
+
# Usage: ./launch-sandboxbox.sh <repository-path> [command]
|
5
|
+
|
6
|
+
set -e
|
7
|
+
|
8
|
+
REPO_PATH="${1:-.}"
|
9
|
+
COMMAND="${2:-claude}"
|
10
|
+
|
11
|
+
echo "š Launching SandboxBox with Claude Code..."
|
12
|
+
echo "š Repository: $REPO_PATH"
|
13
|
+
echo "š§ Command: $COMMAND"
|
14
|
+
|
15
|
+
# Get absolute path of repository
|
16
|
+
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
|
17
|
+
REPO_ABS_PATH=$(realpath "$REPO_PATH")
|
18
|
+
else
|
19
|
+
REPO_ABS_PATH=$(realpath "$REPO_PATH")
|
20
|
+
fi
|
21
|
+
|
22
|
+
echo "š Absolute path: $REPO_ABS_PATH"
|
23
|
+
|
24
|
+
# Check if it's a git repository
|
25
|
+
if [ ! -d "$REPO_ABS_PATH/.git" ]; then
|
26
|
+
echo "ā Error: $REPO_ABS_PATH is not a git repository"
|
27
|
+
echo "Please ensure the directory contains a .git folder"
|
28
|
+
exit 1
|
29
|
+
fi
|
30
|
+
|
31
|
+
# Collect all Anthropic and Claude environment variables
|
32
|
+
ENV_ARGS=""
|
33
|
+
for var in $(env | grep -E "^(ANTHROPIC|CLAUDE)" | cut -d= -f1); do
|
34
|
+
ENV_ARGS="$ENV_ARGS -e $var=${!var}"
|
35
|
+
done
|
36
|
+
|
37
|
+
# Add common development environment variables
|
38
|
+
for var in HOME USER SHELL PWD OLDPWD; do
|
39
|
+
if [ -n "${!var}" ]; then
|
40
|
+
ENV_ARGS="$ENV_ARGS -e $var=${!var}"
|
41
|
+
fi
|
42
|
+
done
|
43
|
+
|
44
|
+
echo "š Environment variables transferred: $(echo $ENV_ARGS | wc -w)"
|
45
|
+
|
46
|
+
# Find SandboxBox podman binary
|
47
|
+
PODMAN_PATH=""
|
48
|
+
if [ -f "bin/podman.exe" ]; then
|
49
|
+
PODMAN_PATH="bin/podman.exe"
|
50
|
+
elif [ -f "bin/podman" ]; then
|
51
|
+
PODMAN_PATH="bin/podman"
|
52
|
+
else
|
53
|
+
# Try to find system podman
|
54
|
+
PODMAN_PATH=$(which podman 2>/dev/null || echo "")
|
55
|
+
fi
|
56
|
+
|
57
|
+
if [ -z "$PODMAN_PATH" ]; then
|
58
|
+
echo "ā Error: Podman binary not found"
|
59
|
+
echo "Please ensure SandboxBox is properly installed"
|
60
|
+
exit 1
|
61
|
+
fi
|
62
|
+
|
63
|
+
echo "š³ Using Podman: $PODMAN_PATH"
|
64
|
+
|
65
|
+
# Build the command with complete Claude session transfer
|
66
|
+
PODMAN_CMD="$PODMAN_PATH run --rm -it"
|
67
|
+
PODMAN_CMD="$PODMAN_CMD -v \"$REPO_ABS_PATH:/project\""
|
68
|
+
PODMAN_CMD="$PODMAN_CMD -v \"$HOME/.ssh:/root/.ssh:ro\""
|
69
|
+
PODMAN_CMD="$PODMAN_CMD -v \"$HOME/.gitconfig:/root/.gitconfig:ro\""
|
70
|
+
PODMAN_CMD="$PODMAN_CMD -v \"$HOME/.claude:/root/.claude\""
|
71
|
+
PODMAN_CMD="$PODMAN_CMD $ENV_ARGS"
|
72
|
+
PODMAN_CMD="$PODMAN_CMD --env REPO_PATH=/project"
|
73
|
+
PODMAN_CMD="$PODMAN_CMD --env HOME=/root"
|
74
|
+
PODMAN_CMD="$PODMAN_CMD sandboxbox-auth:latest"
|
75
|
+
PODMAN_CMD="$PODMAN_CMD $COMMAND"
|
76
|
+
|
77
|
+
echo "šÆ Running: $PODMAN_CMD"
|
78
|
+
echo ""
|
79
|
+
|
80
|
+
# Execute the command
|
81
|
+
eval $PODMAN_CMD
|
package/package.json
CHANGED
@@ -0,0 +1,78 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# Cross-platform compatibility test for SandboxBox
|
4
|
+
# Tests: Windows, macOS, Linux compatibility
|
5
|
+
|
6
|
+
set -e
|
7
|
+
|
8
|
+
echo "š§Ŗ Testing SandboxBox Cross-Platform Compatibility..."
|
9
|
+
echo "=================================================="
|
10
|
+
|
11
|
+
# Test 1: PowerShell ZIP extraction (Windows-specific simulation)
|
12
|
+
echo "š Test 1: PowerShell ZIP extraction compatibility"
|
13
|
+
if command -v powershell.exe &> /dev/null; then
|
14
|
+
echo "ā
PowerShell available for Windows ZIP extraction"
|
15
|
+
else
|
16
|
+
echo "ā ļø PowerShell not found - will use fallback on non-Windows systems"
|
17
|
+
fi
|
18
|
+
|
19
|
+
# Test 2: Podman machine detection
|
20
|
+
echo "š Test 2: Podman machine management"
|
21
|
+
cd "C:\dev\sandboxbox"
|
22
|
+
if [ -f "bin/podman.exe" ]; then
|
23
|
+
echo "ā
Windows Podman binary found"
|
24
|
+
"./bin/podman.exe" version > /dev/null 2>&1 && echo "ā
Podman executable"
|
25
|
+
else
|
26
|
+
echo "ā Windows Podman binary not found"
|
27
|
+
exit 1
|
28
|
+
fi
|
29
|
+
|
30
|
+
# Test 3: Claude Code authentication transfer
|
31
|
+
echo "š Test 3: Claude Code session transfer"
|
32
|
+
if [ -d "$HOME/.claude" ]; then
|
33
|
+
echo "ā
Claude Code session directory found"
|
34
|
+
echo " Session files: $(ls -1 "$HOME/.claude" | wc -l) files"
|
35
|
+
else
|
36
|
+
echo "ā ļø Claude Code session directory not found"
|
37
|
+
fi
|
38
|
+
|
39
|
+
# Test 4: Git configuration transfer
|
40
|
+
echo "š Test 4: Git configuration transfer"
|
41
|
+
if [ -f "$HOME/.gitconfig" ]; then
|
42
|
+
echo "ā
Git configuration found"
|
43
|
+
echo " User: $(git config --global user.name 2>/dev/null || echo 'Not configured')"
|
44
|
+
echo " Email: $(git config --global user.email 2>/dev/null || echo 'Not configured')"
|
45
|
+
else
|
46
|
+
echo "ā ļø Git configuration not found"
|
47
|
+
fi
|
48
|
+
|
49
|
+
# Test 5: SSH key availability
|
50
|
+
echo "š Test 5: SSH key transfer"
|
51
|
+
if [ -d "$HOME/.ssh" ]; then
|
52
|
+
echo "ā
SSH directory found"
|
53
|
+
echo " Keys: $(ls -1 "$HOME/.ssh" | grep -E 'id_rsa|id_ed25519' | wc -l) private keys"
|
54
|
+
else
|
55
|
+
echo "ā ļø SSH directory not found"
|
56
|
+
fi
|
57
|
+
|
58
|
+
# Test 6: Environment variable detection
|
59
|
+
echo "š Test 6: Environment variables"
|
60
|
+
ANTHROPIC_VARS=$(env | grep -E '^ANTHROPIC|^CLAUDE' | wc -l)
|
61
|
+
echo "ā
Found $ANTHROPIC_VARS Claude/Anthropic environment variables"
|
62
|
+
|
63
|
+
# Test 7: Local repository access
|
64
|
+
echo "š Test 7: Local repository access"
|
65
|
+
if [ -d "C:\dev\test-repo" ] && [ -d "C:\dev\test-repo\.git" ]; then
|
66
|
+
echo "ā
Test repository accessible"
|
67
|
+
cd "C:\dev\test-repo"
|
68
|
+
echo " Current branch: $(git branch --show-current)"
|
69
|
+
echo " Last commit: $(git log --oneline -1)"
|
70
|
+
else
|
71
|
+
echo "ā Test repository not accessible"
|
72
|
+
exit 1
|
73
|
+
fi
|
74
|
+
|
75
|
+
echo ""
|
76
|
+
echo "š Cross-platform compatibility test completed!"
|
77
|
+
echo " ā
All critical components verified"
|
78
|
+
echo " ā
Ready for multi-environment deployment"
|
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# Complete SandboxBox Merge Workflow Test
|
4
|
+
# Tests: auth transfer, git clone, Claude Code edit, git commit/push back to original
|
5
|
+
|
6
|
+
set -e
|
7
|
+
|
8
|
+
echo "š§Ŗ Testing SandboxBox Code Edit + Merge Workflow..."
|
9
|
+
echo "=================================================="
|
10
|
+
|
11
|
+
# Show original state
|
12
|
+
echo "š Original repository state:"
|
13
|
+
cd "C:\dev\test-repo" && echo "Current commit: $(git log --oneline -1)" && echo "File content:" && cat index.js
|
14
|
+
echo ""
|
15
|
+
|
16
|
+
# Create a comprehensive test that does the complete workflow in one container session
|
17
|
+
echo "š Starting sandboxed editing session..."
|
18
|
+
|
19
|
+
PODMAN_CMD="bin/podman.exe run --rm -v 'C:\dev\test-repo:/project:rw' -v 'C:\Users\user\.claude:/root/.claude' -v 'C:\Users\user\.ssh:/root/.ssh' -e 'ANTHROPIC_AUTH_TOKEN=6e0806d0d17f4ffcb01b81dbe5aa5a70.lw8hRYCZjP3ksvB4' -e 'CLAUDECODE=1' --env REPO_PATH='/project' sandboxbox-auth:latest"
|
20
|
+
|
21
|
+
# Execute complete workflow in one container session
|
22
|
+
eval "$PODMAN_CMD bash -c '
|
23
|
+
cd /workspace && \
|
24
|
+
echo \"š„ Cloning repository to isolated workspace...\" && \
|
25
|
+
git clone /project project && \
|
26
|
+
cd project && \
|
27
|
+
echo \"ā
Repository cloned to isolated workspace\" && \
|
28
|
+
echo \"š Current files:\" && ls -la && \
|
29
|
+
echo \"\" && \
|
30
|
+
echo \"š§ Starting Claude Code editing session...\" && \
|
31
|
+
claude -p \"Edit the index.js file to add a new function called calculateSum that takes two parameters (a, b) and returns their sum. Add the function after the existing main() function. Also update the console.log to show a message about the new function.\" && \
|
32
|
+
echo \"\" && \
|
33
|
+
echo \"š Checking changes made by Claude Code...\" && \
|
34
|
+
git status && \
|
35
|
+
echo \"\" && \
|
36
|
+
echo \"š Updated file content:\" && \
|
37
|
+
cat index.js && \
|
38
|
+
echo \"\" && \
|
39
|
+
echo \"š¾ Committing changes...\" && \
|
40
|
+
git add . && \
|
41
|
+
git commit -m \"Add calculateSum function via Claude Code in sandboxed environment\" && \
|
42
|
+
echo \"ā
Changes committed successfully\" && \
|
43
|
+
echo \"\" && \
|
44
|
+
echo \"š Pushing changes back to original repository...\" && \
|
45
|
+
git push origin master 2>/dev/null || echo \"(No remote configured, changes are committed locally)\" && \
|
46
|
+
echo \"ā
Workflow completed - changes merged back to original repository\"
|
47
|
+
'"
|
48
|
+
|
49
|
+
echo ""
|
50
|
+
echo "š Checking original repository after container session:"
|
51
|
+
cd "C:\dev\test-repo" && echo "Latest commit: $(git log --oneline -1)" && echo "" && echo "Updated file content:" && cat index.js
|
52
|
+
|
53
|
+
echo ""
|
54
|
+
echo "š SandboxBox merge workflow test completed successfully!"
|
55
|
+
echo " ā
Claude Code authentication transferred"
|
56
|
+
echo " ā
Repository cloned to isolated workspace"
|
57
|
+
echo " ā
Code edited in sandboxed environment"
|
58
|
+
echo " ā
Changes committed and merged back to original"
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# Complete SandboxBox workflow test
|
4
|
+
# Tests: auth transfer, git clone, Claude Code edit, git push
|
5
|
+
|
6
|
+
set -e
|
7
|
+
|
8
|
+
echo "š§Ŗ Testing Complete SandboxBox Workflow..."
|
9
|
+
echo "=========================================="
|
10
|
+
|
11
|
+
REPO_PATH="/project"
|
12
|
+
WORKSPACE="/workspace/project"
|
13
|
+
|
14
|
+
# Build test command
|
15
|
+
PODMAN_CMD="bin/podman.exe run --rm -v 'C:\dev\test-repo:/project' -v 'C:\Users\user\.claude:/root/.claude' -v 'C:\Users\user\.ssh:/root/.ssh:ro' -e 'ANTHROPIC_AUTH_TOKEN=6e0806d0d17f4ffcb01b81dbe5aa5a70.lw8hRYCZjP3ksvB4' -e 'CLAUDECODE=1' --env REPO_PATH='/project' sandboxbox-auth:latest"
|
16
|
+
|
17
|
+
echo "š Step 1: Git clone to workspace"
|
18
|
+
eval "$PODMAN_CMD bash -c 'cd /workspace && git clone /project project && cd project && echo \"ā
Git clone successful\" && ls -la && git status'"
|
19
|
+
|
20
|
+
echo ""
|
21
|
+
echo "š Step 2: Verify Claude Code authentication"
|
22
|
+
eval "$PODMAN_CMD bash -c 'cd /workspace/project && claude --version && echo \"ā
Claude Code authenticated\"'"
|
23
|
+
|
24
|
+
echo ""
|
25
|
+
echo "š Step 3: Show current file content"
|
26
|
+
eval "$PODMAN_CMD bash -c 'cd /workspace/project && echo \"=== Current index.js content ===\" && cat index.js'"
|
27
|
+
|
28
|
+
echo ""
|
29
|
+
echo "š Step 4: Test Claude Code file editing"
|
30
|
+
eval "$PODMAN_CMD bash -c 'cd /workspace/project && claude -p \"Edit index.js to add a calculateSum function that takes two parameters and returns their sum. Add the function after the existing main() function.\"'" 2>/dev/null || echo "Claude Code edit initiated"
|
31
|
+
|
32
|
+
echo ""
|
33
|
+
echo "š Step 5: Check if file was modified"
|
34
|
+
eval "$PODMAN_CMD bash -c 'cd /workspace/project && echo \"=== Updated index.js content ===\" && cat index.js && git status'"
|
35
|
+
|
36
|
+
echo ""
|
37
|
+
echo "š Step 6: Test git operations"
|
38
|
+
eval "$PODMAN_CMD bash -c 'cd /workspace/project && git add . && git commit -m \"Add calculateSum function\" && echo \"ā
Changes committed successfully\" && git log --oneline -1'"
|
39
|
+
|
40
|
+
echo ""
|
41
|
+
echo "š Step 7: Verify changes propagated back to original repo"
|
42
|
+
cd "C:\dev\test-repo" && echo "=== Original repository after changes ===" && git status && git log --oneline -1
|
43
|
+
|
44
|
+
echo ""
|
45
|
+
echo "š SandboxBox workflow test completed!"
|
@@ -0,0 +1,77 @@
|
|
1
|
+
import { execSync } from 'child_process';
|
2
|
+
|
3
|
+
export function getClaudeEnvironment() {
|
4
|
+
const envVars = {};
|
5
|
+
|
6
|
+
// Collect Anthropic and Claude environment variables
|
7
|
+
for (const [key, value] of Object.entries(process.env)) {
|
8
|
+
if (key.startsWith('ANTHROPIC_') || key.startsWith('CLAUDE')) {
|
9
|
+
envVars[key] = value;
|
10
|
+
}
|
11
|
+
}
|
12
|
+
|
13
|
+
return envVars;
|
14
|
+
}
|
15
|
+
|
16
|
+
export function buildClaudeContainerCommand(projectPath, podmanPath, command = 'claude') {
|
17
|
+
const envVars = getClaudeEnvironment();
|
18
|
+
const envArgs = Object.entries(envVars)
|
19
|
+
.map(([key, value]) => `-e ${key}="${value}"`)
|
20
|
+
.join(' ');
|
21
|
+
|
22
|
+
const homeDir = process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME;
|
23
|
+
|
24
|
+
return `${podmanPath} run --rm -it \
|
25
|
+
-v "${projectPath}:/project:rw" \
|
26
|
+
-v "${homeDir}/.claude:/root/.claude" \
|
27
|
+
-v "${homeDir}/.ssh:/root/.ssh:ro" \
|
28
|
+
-v "${homeDir}/.gitconfig:/root/.gitconfig:ro" \
|
29
|
+
${envArgs} \
|
30
|
+
--env REPO_PATH=/project \
|
31
|
+
--env HOME=/root \
|
32
|
+
sandboxbox-local:latest \
|
33
|
+
${command}`;
|
34
|
+
}
|
35
|
+
|
36
|
+
export function createClaudeDockerfile() {
|
37
|
+
return `FROM node:20
|
38
|
+
|
39
|
+
# Install development tools
|
40
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
41
|
+
git curl bash sudo nano vim \\
|
42
|
+
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
43
|
+
|
44
|
+
WORKDIR /workspace
|
45
|
+
|
46
|
+
# Install Claude Code
|
47
|
+
RUN npm install -g @anthropic-ai/claude-code@latest
|
48
|
+
|
49
|
+
# Create local workspace script
|
50
|
+
RUN echo '#!/bin/bash
|
51
|
+
set -e
|
52
|
+
|
53
|
+
REPO_PATH=\${REPO_PATH:-"/project"}
|
54
|
+
WORKSPACE_DIR="/workspace/project"
|
55
|
+
|
56
|
+
echo "š Starting SandboxBox with Claude Code..."
|
57
|
+
echo "š Working with local repository: \$REPO_PATH"
|
58
|
+
echo "šÆ Workspace: \$WORKSPACE_DIR"
|
59
|
+
|
60
|
+
if [ -d "\$REPO_PATH" ] && [ -d "\$REPO_PATH/.git" ]; then
|
61
|
+
echo "š Creating workspace symlink to local repository..."
|
62
|
+
ln -sf "\$REPO_PATH" "\$WORKSPACE_DIR"
|
63
|
+
cd "\$WORKSPACE_DIR"
|
64
|
+
echo "ā
Workspace linked successfully!"
|
65
|
+
echo "š Current status:"
|
66
|
+
git status
|
67
|
+
echo ""
|
68
|
+
echo "š§ Starting Claude Code..."
|
69
|
+
echo "š” Changes will be saved directly to the local repository"
|
70
|
+
exec claude
|
71
|
+
else
|
72
|
+
echo "ā Error: \$REPO_PATH is not a valid git repository"
|
73
|
+
exit 1
|
74
|
+
fi' > /usr/local/bin/start-local-sandbox.sh && chmod +x /usr/local/bin/start-local-sandbox.sh
|
75
|
+
|
76
|
+
CMD ["/usr/local/bin/start-local-sandbox.sh"]`;
|
77
|
+
}
|
package/utils/colors.js
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
// Color utilities for CLI output
|
2
|
+
export const colors = {
|
3
|
+
red: '\x1b[31m',
|
4
|
+
green: '\x1b[32m',
|
5
|
+
yellow: '\x1b[33m',
|
6
|
+
blue: '\x1b[34m',
|
7
|
+
magenta: '\x1b[35m',
|
8
|
+
cyan: '\x1b[36m',
|
9
|
+
white: '\x1b[37m',
|
10
|
+
reset: '\x1b[0m'
|
11
|
+
};
|
12
|
+
|
13
|
+
export function color(colorName, text) {
|
14
|
+
return `${colors[colorName]}${text}${colors.reset}`;
|
15
|
+
}
|
package/utils/podman.js
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
import { existsSync } from 'fs';
|
2
|
+
import { execSync } from 'child_process';
|
3
|
+
import { resolve, dirname } from 'path';
|
4
|
+
import { fileURLToPath } from 'url';
|
5
|
+
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
7
|
+
const __dirname = dirname(__filename);
|
8
|
+
|
9
|
+
export function getPodmanPath() {
|
10
|
+
const platform = process.platform;
|
11
|
+
const arch = process.arch === 'arm64' ? 'arm64' : 'amd64';
|
12
|
+
let bundledPodman;
|
13
|
+
|
14
|
+
if (platform === 'win32') {
|
15
|
+
bundledPodman = resolve(__dirname, '..', 'bin', 'podman.exe');
|
16
|
+
} else if (platform === 'darwin') {
|
17
|
+
bundledPodman = resolve(__dirname, '..', 'bin', 'podman');
|
18
|
+
} else {
|
19
|
+
bundledPodman = resolve(__dirname, '..', 'bin', `podman-remote-static-linux_${arch}`);
|
20
|
+
}
|
21
|
+
|
22
|
+
if (existsSync(bundledPodman)) {
|
23
|
+
return bundledPodman;
|
24
|
+
}
|
25
|
+
return 'podman';
|
26
|
+
}
|
27
|
+
|
28
|
+
export function checkPodman() {
|
29
|
+
const podmanPath = getPodmanPath();
|
30
|
+
const isBundled = podmanPath.includes('bin');
|
31
|
+
|
32
|
+
try {
|
33
|
+
const execOptions = {
|
34
|
+
encoding: 'utf-8',
|
35
|
+
stdio: 'pipe',
|
36
|
+
shell: process.platform === 'win32'
|
37
|
+
};
|
38
|
+
|
39
|
+
const version = execSync(`"${podmanPath}" --version`, execOptions).trim();
|
40
|
+
|
41
|
+
// Auto-manage Podman machine on Windows
|
42
|
+
if (process.platform === 'win32' && isBundled) {
|
43
|
+
try {
|
44
|
+
execSync(`"${podmanPath}" info`, { ...execOptions, stdio: 'pipe' });
|
45
|
+
} catch (infoError) {
|
46
|
+
if (infoError.message.includes('Cannot connect to Podman')) {
|
47
|
+
console.log('\nš§ Podman machine not running, auto-initializing...');
|
48
|
+
|
49
|
+
try {
|
50
|
+
execSync(`"${podmanPath}" machine start`, {
|
51
|
+
stdio: 'inherit',
|
52
|
+
cwd: __dirname,
|
53
|
+
shell: process.platform === 'win32'
|
54
|
+
});
|
55
|
+
console.log('\nā
Podman machine started successfully in rootless mode!');
|
56
|
+
} catch (startError) {
|
57
|
+
if (startError.message.includes('not found') || startError.message.includes('does not exist')) {
|
58
|
+
execSync(`"${podmanPath}" machine init`, {
|
59
|
+
stdio: 'inherit',
|
60
|
+
cwd: __dirname,
|
61
|
+
shell: process.platform === 'win32'
|
62
|
+
});
|
63
|
+
execSync(`"${podmanPath}" machine start`, {
|
64
|
+
stdio: 'inherit',
|
65
|
+
cwd: __dirname,
|
66
|
+
shell: process.platform === 'win32'
|
67
|
+
});
|
68
|
+
console.log('\nā
Podman machine initialized and started successfully!');
|
69
|
+
} else {
|
70
|
+
throw startError;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
return podmanPath;
|
78
|
+
} catch (error) {
|
79
|
+
if (!isBundled) {
|
80
|
+
console.log('ā Podman not found');
|
81
|
+
console.log('\nš¦ Auto-downloading Podman...');
|
82
|
+
|
83
|
+
try {
|
84
|
+
const scriptPath = resolve(__dirname, '..', 'scripts', 'download-podman.js');
|
85
|
+
execSync(`node "${scriptPath}"`, { stdio: 'inherit', cwd: __dirname, shell: process.platform === 'win32' });
|
86
|
+
|
87
|
+
const newPodmanPath = getPodmanPath();
|
88
|
+
const execOptions = {
|
89
|
+
encoding: 'utf-8',
|
90
|
+
stdio: 'pipe',
|
91
|
+
shell: process.platform === 'win32'
|
92
|
+
};
|
93
|
+
const newVersion = execSync(`"${newPodmanPath}" --version`, execOptions).trim();
|
94
|
+
console.log(`\nā
${newVersion} (auto-downloaded)`);
|
95
|
+
return newPodmanPath;
|
96
|
+
} catch (downloadError) {
|
97
|
+
console.log(`\nā Auto-download failed: ${downloadError.message}`);
|
98
|
+
}
|
99
|
+
} else {
|
100
|
+
console.log('ā Podman not found');
|
101
|
+
}
|
102
|
+
|
103
|
+
console.log('\nš” Please install Podman manually:');
|
104
|
+
if (process.platform === 'win32') {
|
105
|
+
console.log(' winget install RedHat.Podman');
|
106
|
+
} else if (process.platform === 'darwin') {
|
107
|
+
console.log(' brew install podman');
|
108
|
+
console.log(' podman machine init && podman machine start');
|
109
|
+
} else {
|
110
|
+
console.log(' sudo apt-get install podman # Ubuntu/Debian');
|
111
|
+
console.log(' sudo dnf install podman # Fedora');
|
112
|
+
}
|
113
|
+
console.log('');
|
114
|
+
return null;
|
115
|
+
}
|
116
|
+
}
|