@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.
Files changed (3) hide show
  1. package/README.md +108 -669
  2. package/bin/ai-run +345 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,612 +1,160 @@
1
1
  # 🔒 AI Sandbox Wrapper
2
2
 
3
- **Isolate AI coding agents from your host system. Protect your data.**
3
+ **Run OpenCode and other AI coding agents in secure Docker containers.**
4
4
 
5
- AI coding tools like Claude, Gemini, and Aider have full access to your filesystem, environment variables, and terminal. This project sandboxes them in Docker containers with **strict security restrictions**.
5
+ Protect your SSH keys, API tokens, and system files while using AI tools that need filesystem access.
6
6
 
7
- **What this does:** Runs AI tools in secure containers that can only access specific project folders, protecting your SSH keys, API tokens, and other sensitive data.
7
+ *Last updated: February 6, 2026*
8
8
 
9
- **What you get:** Peace of mind using AI coding tools without risking your personal and system data.
9
+ ## New in v2.3.0-beta: Web Mode & Port Exposure
10
10
 
11
- *Last updated: Tuesday, February 3, 2026*
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
- docker --version
66
- docker ps # Should not show errors
67
- ```
16
+ # Web mode - automatic port exposure
17
+ opencode web
68
18
 
69
- ### Step 2: Run Setup
19
+ # Custom port
20
+ opencode web --port 8080
70
21
 
71
- **Option A: Using npx (Recommended)**
72
- ```bash
73
- npx @kokorolx/ai-sandbox-wrapper setup
22
+ # Expose additional ports
23
+ opencode --expose 3000,5555 web
74
24
  ```
75
25
 
76
- **Option B: Clone and run manually**
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
- ### Step 3: Follow the Interactive Prompts
91
- 1. **Whitelist workspaces (Optional)** - Enter directories AI tools can access, or just hit **Enter** to whitelist on-demand later.
92
- 2. **Select tools** - Use arrow keys to move, space to select, Enter to confirm
93
- 3. **Choose image source** - Select registry (faster) or build locally
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
- ### Step 4: Complete Setup
96
- ```bash
97
- # Reload your shell to update PATH
98
- source ~/.zshrc
35
+ ## 🚀 Quick Start
99
36
 
100
- # Add your API keys (only if using tools that require them)
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
- # Navigate to a project directory that's in your whitelisted workspaces
107
- cd ~/projects/my-project
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
- ### Verify Everything Works
127
- ```bash
128
- # Reload your shell to get the new commands
43
+ # Reload shell
129
44
  source ~/.zshrc
130
45
 
131
- # Check if the main command works
132
- ai-run --help
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
- ### Configure API Keys (If Needed)
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
- ### Git Access Management
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
- #### Saved Configuration
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
- # CLI commands
344
- npx @kokorolx/ai-sandbox-wrapper network list
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
- #### Without `-n` Flag
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
- # Add to ~/.ai-sandbox/env
72
+ # New --expose flag (recommended)
73
+ opencode --expose 3000
74
+ opencode -e 3000,5555,5556
386
75
 
387
- # Use locally built images (default)
388
- AI_IMAGE_SOURCE=local
76
+ # Expose to network
77
+ PORT_BIND=all opencode --expose 3000
389
78
 
390
- # Use pre-built images from GitLab registry
391
- AI_IMAGE_SOURCE=registry
79
+ # Legacy (deprecated)
80
+ PORT=3000 opencode
392
81
  ```
393
82
 
394
- Or run with environment variable:
83
+ **Web Mode Auto-Detection:**
395
84
  ```bash
396
- AI_IMAGE_SOURCE=registry ai-run claude
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
- #### Platform Selection
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
- #### Port Exposure
419
- Expose container ports to the host for web development, APIs, and dev servers:
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
- | Variable | Values | Default | Description |
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
- **Security Notes:**
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
- # Start container with Rails default port exposed
445
- PORT=3000 ai-run opencode --shell
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
- # Inside container, start Rails server
448
- rails server -b 0.0.0.0
106
+ # Read password from environment variable (more secure)
107
+ MY_PASS=secret ai-run opencode web --password-env MY_PASS
449
108
 
450
- # Access from host browser at http://localhost:3000
109
+ # Explicitly allow unsecured mode (suppresses warning)
110
+ ai-run opencode web --allow-unsecured
451
111
  ```
