@kokorolx/ai-sandbox-wrapper 2.2.0 → 2.4.0
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/README.md +108 -669
- package/bin/ai-run +345 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,612 +1,160 @@
|
|
|
1
1
|
# 🔒 AI Sandbox Wrapper
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Run OpenCode and other AI coding agents in secure Docker containers.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Protect your SSH keys, API tokens, and system files while using AI tools that need filesystem access.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
*Last updated: February 6, 2026*
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## ✨ New in v2.3.0-beta: Web Mode & Port Exposure
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
- **Web Auto-Detection**: `opencode web` automatically exposes port 4096 and injects `--hostname 0.0.0.0`
|
|
12
|
+
- **`--expose` Flag**: New way to expose ports (replaces deprecated `PORT` env var)
|
|
13
|
+
- **Port Conflict Detection**: Fails fast if port is already in use
|
|
12
14
|
|
|
13
|
-
## ✨ New in v2.2.0: Clipboard Fixes & Screenshot Detection
|
|
14
|
-
|
|
15
|
-
The **v2.2.0 release** solves the "air-gap" problem for text copying and streamlines screenshot workflows.
|
|
16
|
-
|
|
17
|
-
- ✅ **OSC 52 Clipboard Support**: Copy text from inside Linux containers directly to your macOS clipboard (works over SSH too!).
|
|
18
|
-
- ✅ **Auto-Detect Screenshot Folder**: Automatically finds where your Mac saves screenshots and offers to whitelist it. No more permission errors when dragging images into AI tools.
|
|
19
|
-
- ✅ **Seamless Drag & Drop**: Just drag screenshots into the terminal window.
|
|
20
|
-
|
|
21
|
-
## ✨ New in v2.1.0: Stability & Native Persistence
|
|
22
|
-
|
|
23
|
-
The **v2.1.0 release** focuses on architectural stability and a more intuitive persistence model.
|
|
24
|
-
|
|
25
|
-
- ✅ **Stable Node 22 LTS**: Switched to a robust Node 22 base image for maximum compatibility and performance.
|
|
26
|
-
- ✅ **Direct Mount Persistence**: Changes made *inside* the container (logins, settings, sessions) now save directly to your host's native folders.
|
|
27
|
-
- ✅ **Cache Isolation**: Heavy caches (`node_modules`, `.npm`, `.cache`) are isolated using anonymous volumes to prevent "cache poisoning" and runtime conflicts.
|
|
28
|
-
|
|
29
|
-
### Native Config Mapping
|
|
30
|
-
Your tool configurations are now directly linked from your Mac/PC:
|
|
31
|
-
- Host: `~/.config/opencode` ↔ Container: `/home/agent/.config/opencode`
|
|
32
|
-
- Host: `~/.local/share/opencode` ↔ Container: `/home/agent/.local/share/opencode`
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## ⚠️ Breaking Change: v2.0.0 - Config Directory Reorganization
|
|
37
|
-
|
|
38
|
-
**Version 2.0.0** reorganized the directory structure to a tool-centric layout and introduced a unified `config.json`.
|
|
39
|
-
|
|
40
|
-
## 🛡️ Why Use This?
|
|
41
|
-
|
|
42
|
-
Without sandbox:
|
|
43
|
-
- AI agents can read your SSH keys, API tokens, browser data
|
|
44
|
-
- Can execute arbitrary code with your user permissions
|
|
45
|
-
- Can access files outside your project
|
|
46
|
-
|
|
47
|
-
With AI Sandbox:
|
|
48
|
-
- ✅ AI only sees whitelisted workspace folders
|
|
49
|
-
- ✅ No access to host environment variables (API keys hidden)
|
|
50
|
-
- ✅ Read-only filesystem (except workspace)
|
|
51
|
-
- ✅ No network access to host services
|
|
52
|
-
- ✅ Runs as non-root user in container
|
|
53
|
-
- ✅ CAP_DROP=ALL (no elevated privileges)
|
|
54
|
-
|
|
55
|
-
## 🚀 Step-by-Step Installation
|
|
56
|
-
|
|
57
|
-
### Step 1: Prerequisites
|
|
58
|
-
Ensure you have Docker installed and running:
|
|
59
|
-
- **macOS:** [Install Docker Desktop](https://www.docker.com/products/docker-desktop) and start it
|
|
60
|
-
- **Linux:** Install Docker Engine with `curl -fsSL https://get.docker.com | sh`
|
|
61
|
-
- **Windows:** Use WSL2 with Docker Desktop
|
|
62
|
-
|
|
63
|
-
Verify Docker is working:
|
|
64
15
|
```bash
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```
|
|
16
|
+
# Web mode - automatic port exposure
|
|
17
|
+
opencode web
|
|
68
18
|
|
|
69
|
-
|
|
19
|
+
# Custom port
|
|
20
|
+
opencode web --port 8080
|
|
70
21
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
npx @kokorolx/ai-sandbox-wrapper setup
|
|
22
|
+
# Expose additional ports
|
|
23
|
+
opencode --expose 3000,5555 web
|
|
74
24
|
```
|
|
75
25
|
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
git clone https://github.com/kokorolx/ai-sandbox-wrapper.git
|
|
79
|
-
cd ai-sandbox-wrapper
|
|
80
|
-
./setup.sh
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**Fresh build (no Docker cache):**
|
|
84
|
-
```bash
|
|
85
|
-
npx @kokorolx/ai-sandbox-wrapper setup --no-cache
|
|
86
|
-
# or
|
|
87
|
-
./setup.sh --no-cache
|
|
88
|
-
```
|
|
26
|
+
## 🛡️ Why Use This?
|
|
89
27
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
28
|
+
| Without Sandbox | With AI Sandbox |
|
|
29
|
+
|-----------------|-----------------|
|
|
30
|
+
| AI reads SSH keys, API tokens | ✅ Only whitelisted folders visible |
|
|
31
|
+
| Full filesystem access | ✅ Read-only except workspace |
|
|
32
|
+
| Host environment exposed | ✅ API keys passed explicitly |
|
|
33
|
+
| Runs with your permissions | ✅ Non-root, CAP_DROP=ALL |
|
|
94
34
|
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
# Reload your shell to update PATH
|
|
98
|
-
source ~/.zshrc
|
|
35
|
+
## 🚀 Quick Start
|
|
99
36
|
|
|
100
|
-
|
|
101
|
-
nano ~/.ai-sandbox/env # Add ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.
|
|
102
|
-
```
|
|
37
|
+
**Prerequisites:** Docker Desktop (macOS/Windows) or Docker Engine (Linux)
|
|
103
38
|
|
|
104
|
-
### Step 5: Run Your First Tool
|
|
105
39
|
```bash
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
# Run a tool (the example below assumes you selected Claude during setup)
|
|
110
|
-
claude --version # or: ai-run claude --version
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## 📋 What You Need
|
|
114
|
-
|
|
115
|
-
**Required:**
|
|
116
|
-
- **Docker** - Docker Desktop (macOS/Windows) or Docker Engine (Linux)
|
|
117
|
-
- **Git** - For cloning the repository
|
|
118
|
-
- **Bash** - For running the setup script
|
|
119
|
-
|
|
120
|
-
**Optional (for specific tools):**
|
|
121
|
-
- **Python 3** - For tools like Aider
|
|
122
|
-
- **SSH keys** - For Git access in containers
|
|
123
|
-
|
|
124
|
-
## ✅ After Installation
|
|
40
|
+
# Install
|
|
41
|
+
npx @kokorolx/ai-sandbox-wrapper setup
|
|
125
42
|
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
# Reload your shell to get the new commands
|
|
43
|
+
# Reload shell
|
|
129
44
|
source ~/.zshrc
|
|
130
45
|
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
# Test a tool you installed (replace 'claude' with your chosen tool)
|
|
135
|
-
claude --version
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Add More Projects Later (Optional)
|
|
139
|
-
If you want to give AI access to more project directories later:
|
|
140
|
-
```bash
|
|
141
|
-
# Add a new workspace
|
|
142
|
-
echo '/path/to/new/project' >> ~/.ai-sandbox/workspaces
|
|
143
|
-
|
|
144
|
-
# View current allowed directories
|
|
145
|
-
cat ~/.ai-sandbox/workspaces
|
|
46
|
+
# Run OpenCode
|
|
47
|
+
opencode
|
|
146
48
|
```
|
|
147
49
|
|
|
148
|
-
|
|
149
|
-
Some tools require API keys to work properly:
|
|
150
|
-
```bash
|
|
151
|
-
nano ~/.ai-sandbox/env
|
|
152
|
-
```
|
|
153
|
-
Then add your keys in the format: `KEY_NAME=your_actual_key_here`
|
|
154
|
-
Examples:
|
|
155
|
-
- `ANTHROPIC_API_KEY=your_key_here` (for Claude)
|
|
156
|
-
- `OPENAI_API_KEY=your_key_here` (for OpenAI tools)
|
|
157
|
-
|
|
158
|
-
## 🐳 Using Pre-Built Images
|
|
159
|
-
|
|
160
|
-
**Skip the build process!** Pull pre-built images directly from GitLab Container Registry:
|
|
161
|
-
|
|
162
|
-
```bash
|
|
163
|
-
# Pull a specific tool image
|
|
164
|
-
docker pull registry.gitlab.com/kokorolee/ai-sandbox-wrapper/ai-claude:latest
|
|
165
|
-
docker pull registry.gitlab.com/kokorolee/ai-sandbox-wrapper/ai-gemini:latest
|
|
166
|
-
docker pull registry.gitlab.com/kokorolee/ai-sandbox-wrapper/ai-aider:latest
|
|
167
|
-
|
|
168
|
-
# Or let setup.sh pull them automatically
|
|
169
|
-
./setup.sh # Select tools, images will be pulled if available
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
**Available pre-built images:**
|
|
173
|
-
- `ai-base:latest` - Base image with Node.js 22 LTS runtime
|
|
174
|
-
- `ai-amp:latest` - Sourcegraph Amp
|
|
175
|
-
- `ai-claude:latest` - Claude Code CLI
|
|
176
|
-
- `ai-droid:latest` - Factory CLI
|
|
177
|
-
- `ai-gemini:latest` - Google Gemini CLI
|
|
178
|
-
- `ai-kilo:latest` - Kilo Code (500+ models)
|
|
179
|
-
- `ai-codex:latest` - OpenAI Codex
|
|
180
|
-
- `ai-aider:latest` - AI pair programmer
|
|
181
|
-
- `ai-opencode:latest` - Open-source AI coding
|
|
182
|
-
- `ai-qwen:latest` - Alibaba Qwen (1M context)
|
|
183
|
-
- `ai-qoder:latest` - Qoder AI assistant
|
|
184
|
-
- `ai-auggie:latest` - Augment Auggie
|
|
185
|
-
- `ai-codebuddy:latest` - Tencent CodeBuddy
|
|
186
|
-
- `ai-jules:latest` - Google Jules
|
|
187
|
-
- `ai-shai:latest` - OVHcloud SHAI
|
|
188
|
-
|
|
189
|
-
**Benefits:**
|
|
190
|
-
- ⚡ **Faster setup** - No build time (seconds vs minutes)
|
|
191
|
-
- ✅ **CI-tested** - All images verified in GitLab CI
|
|
192
|
-
- 🔄 **Auto-updated** - Latest versions on every push to beta branch
|
|
193
|
-
|
|
194
|
-
## 📦 Supported Tools
|
|
195
|
-
|
|
196
|
-
### CLI Tools (Terminal-based)
|
|
197
|
-
|
|
198
|
-
| Tool | Status | Install Type | Description |
|
|
199
|
-
|------|--------|--------------|-------------|
|
|
200
|
-
| **claude** | ✅ | Native binary | Anthropic Claude Code |
|
|
201
|
-
| **opencode** | ✅ | Native Go | Open-source AI coding |
|
|
202
|
-
| **gemini** | ✅ | npm/Node | Google Gemini CLI (free tier) |
|
|
203
|
-
| **aider** | ✅ | Python | AI pair programmer (Git-native) |
|
|
204
|
-
| **kilo** | ✅ | npm/Node | Kilo Code (500+ models) |
|
|
205
|
-
| **codex** | ✅ | npm/Node | OpenAI Codex agent |
|
|
206
|
-
| **amp** | ✅ | npm/Node | Sourcegraph Amp |
|
|
207
|
-
| **qwen** | ✅ | npm/Node | Alibaba Qwen CLI (1M context) |
|
|
208
|
-
| **droid** | ✅ | Custom | Factory CLI |
|
|
209
|
-
|
|
210
|
-
> **Note:** GUI tools (VSCode, codeserver) have been removed in v2.0.1. Use your native IDE with AI tools running in the sandbox.
|
|
211
|
-
|
|
212
|
-
## ⚠️ Known Issues
|
|
213
|
-
|
|
214
|
-
### Native Tool Config Compatibility
|
|
215
|
-
|
|
216
|
-
In v2.1.0+, tool configurations are **directly bind-mounted** from your host. This ensures 100% compatibility with your native tool settings and authentications.
|
|
217
|
-
|
|
218
|
-
1. **Host Config**: `~/.config/<tool>/` or `~/.<tool>/`
|
|
219
|
-
2. **Container Mount**: `/home/agent/.config/<tool>` (Automatic)
|
|
220
|
-
|
|
221
|
-
**Currently Supported for Direct Mount:**
|
|
222
|
-
- ✅ `claude`
|
|
223
|
-
- ✅ `opencode`
|
|
224
|
-
- ✅ `amp`
|
|
225
|
-
- ✅ `gemini`
|
|
226
|
-
- ✅ `aider`
|
|
227
|
-
- ... and all other supported tools.
|
|
228
|
-
|
|
229
|
-
Please [open an issue](https://github.com/kokorolx/ai-sandbox-wrapper/issues) if you encounter problems with specific tools.
|
|
230
|
-
|
|
231
|
-
## 🖥️ Platform Support
|
|
232
|
-
|
|
233
|
-
| Platform | Status |
|
|
234
|
-
|----------|--------|
|
|
235
|
-
| macOS (Intel) | ✅ |
|
|
236
|
-
| macOS (Apple Silicon) | ✅ |
|
|
237
|
-
| Linux (x64) | ✅ |
|
|
238
|
-
| Linux (ARM64) | ✅ |
|
|
239
|
-
| Windows (Docker Desktop + WSL2) | ✅ |
|
|
240
|
-
|
|
241
|
-
## 📁 Directory Structure
|
|
242
|
-
|
|
243
|
-
AI Sandbox Wrapper creates and manages a single consolidated directory in your home folder:
|
|
244
|
-
|
|
245
|
-
| Directory | Purpose | Contents |
|
|
246
|
-
|-----------|---------|----------|
|
|
247
|
-
| `~/bin/` | Executables | `ai-run` wrapper and symlinks to tool scripts |
|
|
248
|
-
| `~/.ai-sandbox/` | All config | Consolidated configuration directory (see structure below) |
|
|
249
|
-
| `~/.ai-images/` | Local images | Locally built Docker images (if not using registry) |
|
|
250
|
-
|
|
251
|
-
### Sandbox Structure
|
|
252
|
-
|
|
253
|
-
```
|
|
254
|
-
~/.ai-sandbox/
|
|
255
|
-
├── config.json # Unified config (workspaces, git, networks)
|
|
256
|
-
├── tools/ # Isolated sandbox environments
|
|
257
|
-
│ └── <tool>/
|
|
258
|
-
│ └── home/ # Sandbox home directory (excludes native configs)
|
|
259
|
-
├── shared/ # Shared assets
|
|
260
|
-
│ └── git/ # Shared git config and keys
|
|
261
|
-
└── env # API keys (format: KEY=value)
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
**Note:** Tools also bind-mount your **native** `~/.config/<tool>` directories for persistence.
|
|
265
|
-
|
|
266
|
-
### Key Files
|
|
267
|
-
|
|
268
|
-
| File | Purpose |
|
|
269
|
-
|------|---------|
|
|
270
|
-
| `~/.ai-sandbox/config.json` | Unified config (workspaces, git access, networks) |
|
|
271
|
-
| `~/.ai-sandbox/env` | API keys (format: `KEY=value`, one per line) |
|
|
272
|
-
| `~/.ai-sandbox/workspaces` | Legacy workspace file (fallback) |
|
|
273
|
-
| `~/.ai-sandbox/git-allowed` | Legacy git-allowed file (fallback) |
|
|
50
|
+
During setup: select **opencode**, choose registry images (faster), whitelist your project directories.
|
|
274
51
|
|
|
275
52
|
## ⚙️ Configuration
|
|
276
53
|
|
|
277
|
-
### Tool-Specific Configuration
|
|
278
|
-
|
|
279
|
-
Each tool has its own persistent home directory inside `~/.ai-sandbox/tools/<tool>/home/`.
|
|
280
|
-
|
|
281
|
-
```bash
|
|
282
|
-
# View configuration paths for a specific tool (Recommended)
|
|
283
|
-
npx @kokorolx/ai-sandbox-wrapper config tool claude
|
|
284
|
-
|
|
285
|
-
# View configuration content
|
|
286
|
-
npx @kokorolx/ai-sandbox-wrapper config tool claude --show
|
|
287
|
-
```
|
|
288
|
-
|
|
289
54
|
### API Keys
|
|
290
55
|
```bash
|
|
291
|
-
# Edit environment file
|
|
292
56
|
nano ~/.ai-sandbox/env
|
|
293
57
|
```
|
|
294
|
-
|
|
295
|
-
### Workspace Management
|
|
296
|
-
```bash
|
|
297
|
-
# CLI commands (recommended)
|
|
298
|
-
npx @kokorolx/ai-sandbox-wrapper workspace list
|
|
299
|
-
npx @kokorolx/ai-sandbox-wrapper workspace add ~/projects/my-new-app
|
|
300
|
-
npx @kokorolx/ai-sandbox-wrapper workspace remove ~/old-project
|
|
301
|
-
|
|
302
|
-
# Interactive menu
|
|
303
|
-
npx @kokorolx/ai-sandbox-wrapper update
|
|
304
|
-
|
|
305
|
-
# Legacy (still works)
|
|
306
|
-
echo '/path/to/project' >> ~/.ai-sandbox/workspaces
|
|
307
|
-
cat ~/.ai-sandbox/workspaces
|
|
308
58
|
```
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
```bash
|
|
312
|
-
# CLI commands
|
|
313
|
-
npx @kokorolx/ai-sandbox-wrapper git status
|
|
314
|
-
npx @kokorolx/ai-sandbox-wrapper git enable ~/projects/myrepo
|
|
315
|
-
npx @kokorolx/ai-sandbox-wrapper git disable ~/projects/myrepo
|
|
316
|
-
|
|
317
|
-
# Interactive menu
|
|
318
|
-
npx @kokorolx/ai-sandbox-wrapper update
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
### Network Configuration
|
|
322
|
-
|
|
323
|
-
AI containers can join Docker networks to communicate with other services (databases, APIs, MetaMCP).
|
|
324
|
-
|
|
325
|
-
#### Runtime Selection (Recommended)
|
|
326
|
-
|
|
327
|
-
```bash
|
|
328
|
-
# Interactive network selection
|
|
329
|
-
ai-run opencode -n
|
|
330
|
-
|
|
331
|
-
# Direct network specification
|
|
332
|
-
ai-run opencode -n metamcp_metamcp-network
|
|
333
|
-
ai-run opencode -n network1,network2,network3
|
|
59
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
60
|
+
OPENAI_API_KEY=sk-...
|
|
334
61
|
```
|
|
335
62
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
Network selections are saved to `~/.ai-sandbox/config.json`:
|
|
339
|
-
- **Per-workspace**: Saved for specific project directories
|
|
340
|
-
- **Global**: Default for all workspaces
|
|
341
|
-
|
|
63
|
+
### Workspaces
|
|
342
64
|
```bash
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
npx @kokorolx/ai-sandbox-wrapper network add mynetwork --global
|
|
346
|
-
npx @kokorolx/ai-sandbox-wrapper network add dev-network --workspace ~/projects/myapp
|
|
347
|
-
npx @kokorolx/ai-sandbox-wrapper network remove mynetwork --global
|
|
348
|
-
|
|
349
|
-
# View current config
|
|
350
|
-
npx @kokorolx/ai-sandbox-wrapper config show
|
|
351
|
-
npx @kokorolx/ai-sandbox-wrapper config show --json
|
|
352
|
-
|
|
353
|
-
# Example config.json structure (v2)
|
|
354
|
-
{
|
|
355
|
-
"version": 2,
|
|
356
|
-
"workspaces": ["/Users/you/projects/my-app"],
|
|
357
|
-
"git": {
|
|
358
|
-
"allowedWorkspaces": ["/Users/you/projects/my-repo"],
|
|
359
|
-
"keySelections": {}
|
|
360
|
-
},
|
|
361
|
-
"networks": {
|
|
362
|
-
"global": ["shared-services"],
|
|
363
|
-
"workspaces": {
|
|
364
|
-
"/Users/you/projects/my-app": ["my-app_default", "redis_network"]
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
65
|
+
npx @kokorolx/ai-sandbox-wrapper workspace add ~/projects/my-app
|
|
66
|
+
# Or: echo '/path/to/project' >> ~/.ai-sandbox/workspaces
|
|
368
67
|
```
|
|
369
68
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
When running without the flag, saved networks are used silently:
|
|
373
|
-
- Workspace-specific config takes priority
|
|
374
|
-
- Falls back to global config
|
|
375
|
-
- Non-existent networks are skipped silently
|
|
376
|
-
|
|
377
|
-
### Environment Variables
|
|
378
|
-
|
|
379
|
-
All environment variables are configured in `~/.ai-sandbox/env` or passed at runtime:
|
|
380
|
-
|
|
381
|
-
#### Image Source
|
|
382
|
-
Choose between locally built images or pre-built GitLab registry images:
|
|
69
|
+
### Port Exposure
|
|
383
70
|
|
|
384
71
|
```bash
|
|
385
|
-
#
|
|
72
|
+
# New --expose flag (recommended)
|
|
73
|
+
opencode --expose 3000
|
|
74
|
+
opencode -e 3000,5555,5556
|
|
386
75
|
|
|
387
|
-
#
|
|
388
|
-
|
|
76
|
+
# Expose to network
|
|
77
|
+
PORT_BIND=all opencode --expose 3000
|
|
389
78
|
|
|
390
|
-
#
|
|
391
|
-
|
|
79
|
+
# Legacy (deprecated)
|
|
80
|
+
PORT=3000 opencode
|
|
392
81
|
```
|
|
393
82
|
|
|
394
|
-
|
|
83
|
+
**Web Mode Auto-Detection:**
|
|
395
84
|
```bash
|
|
396
|
-
|
|
85
|
+
opencode web # Auto-exposes 4096
|
|
86
|
+
opencode web --port 8080 # Auto-exposes 8080
|
|
87
|
+
opencode --expose 3000 web # Exposes both 3000 and 4096
|
|
397
88
|
```
|
|
398
89
|
|
|
399
|
-
|
|
400
|
-
For ARM64 Macs or other platforms, specify the container platform:
|
|
401
|
-
|
|
402
|
-
```bash
|
|
403
|
-
# Run with specific platform (linux/arm64, linux/amd64)
|
|
404
|
-
AI_RUN_PLATFORM=linux/arm64 ai-run claude
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
#### Docker Connection
|
|
408
|
-
For remote Docker hosts or non-default configurations:
|
|
409
|
-
|
|
410
|
-
```bash
|
|
411
|
-
# Use a different Docker socket
|
|
412
|
-
export DOCKER_HOST=unix:///var/run/docker.sock
|
|
413
|
-
|
|
414
|
-
# Or TCP connection
|
|
415
|
-
export DOCKER_HOST=tcp://localhost:2375
|
|
90
|
+
Output:
|
|
416
91
|
```
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
```bash
|
|
422
|
-
# Expose a single port (localhost only - secure default)
|
|
423
|
-
PORT=3000 ai-run opencode
|
|
424
|
-
|
|
425
|
-
# Expose multiple ports
|
|
426
|
-
PORT=3000,5555,5556,5557 ai-run opencode
|
|
427
|
-
|
|
428
|
-
# Expose to network (use with caution)
|
|
429
|
-
PORT=3000 PORT_BIND=all ai-run opencode
|
|
92
|
+
🌐 Detected web command. Auto-exposing port 4096.
|
|
93
|
+
🔌 Port mappings: 4096
|
|
94
|
+
🌐 Web UI available at http://localhost:4096
|
|
430
95
|
```
|
|
431
96
|
|
|
432
|
-
|
|
433
|
-
|----------|--------|---------|-------------|
|
|
434
|
-
| `PORT` | Comma-separated ports | (none) | Ports to expose (e.g., `3000,5555`) |
|
|
435
|
-
| `PORT_BIND` | `localhost`, `all` | `localhost` | Bind to localhost only or all interfaces |
|
|
97
|
+
### Server Authentication (OpenCode web/serve)
|
|
436
98
|
|
|
437
|
-
|
|
438
|
-
- Default binding is `127.0.0.1` (localhost only) - only accessible from your machine
|
|
439
|
-
- Using `PORT_BIND=all` exposes ports to your network - a warning is displayed
|
|
440
|
-
- Invalid port numbers (outside 1-65535) are skipped with a warning
|
|
99
|
+
Control authentication for OpenCode web server:
|
|
441
100
|
|
|
442
|
-
**Example: Rails Development**
|
|
443
101
|
```bash
|
|
444
|
-
#
|
|
445
|
-
|
|
102
|
+
# Set password directly (visible in process list)
|
|
103
|
+
ai-run opencode web --password mysecret
|
|
104
|
+
ai-run opencode web -p mysecret
|
|
446
105
|
|
|
447
|
-
#
|
|
448
|
-
|
|
106
|
+
# Read password from environment variable (more secure)
|
|
107
|
+
MY_PASS=secret ai-run opencode web --password-env MY_PASS
|
|
449
108
|
|
|
450
|
-
#
|
|
109
|
+
# Explicitly allow unsecured mode (suppresses warning)
|
|
110
|
+
ai-run opencode web --allow-unsecured
|
|
451
111
|
```
|
|
452
112
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
```bash
|
|
457
|
-
# Required for Claude tools
|
|
458
|
-
ANTHROPIC_API_KEY=sk-ant-api03-...
|
|
459
|
-
|
|
460
|
-
# Required for OpenAI-based tools
|
|
461
|
-
OPENAI_API_KEY=sk-...
|
|
462
|
-
|
|
463
|
-
# Optional for Gemini CLI
|
|
464
|
-
GOOGLE_API_KEY=AIza...
|
|
465
|
-
|
|
466
|
-
# Optional: disable specific keys
|
|
467
|
-
# ANTHROPIC_API_KEY=
|
|
468
|
-
# OPENAI_API_KEY=
|
|
469
|
-
|
|
470
|
-
### Per-Project Config
|
|
471
|
-
|
|
472
|
-
Each tool supports project-specific config files that override global settings. These files are located in your workspace and are accessible to the tool:
|
|
473
|
-
|
|
474
|
-
| Tool | Project Config | Native Global Config |
|
|
475
|
-
|------|----------------|----------------------|
|
|
476
|
-
| Claude | `.claude.json` | `~/.config/claude/` |
|
|
477
|
-
| Gemini | `.gemini.json` | `~/.config/gemini/` |
|
|
478
|
-
| Aider | `.aider.conf` | `~/.config/aider/` |
|
|
479
|
-
| Opencode | `.opencode.json` | `~/.config/opencode/` |
|
|
480
|
-
| Amp | `.amp.json` | `~/.config/amp/` |
|
|
481
|
-
|
|
482
|
-
**Persistence:** Since v2.1.0, changes to global settings *inside* the container are automatically saved back to your **Native Global Config** on the host.
|
|
483
|
-
|
|
484
|
-
**Priority:** Project config > Native Global config > Container defaults
|
|
113
|
+
**Login credentials:**
|
|
114
|
+
- Username: `opencode` (default, override with `OPENCODE_SERVER_USERNAME` env var)
|
|
115
|
+
- Password: your configured password
|
|
485
116
|
|
|
486
|
-
|
|
487
|
-
# Example: Project-specific Claude config
|
|
488
|
-
cat > .claude.json << 'EOF'
|
|
489
|
-
{
|
|
490
|
-
"model": "sonnet-4-20250514",
|
|
491
|
-
"max_output_tokens": 8192,
|
|
492
|
-
"temperature": 0.7
|
|
493
|
-
}
|
|
494
|
-
EOF
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
### Tool-Specific Config Locations
|
|
117
|
+
**Precedence:** `--password` > `--password-env` > `OPENCODE_SERVER_PASSWORD` env > interactive prompt
|
|
498
118
|
|
|
499
|
-
|
|
119
|
+
Without flags, interactive mode shows a menu; non-interactive mode shows a security warning.
|
|
500
120
|
|
|
121
|
+
**Port Conflict Detection:**
|
|
501
122
|
```
|
|
502
|
-
|
|
503
|
-
├── .config/ # Tool configuration
|
|
504
|
-
│ └── {tool}/ # Per-tool config directory
|
|
505
|
-
├── .local/share/ # Tool data (cache, sessions)
|
|
506
|
-
├── .cache/ # Runtime cache
|
|
507
|
-
└── .claude/ # Claude-specific (for claude tool)
|
|
123
|
+
❌ ERROR: Port 3000 is already in use by node (PID: 12345)
|
|
508
124
|
```
|
|
509
125
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
### Additional Tools (Container-Only)
|
|
513
|
-
|
|
514
|
-
During setup, you can optionally install additional tools into the base Docker image. Tools are organized into two categories:
|
|
515
|
-
|
|
516
|
-
#### AI Enhancement Tools
|
|
517
|
-
|
|
518
|
-
| Tool | Description | Size Impact |
|
|
519
|
-
|------|-------------|-------------|
|
|
520
|
-
| spec-kit | Spec-driven development toolkit | ~50MB |
|
|
521
|
-
| ux-ui-promax | UI/UX design intelligence tool | ~30MB |
|
|
522
|
-
| openspec | OpenSpec - spec-driven development | ~20MB |
|
|
523
|
-
| playwright | Browser automation with Chromium/Firefox/WebKit | ~500MB |
|
|
524
|
-
|
|
525
|
-
**Playwright** is useful when AI tools need to:
|
|
526
|
-
- Run browser-based tests
|
|
527
|
-
- Scrape web content
|
|
528
|
-
- Verify UI changes
|
|
529
|
-
- Automate browser workflows
|
|
530
|
-
|
|
531
|
-
#### Language Runtimes
|
|
532
|
-
|
|
533
|
-
| Runtime | Description | Size Impact |
|
|
534
|
-
|---------|-------------|-------------|
|
|
535
|
-
| ruby | Ruby 3.3.0 + Rails 8.0.2 (via rbenv) | ~500MB |
|
|
536
|
-
|
|
537
|
-
**Ruby/Rails** is useful when:
|
|
538
|
-
- Developing Ruby on Rails applications
|
|
539
|
-
- Running Rails generators and migrations
|
|
540
|
-
- Using Bundler for dependency management
|
|
541
|
-
- Building Ruby-based APIs and web apps
|
|
542
|
-
|
|
543
|
-
#### Always Installed
|
|
544
|
-
|
|
545
|
-
- `typescript` + `typescript-language-server` - Required for AI coding assistants with LSP integration
|
|
546
|
-
|
|
547
|
-
#### Manual Installation
|
|
126
|
+
### Network Access
|
|
548
127
|
|
|
549
128
|
```bash
|
|
550
|
-
#
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
# Manual build with Ruby/Rails (if not selected during setup)
|
|
554
|
-
INSTALL_RUBY=1 bash lib/install-base.sh
|
|
555
|
-
|
|
556
|
-
# Verify Playwright in container
|
|
557
|
-
docker run --rm ai-base:latest npx playwright --version
|
|
558
|
-
|
|
559
|
-
# Verify Ruby/Rails in container
|
|
560
|
-
docker run --rm ai-base:latest ruby --version
|
|
561
|
-
docker run --rm ai-base:latest rails --version
|
|
562
|
-
|
|
563
|
-
# Verify TypeScript LSP
|
|
564
|
-
docker run --rm ai-base:latest tsc --version
|
|
129
|
+
# Join Docker networks (for databases, APIs, MetaMCP)
|
|
130
|
+
opencode -n mynetwork
|
|
131
|
+
opencode -n network1,network2
|
|
565
132
|
```
|
|
566
133
|
|
|
567
|
-
### Git
|
|
568
|
-
AI tools work **inside** containers without Git credentials by default (secure).
|
|
569
|
-
|
|
570
|
-
**Option 1: Secure (Default) - Review & Commit from Host**
|
|
571
|
-
```bash
|
|
572
|
-
# 1. AI tool makes changes
|
|
573
|
-
ai-run claude # Edits files in your workspace
|
|
574
|
-
|
|
575
|
-
# 2. Review changes on host
|
|
576
|
-
git diff
|
|
577
|
-
|
|
578
|
-
# 3. Commit from host (you have full control)
|
|
579
|
-
git add .
|
|
580
|
-
git commit -m "feat: changes suggested by AI"
|
|
581
|
-
git push
|
|
582
|
-
```
|
|
134
|
+
### Git Access
|
|
583
135
|
|
|
584
|
-
**
|
|
585
|
-
When you run an AI tool, you'll be prompted:
|
|
136
|
+
Git credentials are **not** shared by default. When you run a tool, you'll be prompted:
|
|
586
137
|
```
|
|
587
138
|
🔐 Git Access Control
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
1) Yes, allow once (this session only)
|
|
591
|
-
2) Yes, always allow for this workspace
|
|
139
|
+
1) Yes, allow once
|
|
140
|
+
2) Yes, always allow for this workspace
|
|
592
141
|
3) No, keep Git disabled (secure default)
|
|
593
142
|
```
|
|
594
143
|
|
|
595
|
-
|
|
596
|
-
```bash
|
|
597
|
-
# View allowed workspaces
|
|
598
|
-
cat ~/.ai-sandbox/git-allowed
|
|
144
|
+
## 📁 Directory Structure
|
|
599
145
|
|
|
600
|
-
|
|
601
|
-
|
|
146
|
+
```
|
|
147
|
+
~/.ai-sandbox/
|
|
148
|
+
├── config.json # Workspaces, git, networks
|
|
149
|
+
├── env # API keys
|
|
150
|
+
├── tools/ # Per-tool sandbox homes
|
|
151
|
+
│ └── opencode/home/
|
|
152
|
+
└── shared/git/ # Shared git credentials
|
|
602
153
|
```
|
|
603
154
|
|
|
604
|
-
|
|
605
|
-
-
|
|
606
|
-
-
|
|
607
|
-
- ✅ SSH keys mounted read-only
|
|
608
|
-
- ✅ You control which projects get Git access
|
|
609
|
-
- ✅ Easy to revoke access anytime
|
|
155
|
+
Native configs are bind-mounted:
|
|
156
|
+
- `~/.config/opencode` ↔ `/home/agent/.config/opencode`
|
|
157
|
+
- `~/.local/share/opencode` ↔ `/home/agent/.local/share/opencode`
|
|
610
158
|
|
|
611
159
|
## 🔐 Security Model
|
|
612
160
|
|
|
@@ -623,159 +171,50 @@ nano ~/.ai-sandbox/git-allowed # Delete the line
|
|
|
623
171
|
┌─────────────────────────────────────────────────┐
|
|
624
172
|
│ AI SANDBOX CONTAINER │
|
|
625
173
|
│ ✅ /workspace (whitelisted folders only) │
|
|
626
|
-
│ ✅ Passed API keys (explicit
|
|
627
|
-
│ ✅ Git config (
|
|
174
|
+
│ ✅ Passed API keys (explicit) │
|
|
175
|
+
│ ✅ Git config (opt-in per workspace) │
|
|
628
176
|
│ ❌ Everything else │
|
|
629
177
|
└─────────────────────────────────────────────────┘
|
|
630
178
|
```
|
|
631
179
|
|
|
632
|
-
## ❓ Troubleshooting
|
|
633
|
-
|
|
634
|
-
### Common Issues
|
|
635
|
-
|
|
636
|
-
**Docker not found**
|
|
637
|
-
- Make sure Docker Desktop is installed and running
|
|
638
|
-
- Check with: `docker --version` and `docker ps`
|
|
639
|
-
|
|
640
|
-
**"command not found: ai-run"**
|
|
641
|
-
- Reload your shell: `source ~/.zshrc`
|
|
642
|
-
- Verify setup completed: check if `~/bin/ai-run` exists
|
|
643
|
-
|
|
644
|
-
**"Workspaces not configured"** (Legacy)
|
|
645
|
-
- Note: This error is resolved in v2.1.0+.
|
|
646
|
-
- Run setup again: `./setup.sh` or simply run an AI tool in your project folder to trigger interactive whitelisting.
|
|
647
|
-
|
|
648
|
-
**"BunInstallFailedError"** (Resolved in v2.1.0)
|
|
649
|
-
- This was caused by stale caches. We now use **Cache Isolation** via anonymous volumes. If you still see this, run `./setup.sh --no-cache` to force a clean build.
|
|
650
|
-
|
|
651
|
-
**Tool doesn't start**
|
|
652
|
-
- Check if you selected the tool during setup
|
|
653
|
-
- Look for the Docker image: `docker images | grep ai-`
|
|
654
|
-
|
|
655
|
-
**"Outside whitelisted workspace" error**
|
|
656
|
-
- Add your current directory: `echo "$(pwd)" >> ~/.ai-sandbox/workspaces`
|
|
657
|
-
- Or navigate to a directory you whitelisted during setup
|
|
658
|
-
|
|
659
|
-
**API key errors**
|
|
660
|
-
- Check your keys in: `cat ~/.ai-sandbox/env`
|
|
661
|
-
- Make sure keys are in format: `KEY_NAME=actual_key_value`
|
|
662
|
-
|
|
663
|
-
### Getting Help
|
|
664
|
-
|
|
665
|
-
If you're still having issues:
|
|
666
|
-
1. Check that Docker is running
|
|
667
|
-
2. Re-run `./setup.sh` to reinstall
|
|
668
|
-
3. Look at the configuration files in `~/.ai-sandbox/`:
|
|
669
|
-
- `~/.ai-sandbox/workspaces` - should contain your project directories
|
|
670
|
-
- `~/.ai-sandbox/env` - should contain your API keys (if needed)
|
|
671
|
-
4. View Docker images: `docker images` to see if tools built successfully
|
|
672
|
-
|
|
673
180
|
## 📚 Quick Reference
|
|
674
181
|
|
|
675
|
-
### Main Commands
|
|
676
|
-
- `ai-run <tool>` - Run any tool in sandbox (e.g., `ai-run claude`)
|
|
677
|
-
- `ai-run <tool> --shell` - Start interactive shell mode (see [Shell Mode Guide](SHELL-MODE-USAGE.md))
|
|
678
|
-
- `<tool>` - Shortcut for tools you installed (e.g., `claude`, `aider`)
|
|
679
|
-
|
|
680
|
-
### Execution Modes
|
|
681
|
-
|
|
682
|
-
**Direct Mode (Default):**
|
|
683
182
|
```bash
|
|
684
|
-
|
|
685
|
-
#
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
**Shell Mode (Interactive):**
|
|
689
|
-
```bash
|
|
690
|
-
ai-run opencode --shell # or -s
|
|
691
|
-
# Starts bash shell, run tool manually
|
|
692
|
-
# Ctrl+C stops tool only, not container
|
|
693
|
-
# Perfect for development and debugging
|
|
694
|
-
```
|
|
183
|
+
# Run OpenCode
|
|
184
|
+
opencode # Direct mode
|
|
185
|
+
opencode --shell # Interactive shell
|
|
186
|
+
opencode web # Web UI mode
|
|
695
187
|
|
|
696
|
-
|
|
188
|
+
# Port exposure
|
|
189
|
+
opencode --expose 3000 # Expose port
|
|
190
|
+
opencode -e 3000,4000 # Multiple ports
|
|
697
191
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
- `~/.ai-sandbox/workspaces` - Whitelisted project directories
|
|
701
|
-
- `~/.ai-sandbox/cache/` - Tool cache (persistent)
|
|
702
|
-
- `~/.ai-sandbox/home/` - Tool configurations (persistent)
|
|
192
|
+
# Network
|
|
193
|
+
opencode -n mynetwork # Join Docker network
|
|
703
194
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
# Add a new project directory to AI access
|
|
707
|
-
echo '/path/to/my/new/project' >> ~/.ai-sandbox/workspaces
|
|
708
|
-
|
|
709
|
-
# Check what tools are installed
|
|
710
|
-
ls ~/bin/
|
|
711
|
-
|
|
712
|
-
# Reload shell after setup
|
|
713
|
-
source ~/.zshrc
|
|
714
|
-
|
|
715
|
-
# Update to latest version
|
|
716
|
-
npx @kokorolx/ai-sandbox-wrapper@latest setup
|
|
717
|
-
|
|
718
|
-
# Clean up caches and configs
|
|
719
|
-
npx @kokorolx/ai-sandbox-wrapper clean
|
|
720
|
-
```
|
|
721
|
-
|
|
722
|
-
### Cleanup Command
|
|
723
|
-
|
|
724
|
-
The `clean` command provides an interactive way to remove AI Sandbox directories:
|
|
725
|
-
|
|
726
|
-
```bash
|
|
195
|
+
# Management
|
|
196
|
+
npx @kokorolx/ai-sandbox-wrapper workspace list
|
|
727
197
|
npx @kokorolx/ai-sandbox-wrapper clean
|
|
728
198
|
```
|
|
729
199
|
|
|
730
|
-
|
|
731
|
-
- Two-level menu: First select category, then specific tools/items
|
|
732
|
-
- Shows directory sizes before deletion
|
|
733
|
-
- Groups items by risk level (🟢 Safe, 🟡 Medium, 🔴 Critical)
|
|
734
|
-
- Requires typing "yes" to confirm deletion
|
|
735
|
-
|
|
736
|
-
**Categories:**
|
|
737
|
-
| Category | Contents | Risk |
|
|
738
|
-
|----------|----------|------|
|
|
739
|
-
| Tool caches | `~/.ai-sandbox/cache/{tool}/` | 🟢 Safe to delete |
|
|
740
|
-
| Tool configs | `~/.ai-sandbox/home/{tool}/` | 🟡 Loses settings |
|
|
741
|
-
| Global config | `~/.ai-sandbox/workspaces`, `~/.ai-sandbox/env`, etc. | 🟡🔴 Mixed |
|
|
742
|
-
| Everything | `~/.ai-sandbox/` | 🔴 Full reset |
|
|
743
|
-
|
|
744
|
-
**Example:**
|
|
745
|
-
```
|
|
746
|
-
🧹 AI Sandbox Cleanup
|
|
747
|
-
|
|
748
|
-
What would you like to clean?
|
|
749
|
-
1. Tool caches (~/.ai-sandbox/cache/) - Safe to delete
|
|
750
|
-
2. Tool configs (~/.ai-sandbox/home/) - Loses settings
|
|
751
|
-
3. Global config files - Loses preferences
|
|
752
|
-
4. Everything (~/.ai-sandbox/) - Full reset
|
|
753
|
-
|
|
754
|
-
Enter selection (or 'q' to quit): 1
|
|
755
|
-
|
|
756
|
-
📁 Tool Caches (~/.ai-sandbox/cache/)
|
|
757
|
-
|
|
758
|
-
Select tools to clear:
|
|
759
|
-
1. claude/ (45.2 MB)
|
|
760
|
-
2. opencode/ (120.5 MB)
|
|
761
|
-
|
|
762
|
-
Enter selection (comma-separated, 'all', or 'b' to go back): 1
|
|
763
|
-
|
|
764
|
-
You are about to delete:
|
|
765
|
-
- ~/.ai-sandbox/cache/claude/ (45.2 MB)
|
|
200
|
+
## ❓ Troubleshooting
|
|
766
201
|
|
|
767
|
-
|
|
202
|
+
| Issue | Solution |
|
|
203
|
+
|-------|----------|
|
|
204
|
+
| `command not found: opencode` | Run `source ~/.zshrc` |
|
|
205
|
+
| `Outside whitelisted workspace` | `echo "$(pwd)" >> ~/.ai-sandbox/workspaces` |
|
|
206
|
+
| Port already in use | Stop the process or use different port |
|
|
207
|
+
| Docker not found | Install and start Docker Desktop |
|
|
768
208
|
|
|
769
|
-
|
|
209
|
+
## 📦 Other Tools
|
|
770
210
|
|
|
771
|
-
|
|
211
|
+
This sandbox also supports **Claude, Gemini, Aider, Kilo, Codex, Amp, Qwen**, and more.
|
|
772
212
|
|
|
773
|
-
|
|
774
|
-
```
|
|
213
|
+
See [TOOLS.md](TOOLS.md) for the full list and tool-specific configuration.
|
|
775
214
|
|
|
776
215
|
## 🤝 Contributing
|
|
777
216
|
|
|
778
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
217
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
779
218
|
|
|
780
219
|
## 📝 License
|
|
781
220
|
|
package/bin/ai-run
CHANGED
|
@@ -1,13 +1,65 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -e
|
|
3
3
|
|
|
4
|
+
# Show help if requested
|
|
5
|
+
show_help() {
|
|
6
|
+
cat << 'EOF'
|
|
7
|
+
Usage: ai-run <tool> [options] [-- tool-args...]
|
|
8
|
+
|
|
9
|
+
Run AI tools in a secure Docker sandbox.
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
-s, --shell Start interactive shell instead of running tool directly
|
|
13
|
+
-n, --network [name] Connect to Docker network(s) (comma-separated or interactive)
|
|
14
|
+
-e, --expose <ports> Expose container ports (comma-separated, e.g., 3000,5555)
|
|
15
|
+
-p, --password <value> Set OpenCode server password (for web/serve mode)
|
|
16
|
+
--password-env <VAR> Read OpenCode server password from environment variable
|
|
17
|
+
--allow-unsecured Allow OpenCode server to run without password (suppresses warning)
|
|
18
|
+
-h, --help Show this help message
|
|
19
|
+
|
|
20
|
+
OpenCode Server Authentication (web/serve mode only):
|
|
21
|
+
When running 'opencode web' or 'opencode serve', you can control authentication:
|
|
22
|
+
|
|
23
|
+
# Set password directly (visible in process list)
|
|
24
|
+
ai-run opencode web --password mysecret
|
|
25
|
+
|
|
26
|
+
# Read password from environment variable (more secure)
|
|
27
|
+
MY_PASS=secret ai-run opencode web --password-env MY_PASS
|
|
28
|
+
|
|
29
|
+
# Explicitly allow unsecured mode (suppresses warning)
|
|
30
|
+
ai-run opencode web --allow-unsecured
|
|
31
|
+
|
|
32
|
+
Default username: opencode (override with OPENCODE_SERVER_USERNAME env var)
|
|
33
|
+
Precedence: --password > --password-env > OPENCODE_SERVER_PASSWORD env > interactive prompt
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
ai-run claude # Run Claude in sandbox
|
|
37
|
+
ai-run opencode web -e 4096 # Run OpenCode web with port exposed
|
|
38
|
+
ai-run opencode web -p secret # Run with password
|
|
39
|
+
ai-run opencode --shell # Start shell, run tool manually
|
|
40
|
+
ai-run aider -n mynetwork # Connect to Docker network
|
|
41
|
+
|
|
42
|
+
Documentation: https://github.com/kokorolx/ai-sandbox-wrapper
|
|
43
|
+
EOF
|
|
44
|
+
exit 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Check for help flag before anything else
|
|
48
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
49
|
+
show_help
|
|
50
|
+
fi
|
|
51
|
+
|
|
4
52
|
TOOL="$1"
|
|
5
|
-
shift
|
|
53
|
+
shift 2>/dev/null || { echo "❌ ERROR: No tool specified. Use 'ai-run --help' for usage."; exit 1; }
|
|
6
54
|
|
|
7
55
|
# Parse flags
|
|
8
56
|
SHELL_MODE=false
|
|
9
57
|
NETWORK_FLAG=false
|
|
10
58
|
NETWORK_ARG=""
|
|
59
|
+
EXPOSE_ARG=""
|
|
60
|
+
SERVER_PASSWORD=""
|
|
61
|
+
PASSWORD_ENV_VAR=""
|
|
62
|
+
ALLOW_UNSECURED=false
|
|
11
63
|
TOOL_ARGS=()
|
|
12
64
|
|
|
13
65
|
while [[ $# -gt 0 ]]; do
|
|
@@ -25,6 +77,31 @@ while [[ $# -gt 0 ]]; do
|
|
|
25
77
|
shift
|
|
26
78
|
fi
|
|
27
79
|
;;
|
|
80
|
+
--expose|-e)
|
|
81
|
+
shift
|
|
82
|
+
if [[ $# -gt 0 && ! "$1" =~ ^- ]]; then
|
|
83
|
+
EXPOSE_ARG="$1"
|
|
84
|
+
shift
|
|
85
|
+
fi
|
|
86
|
+
;;
|
|
87
|
+
--password|-p)
|
|
88
|
+
shift
|
|
89
|
+
if [[ $# -gt 0 && ! "$1" =~ ^- ]]; then
|
|
90
|
+
SERVER_PASSWORD="$1"
|
|
91
|
+
shift
|
|
92
|
+
fi
|
|
93
|
+
;;
|
|
94
|
+
--password-env)
|
|
95
|
+
shift
|
|
96
|
+
if [[ $# -gt 0 && ! "$1" =~ ^- ]]; then
|
|
97
|
+
PASSWORD_ENV_VAR="$1"
|
|
98
|
+
shift
|
|
99
|
+
fi
|
|
100
|
+
;;
|
|
101
|
+
--allow-unsecured)
|
|
102
|
+
ALLOW_UNSECURED=true
|
|
103
|
+
shift
|
|
104
|
+
;;
|
|
28
105
|
*)
|
|
29
106
|
TOOL_ARGS+=("$1")
|
|
30
107
|
shift
|
|
@@ -118,7 +195,7 @@ migrate_to_v2() {
|
|
|
118
195
|
[[ -d "$SANDBOX_DIR/cache" ]] && needs_migration=true
|
|
119
196
|
[[ -d "$SANDBOX_DIR/git-keys" ]] && needs_migration=true
|
|
120
197
|
|
|
121
|
-
[[ "$needs_migration" == "false" ]] && { touch "$V2_MARKER"; return 0; }
|
|
198
|
+
[[ "$needs_migration" == "false" ]] && { mkdir -p "$SANDBOX_DIR" && touch "$V2_MARKER"; return 0; }
|
|
122
199
|
|
|
123
200
|
echo "🔄 Migrating to v2 folder structure..."
|
|
124
201
|
mkdir -p "$SANDBOX_DIR/tools" "$SANDBOX_DIR/shared/git"
|
|
@@ -1213,31 +1290,280 @@ if [[ -n "$TTY_FLAGS" ]]; then
|
|
|
1213
1290
|
CONTAINER_NAME="--name $(generate_container_name)"
|
|
1214
1291
|
fi
|
|
1215
1292
|
|
|
1216
|
-
#
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
BIND_ADDR="127.0.0.1"
|
|
1293
|
+
# ============================================================================
|
|
1294
|
+
# OPENCODE SERVER PASSWORD HANDLING
|
|
1295
|
+
# ============================================================================
|
|
1296
|
+
OPENCODE_PASSWORD_ENV=""
|
|
1221
1297
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1298
|
+
# Detect if running opencode web or serve command
|
|
1299
|
+
is_opencode_web_mode() {
|
|
1300
|
+
[[ "$TOOL" == "opencode" ]] || return 1
|
|
1301
|
+
for arg in "${TOOL_ARGS[@]}"; do
|
|
1302
|
+
[[ "$arg" == "web" || "$arg" == "serve" ]] && return 0
|
|
1303
|
+
done
|
|
1304
|
+
return 1
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
# Resolve password from CLI flags, env vars, or interactive prompt
|
|
1308
|
+
# Precedence: --password > --password-env > OPENCODE_SERVER_PASSWORD env > interactive/warning
|
|
1309
|
+
resolve_opencode_password() {
|
|
1310
|
+
# 1. CLI --password flag (highest priority)
|
|
1311
|
+
if [[ -n "$SERVER_PASSWORD" ]]; then
|
|
1312
|
+
OPENCODE_PASSWORD_ENV="-e OPENCODE_SERVER_PASSWORD=$SERVER_PASSWORD"
|
|
1313
|
+
return 0
|
|
1314
|
+
fi
|
|
1315
|
+
|
|
1316
|
+
# 2. CLI --password-env flag
|
|
1317
|
+
if [[ -n "$PASSWORD_ENV_VAR" ]]; then
|
|
1318
|
+
local env_value="${!PASSWORD_ENV_VAR:-}"
|
|
1319
|
+
if [[ -z "$env_value" ]]; then
|
|
1320
|
+
echo "❌ ERROR: Environment variable '$PASSWORD_ENV_VAR' is not set"
|
|
1321
|
+
exit 1
|
|
1322
|
+
fi
|
|
1323
|
+
OPENCODE_PASSWORD_ENV="-e OPENCODE_SERVER_PASSWORD=$env_value"
|
|
1324
|
+
return 0
|
|
1325
|
+
fi
|
|
1326
|
+
|
|
1327
|
+
# 3. Existing OPENCODE_SERVER_PASSWORD environment variable
|
|
1328
|
+
if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then
|
|
1329
|
+
OPENCODE_PASSWORD_ENV="-e OPENCODE_SERVER_PASSWORD=$OPENCODE_SERVER_PASSWORD"
|
|
1330
|
+
return 0
|
|
1331
|
+
fi
|
|
1332
|
+
|
|
1333
|
+
# 4. --allow-unsecured flag: skip prompt/warning, no password
|
|
1334
|
+
if [[ "$ALLOW_UNSECURED" == "true" ]]; then
|
|
1335
|
+
return 0
|
|
1336
|
+
fi
|
|
1337
|
+
|
|
1338
|
+
# 5. Interactive mode: show menu
|
|
1339
|
+
if [[ -t 0 ]]; then
|
|
1340
|
+
echo ""
|
|
1341
|
+
echo "🔐 OpenCode Web Server Security"
|
|
1342
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
1343
|
+
echo "The web server requires a password for security."
|
|
1344
|
+
echo ""
|
|
1345
|
+
echo " 1) Generate random password (recommended)"
|
|
1346
|
+
echo " 2) Enter custom password"
|
|
1347
|
+
echo " 3) No password (⚠️ unsecured - localhost only)"
|
|
1348
|
+
echo ""
|
|
1349
|
+
read -p "Choice [1-3]: " password_choice
|
|
1350
|
+
|
|
1351
|
+
case "$password_choice" in
|
|
1352
|
+
1)
|
|
1353
|
+
local generated_password=$(openssl rand -base64 24 | tr -d '/+=' | head -c 24)
|
|
1354
|
+
OPENCODE_PASSWORD_ENV="-e OPENCODE_SERVER_PASSWORD=$generated_password"
|
|
1355
|
+
echo ""
|
|
1356
|
+
echo "🔑 Generated password: $generated_password"
|
|
1357
|
+
echo "👤 Username: opencode (default)"
|
|
1358
|
+
echo ""
|
|
1359
|
+
echo "To connect from another terminal:"
|
|
1360
|
+
echo " OPENCODE_SERVER_PASSWORD='$generated_password' opencode attach http://localhost:4096"
|
|
1361
|
+
;;
|
|
1362
|
+
2)
|
|
1363
|
+
read -sp "Enter password: " custom_password
|
|
1364
|
+
echo ""
|
|
1365
|
+
if [[ -n "$custom_password" ]]; then
|
|
1366
|
+
OPENCODE_PASSWORD_ENV="-e OPENCODE_SERVER_PASSWORD=$custom_password"
|
|
1367
|
+
echo "✅ Custom password set"
|
|
1368
|
+
echo "👤 Username: opencode (default)"
|
|
1369
|
+
echo ""
|
|
1370
|
+
echo "To connect from another terminal:"
|
|
1371
|
+
echo " OPENCODE_SERVER_PASSWORD='<your-password>' opencode attach http://localhost:4096"
|
|
1372
|
+
else
|
|
1373
|
+
echo "⚠️ Empty password - server will be unsecured"
|
|
1374
|
+
fi
|
|
1375
|
+
;;
|
|
1376
|
+
*)
|
|
1377
|
+
echo "⚠️ No password set - server is unsecured"
|
|
1378
|
+
echo " Only use this for localhost access!"
|
|
1379
|
+
;;
|
|
1380
|
+
esac
|
|
1381
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
1382
|
+
echo ""
|
|
1383
|
+
else
|
|
1384
|
+
# 6. Non-interactive: show warning
|
|
1385
|
+
echo ""
|
|
1386
|
+
echo "🔐 OpenCode Web Server Security"
|
|
1387
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
1388
|
+
echo "⚠️ OPENCODE_SERVER_PASSWORD not set - server is unsecured"
|
|
1389
|
+
echo " Set OPENCODE_SERVER_PASSWORD environment variable for security"
|
|
1390
|
+
echo " Or use --allow-unsecured to suppress this warning"
|
|
1391
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
1392
|
+
echo ""
|
|
1393
|
+
fi
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
# Run password resolution if in web/serve mode
|
|
1397
|
+
if is_opencode_web_mode; then
|
|
1398
|
+
resolve_opencode_password
|
|
1399
|
+
fi
|
|
1400
|
+
|
|
1401
|
+
# ============================================================================
|
|
1402
|
+
# WEB COMMAND DETECTION AND PORT EXPOSURE
|
|
1403
|
+
# ============================================================================
|
|
1404
|
+
|
|
1405
|
+
# Detect if running opencode web command
|
|
1406
|
+
detect_opencode_web() {
|
|
1407
|
+
[[ "$TOOL" == "opencode" ]] || return 1
|
|
1408
|
+
for arg in "${TOOL_ARGS[@]}"; do
|
|
1409
|
+
[[ "$arg" == "web" ]] && return 0
|
|
1410
|
+
done
|
|
1411
|
+
return 1
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
# Parse --port value from TOOL_ARGS (supports --port N and --port=N)
|
|
1415
|
+
parse_port_from_args() {
|
|
1416
|
+
local i=0
|
|
1417
|
+
while [[ $i -lt ${#TOOL_ARGS[@]} ]]; do
|
|
1418
|
+
local arg="${TOOL_ARGS[$i]}"
|
|
1419
|
+
if [[ "$arg" == "--port" ]]; then
|
|
1420
|
+
((i++))
|
|
1421
|
+
if [[ $i -lt ${#TOOL_ARGS[@]} ]]; then
|
|
1422
|
+
echo "${TOOL_ARGS[$i]}"
|
|
1423
|
+
return 0
|
|
1424
|
+
fi
|
|
1425
|
+
elif [[ "$arg" =~ ^--port=(.+)$ ]]; then
|
|
1426
|
+
echo "${BASH_REMATCH[1]}"
|
|
1427
|
+
return 0
|
|
1428
|
+
fi
|
|
1429
|
+
((i++))
|
|
1430
|
+
done
|
|
1431
|
+
return 1
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
# Check if --hostname is already in TOOL_ARGS
|
|
1435
|
+
has_hostname_arg() {
|
|
1436
|
+
for arg in "${TOOL_ARGS[@]}"; do
|
|
1437
|
+
[[ "$arg" == "--hostname" || "$arg" =~ ^--hostname= ]] && return 0
|
|
1438
|
+
done
|
|
1439
|
+
return 1
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
# Check if port is in use (lsof with netstat fallback)
|
|
1443
|
+
check_port_in_use() {
|
|
1444
|
+
local port="$1"
|
|
1445
|
+
if command -v lsof &>/dev/null; then
|
|
1446
|
+
lsof -i ":$port" &>/dev/null && return 0
|
|
1447
|
+
elif command -v netstat &>/dev/null; then
|
|
1448
|
+
netstat -tuln 2>/dev/null | grep -q ":$port " && return 0
|
|
1449
|
+
else
|
|
1450
|
+
return 2
|
|
1225
1451
|
fi
|
|
1452
|
+
|
|
1453
|
+
docker ps --format "{{.Ports}}" 2>/dev/null | grep -q ":$port->" && return 0
|
|
1454
|
+
return 1
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
# Get process info for port
|
|
1458
|
+
get_port_process_info() {
|
|
1459
|
+
local port="$1"
|
|
1460
|
+
if command -v lsof &>/dev/null; then
|
|
1461
|
+
lsof -i ":$port" -sTCP:LISTEN 2>/dev/null | awk 'NR==2 {print $1 " (PID: " $2 ")"}'
|
|
1462
|
+
elif command -v netstat &>/dev/null; then
|
|
1463
|
+
echo "unknown process"
|
|
1464
|
+
else
|
|
1465
|
+
echo "unknown"
|
|
1466
|
+
fi
|
|
1467
|
+
}
|
|
1226
1468
|
|
|
1469
|
+
# Initialize port list (bash 3.x compatible - no associative arrays)
|
|
1470
|
+
EXPOSE_PORTS_LIST=""
|
|
1471
|
+
WEB_PORT=""
|
|
1472
|
+
|
|
1473
|
+
# Helper: add port to list if not already present
|
|
1474
|
+
add_port_to_list() {
|
|
1475
|
+
local port="$1"
|
|
1476
|
+
local source="$2"
|
|
1477
|
+
if [[ ! " $EXPOSE_PORTS_LIST " =~ " $port " ]]; then
|
|
1478
|
+
EXPOSE_PORTS_LIST="$EXPOSE_PORTS_LIST $port"
|
|
1479
|
+
if [[ "${AI_RUN_DEBUG:-}" == "1" ]]; then
|
|
1480
|
+
echo "🔧 Debug: Added port $port from $source"
|
|
1481
|
+
fi
|
|
1482
|
+
fi
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
# Parse --expose flag
|
|
1486
|
+
if [[ -n "$EXPOSE_ARG" ]]; then
|
|
1487
|
+
IFS=',' read -ra EXPOSE_PORTS <<< "$EXPOSE_ARG"
|
|
1488
|
+
for port in "${EXPOSE_PORTS[@]}"; do
|
|
1489
|
+
port=$(echo "$port" | tr -d ' ')
|
|
1490
|
+
if [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]; then
|
|
1491
|
+
add_port_to_list "$port" "--expose"
|
|
1492
|
+
else
|
|
1493
|
+
echo "⚠️ WARNING: Invalid port number in --expose: $port (skipped)"
|
|
1494
|
+
fi
|
|
1495
|
+
done
|
|
1496
|
+
fi
|
|
1497
|
+
|
|
1498
|
+
# Web command detection and auto-exposure
|
|
1499
|
+
WEB_DETECTED=false
|
|
1500
|
+
if detect_opencode_web; then
|
|
1501
|
+
WEB_DETECTED=true
|
|
1502
|
+
WEB_PORT=$(parse_port_from_args)
|
|
1503
|
+
if [[ -z "$WEB_PORT" ]]; then
|
|
1504
|
+
WEB_PORT=4096
|
|
1505
|
+
fi
|
|
1506
|
+
|
|
1507
|
+
add_port_to_list "$WEB_PORT" "auto-detected"
|
|
1508
|
+
echo "🌐 Detected web command. Auto-exposing port $WEB_PORT."
|
|
1509
|
+
|
|
1510
|
+
if ! has_hostname_arg; then
|
|
1511
|
+
TOOL_ARGS+=("--hostname" "0.0.0.0")
|
|
1512
|
+
fi
|
|
1513
|
+
fi
|
|
1514
|
+
|
|
1515
|
+
# Handle legacy PORT environment variable
|
|
1516
|
+
if [[ -n "${PORT:-}" ]]; then
|
|
1517
|
+
echo "⚠️ WARNING: PORT environment variable is deprecated. Use --expose flag instead."
|
|
1227
1518
|
IFS=',' read -ra PORTS <<< "$PORT"
|
|
1228
1519
|
for port in "${PORTS[@]}"; do
|
|
1229
|
-
# Trim whitespace
|
|
1230
1520
|
port=$(echo "$port" | tr -d ' ')
|
|
1231
|
-
# Validate port number (1-65535)
|
|
1232
1521
|
if [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]; then
|
|
1233
|
-
|
|
1522
|
+
add_port_to_list "$port" "PORT env"
|
|
1234
1523
|
else
|
|
1235
|
-
echo "⚠️ WARNING: Invalid port number: $port (skipped)"
|
|
1524
|
+
echo "⚠️ WARNING: Invalid port number in PORT: $port (skipped)"
|
|
1236
1525
|
fi
|
|
1237
1526
|
done
|
|
1527
|
+
fi
|
|
1528
|
+
|
|
1529
|
+
# Trim leading space from port list
|
|
1530
|
+
EXPOSE_PORTS_LIST=$(echo "$EXPOSE_PORTS_LIST" | sed 's/^ //')
|
|
1531
|
+
|
|
1532
|
+
# Port conflict detection
|
|
1533
|
+
PORT_CHECK_AVAILABLE=true
|
|
1534
|
+
if ! command -v lsof &>/dev/null && ! command -v netstat &>/dev/null; then
|
|
1535
|
+
echo "⚠️ WARNING: Cannot check port availability (lsof/netstat not found)"
|
|
1536
|
+
PORT_CHECK_AVAILABLE=false
|
|
1537
|
+
fi
|
|
1538
|
+
|
|
1539
|
+
if [[ "$PORT_CHECK_AVAILABLE" == "true" && -n "$EXPOSE_PORTS_LIST" ]]; then
|
|
1540
|
+
for port in $EXPOSE_PORTS_LIST; do
|
|
1541
|
+
if check_port_in_use "$port"; then
|
|
1542
|
+
PROCESS_INFO=$(get_port_process_info "$port")
|
|
1543
|
+
echo "❌ ERROR: Port $port is already in use by $PROCESS_INFO"
|
|
1544
|
+
exit 1
|
|
1545
|
+
fi
|
|
1546
|
+
done
|
|
1547
|
+
fi
|
|
1548
|
+
|
|
1549
|
+
# Build PORT_MAPPINGS from EXPOSE_PORTS_LIST
|
|
1550
|
+
if [[ -n "$EXPOSE_PORTS_LIST" ]]; then
|
|
1551
|
+
PORT_BIND="${PORT_BIND:-localhost}"
|
|
1552
|
+
BIND_ADDR="127.0.0.1"
|
|
1553
|
+
|
|
1554
|
+
if [[ "$PORT_BIND" == "all" ]]; then
|
|
1555
|
+
BIND_ADDR="0.0.0.0"
|
|
1556
|
+
echo "⚠️ WARNING: Ports will be accessible from network (PORT_BIND=all)"
|
|
1557
|
+
fi
|
|
1558
|
+
|
|
1559
|
+
for port in $EXPOSE_PORTS_LIST; do
|
|
1560
|
+
PORT_MAPPINGS="$PORT_MAPPINGS -p $BIND_ADDR:$port:$port"
|
|
1561
|
+
done
|
|
1238
1562
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1563
|
+
echo "🔌 Port mappings: $EXPOSE_PORTS_LIST"
|
|
1564
|
+
|
|
1565
|
+
if [[ "$WEB_DETECTED" == "true" ]]; then
|
|
1566
|
+
echo "🌐 Web UI available at http://localhost:$WEB_PORT"
|
|
1241
1567
|
fi
|
|
1242
1568
|
fi
|
|
1243
1569
|
|
|
@@ -1251,6 +1577,8 @@ if [[ "${AI_RUN_DEBUG:-}" == "1" ]]; then
|
|
|
1251
1577
|
echo "🔧 Debug: PORT='${PORT:-}'"
|
|
1252
1578
|
echo "🔧 Debug: PORT_BIND='${PORT_BIND:-localhost}'"
|
|
1253
1579
|
echo "🔧 Debug: PORT_MAPPINGS='$PORT_MAPPINGS'"
|
|
1580
|
+
echo "🔧 Debug: WEB_DETECTED='$WEB_DETECTED'"
|
|
1581
|
+
echo "🔧 Debug: EXPOSE_PORTS_LIST='$EXPOSE_PORTS_LIST'"
|
|
1254
1582
|
fi
|
|
1255
1583
|
|
|
1256
1584
|
# Prepare command based on mode
|
|
@@ -1322,6 +1650,7 @@ docker run $CONTAINER_NAME --rm $TTY_FLAGS \
|
|
|
1322
1650
|
$DISPLAY_FLAGS \
|
|
1323
1651
|
$HOST_ACCESS_ARGS \
|
|
1324
1652
|
$PORT_MAPPINGS \
|
|
1653
|
+
$OPENCODE_PASSWORD_ENV \
|
|
1325
1654
|
-v "$HOME_DIR":/home/agent \
|
|
1326
1655
|
-w "$CURRENT_DIR" \
|
|
1327
1656
|
--env-file "$ENV_FILE" \
|
package/package.json
CHANGED