452
112
 
453
- #### API Keys
454
- Configure in `~/.ai-sandbox/env`:
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
- ```bash
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
- All tool configs are consolidated under `~/.ai-sandbox/home/{tool}/`:
119
+ Without flags, interactive mode shows a menu; non-interactive mode shows a security warning.
500
120
 
121
+ **Port Conflict Detection:**
501
122
  ```
502
- ~/.ai-sandbox/home/{tool}/
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
- Each tool's config is mounted to `/home/agent/` inside the container.
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
- # Manual build with Playwright (if not selected during setup)
551
- INSTALL_PLAYWRIGHT=1 bash lib/install-base.sh
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 Workflow
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
- **Option 2: Enable Git Access (Interactive Prompt)**
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
- Allow AI tool to access Git credentials for this workspace?
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
- **Managing Git access:**
596
- ```bash
597
- # View allowed workspaces
598
- cat ~/.ai-sandbox/git-allowed
144
+ ## 📁 Directory Structure
599
145
 
600
- # Remove a workspace from allowed list
601
- nano ~/.ai-sandbox/git-allowed # Delete the line
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
- **Why this is secure:**
605
- - Opt-in per workspace (not global)
606
- - Granular control: Only selected keys and their matching Host configs are shared
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, for API calls)
627
- │ ✅ Git config (for commits)
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
- ai-run opencode
685
- # Tool runs directly, exits on Ctrl+C
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
- See [SHELL-MODE-USAGE.md](SHELL-MODE-USAGE.md) for detailed examples and use cases.
188
+ # Port exposure
189
+ opencode --expose 3000 # Expose port
190
+ opencode -e 3000,4000 # Multiple ports
697
191
 
698
- ### Configuration Files
699
- - `~/.ai-sandbox/env` - Store API keys here
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
- ### Common Tasks
705
- ```bash
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
- **Features:**
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
- Total: 45.2 MB
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
- Type 'yes' to confirm: yes
209
+ ## 📦 Other Tools
770
210
 
771
- Deleted ~/.ai-sandbox/cache/claude/
211
+ This sandbox also supports **Claude, Gemini, Aider, Kilo, Codex, Amp, Qwen**, and more.
772
212
 
773
- Deleted 1 items, freed 45.2 MB
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) for guidelines.
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
- # Port exposure configuration
1217
- PORT_MAPPINGS=""
1218
- if [[ -n "${PORT:-}" ]]; then
1219
- PORT_BIND="${PORT_BIND:-localhost}"
1220
- BIND_ADDR="127.0.0.1"
1293
+ # ============================================================================
1294
+ # OPENCODE SERVER PASSWORD HANDLING
1295
+ # ============================================================================
1296
+ OPENCODE_PASSWORD_ENV=""
1221
1297
 
1222
- if [[ "$PORT_BIND" == "all" ]]; then
1223
- BIND_ADDR="0.0.0.0"
1224
- echo "⚠️ WARNING: Ports will be accessible from network (PORT_BIND=all)"
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
- PORT_MAPPINGS="$PORT_MAPPINGS -p $BIND_ADDR:$port:$port"
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
- if [[ -n "$PORT_MAPPINGS" ]]; then
1240
- echo "🔌 Port mappings: ${PORT//,/ }"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kokorolx/ai-sandbox-wrapper",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Docker-based security sandbox for AI coding agents. Isolate Claude, Gemini, Aider, and other AI tools from your host system.",
5
5
  "keywords": [
6
6
  "ai",