swe-swe 0.1.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 +765 -0
  2. package/bin/swe-swe.js +90 -0
  3. package/package.json +25 -0
package/README.md ADDED
@@ -0,0 +1,765 @@
1
+ # swe-swe
2
+
3
+ Your agent: containerized with its own browser for manual testing. Your terminal: pair live or share recordings with teammates. Your sessions: run multiple in parallel, each on its own git worktree.
4
+
5
+ Works with Claude, Aider, Goose, Gemini, Codex, OpenCode. Not listed? [Let us know](https://github.com/choonkeat/swe-swe/issues)!
6
+
7
+ ## Quick Start
8
+
9
+ 1. **Install swe-swe**
10
+ ```bash
11
+ # Option A: install via curl (recommended)
12
+ curl -fsSL https://raw.githubusercontent.com/choonkeat/swe-swe/main/install.sh | sh
13
+
14
+ # Option B: run via npx (no install needed)
15
+ npx swe-swe init
16
+ alias swe-swe='npx -y swe-swe' # so the rest of the commands below work
17
+ ```
18
+
19
+ 2. **Initialize a project**
20
+ ```bash
21
+ swe-swe init --project-directory /path/to/your/project
22
+ ```
23
+
24
+ 3. **Start the environment**
25
+ ```bash
26
+ swe-swe up --project-directory /path/to/your/project
27
+ ```
28
+
29
+ 4. **Access the services** (use `https://` if initialized with `--ssl=selfsign`)
30
+ - **swe-swe terminal**: http://0.0.0.0:1977
31
+ - **VSCode**: http://0.0.0.0:1977/vscode
32
+ - **Chrome Browser**: http://0.0.0.0:1977/chrome (browser screencast viewer)
33
+ - **Traefik dashboard**: http://0.0.0.0:1977/dashboard/
34
+
35
+ 5. **View all initialized projects**
36
+ ```bash
37
+ swe-swe list
38
+ ```
39
+
40
+ 6. **Stop the environment**
41
+ ```bash
42
+ swe-swe down --project-directory /path/to/your/project
43
+ ```
44
+
45
+ ### Requirements
46
+
47
+ - Docker & Docker Compose installed
48
+ - Terminal access (works on macOS, Linux, Windows with WSL)
49
+
50
+ ## Commands
51
+
52
+ swe-swe has three native commands (`init`, `list`, and `proxy`). All other commands are passed directly to `docker compose` with the project's configuration.
53
+
54
+ ### Native Commands
55
+
56
+ #### `swe-swe init [options]`
57
+
58
+ Initializes a new swe-swe project at the specified path. Creates metadata directory at `$HOME/.swe-swe/projects/{sanitized-path}/` (see [Directory Structure](#directory-structure) for full layout).
59
+
60
+ **Options**:
61
+ - `--project-directory PATH`: Project directory (defaults to current directory)
62
+ - `--previous-init-flags=reuse`: Reapply saved configuration from previous init (cannot be combined with other flags)
63
+ - `--previous-init-flags=ignore`: Ignore saved configuration, use provided flags for fresh init
64
+ - `--agents AGENTS`: Comma-separated list of agents to include (default: all)
65
+ - `--exclude-agents AGENTS`: Comma-separated list of agents to exclude
66
+ - `--apt-get-install PACKAGES`: Additional apt packages to install
67
+ - `--npm-install PACKAGES`: Additional npm packages to install globally
68
+ - `--with-docker`: Mount Docker socket to allow container to run Docker commands on host
69
+ - `--with-slash-commands REPOS`: Git repos to clone as slash commands for Claude, Codex, and OpenCode (space-separated, format: `[alias@]<git-url>`)
70
+ - `--ssl MODE`: SSL/TLS mode - `no` (default, HTTP), `selfsign` (HTTPS with self-signed certificate), `selfsign@<hostname>` (includes hostname/IP in cert for remote access), `letsencrypt@domain` (HTTPS with Let's Encrypt), or `letsencrypt-staging@domain` (internal testing)
71
+ - `--email EMAIL`: Email for Let's Encrypt certificate expiry notifications (required with letsencrypt)
72
+ - `--copy-home-paths PATHS`: Comma-separated paths relative to `$HOME` to copy into the container's home directory (e.g., `.gitconfig,.ssh/config`)
73
+ - `--preview-ports RANGE`: App preview port range (default: `3000-3019`). Each session gets its own preview port from this range.
74
+ - `--status-bar-color COLOR`: Status bar background color (default: `#007acc`). Use `--status-bar-color=list` to see available preset colors.
75
+ - `--terminal-font-size SIZE`: Terminal font size in pixels (default: `14`)
76
+ - `--terminal-font-family FONT`: Terminal font family (default: `Menlo, Monaco, "Courier New", monospace`)
77
+ - `--status-bar-font-size SIZE`: Status bar font size in pixels (default: `12`)
78
+ - `--status-bar-font-family FONT`: Status bar font family (default: system sans-serif)
79
+
80
+ **Available Agents**: `claude`, `gemini`, `codex`, `aider`, `goose`, `opencode`
81
+
82
+ **Examples**:
83
+ ```bash
84
+ # Initialize current directory with all agents (default)
85
+ swe-swe init
86
+
87
+ # Initialize current directory with Claude only (minimal, fastest build)
88
+ swe-swe init --agents=claude
89
+
90
+ # Initialize current directory with Claude and Gemini
91
+ swe-swe init --agents=claude,gemini
92
+
93
+ # Initialize current directory without Python-based agents (no aider)
94
+ swe-swe init --exclude-agents=aider
95
+
96
+ # Initialize current directory with additional system packages
97
+ swe-swe init --apt-get-install="vim htop tmux"
98
+
99
+ # Initialize current directory with Docker access
100
+ swe-swe init --with-docker
101
+
102
+ # Initialize current directory with custom slash commands for Claude/Codex/OpenCode
103
+ swe-swe init --with-slash-commands=ck@https://github.com/choonkeat/slash-commands.git
104
+
105
+ # Initialize with HTTPS (self-signed certificate)
106
+ swe-swe init --ssl=selfsign
107
+
108
+ # Initialize with HTTPS (Let's Encrypt - requires public domain + port 80 access)
109
+ swe-swe init --ssl=letsencrypt@mydomain.com --email=admin@mydomain.com
110
+
111
+ # Initialize with Let's Encrypt staging (for testing - certs not browser-trusted)
112
+ swe-swe init --ssl=letsencrypt-staging@mydomain.com --email=admin@mydomain.com
113
+
114
+ # Initialize with git and SSH config copied from host
115
+ swe-swe init --copy-home-paths=.gitconfig,.ssh/config
116
+
117
+ # Initialize a specific directory
118
+ swe-swe init --project-directory ~/my-project
119
+
120
+ # Reinitialize with same configuration (after updates)
121
+ swe-swe init --previous-init-flags=reuse
122
+
123
+ # Reinitialize with new configuration (overwrite previous)
124
+ swe-swe init --previous-init-flags=ignore --agents=claude
125
+ ```
126
+
127
+ **Security Note on `--with-docker`**: Mounting the Docker socket grants the container effective root access to the host. The container can mount host filesystems, run privileged containers, and access other containers. Only use this flag when you trust the code running inside the container (e.g., for your own projects, not untrusted third-party code).
128
+
129
+ **Dependency Optimization**: The Dockerfile is automatically optimized based on selected agents:
130
+ - Python/pip is only installed if `aider` is included
131
+ - Node.js/npm is only installed if `claude`, `gemini`, `codex`, or `opencode` is included
132
+ - This can significantly reduce image size and build time
133
+
134
+ Services are accessible via path-based routing on `localhost` (or `0.0.0.0`) at the configured port.
135
+
136
+ **Note on Port**: The port defaults to `1977` and can be customized via the `SWE_PORT` environment variable (e.g., `SWE_PORT=8080 swe-swe up`). Services are routed based on request paths (e.g., `/vscode`, `/chrome`) rather than subdomains, making it compatible with ngrok, cloudflared, and other tunnel services.
137
+
138
+ **Enterprise Certificate Support**: If you're behind a corporate firewall or VPN, the init command automatically detects and copies certificates from:
139
+ - `NODE_EXTRA_CA_CERTS`
140
+ - `SSL_CERT_FILE`
141
+ - `NODE_EXTRA_CA_CERTS_BUNDLE`
142
+
143
+ These are copied to `.swe-swe/certs/` and mounted into all containers.
144
+
145
+ #### `swe-swe list`
146
+
147
+ Lists all initialized swe-swe projects and automatically prunes stale ones.
148
+
149
+ **What it does:**
150
+ 1. Scans `$HOME/.swe-swe/projects/` directory
151
+ 2. For each project, reads `.path` file to get original path
152
+ 3. Checks if original path still exists
153
+ 4. Auto-removes metadata directories for deleted projects (pruning)
154
+ 5. Displays remaining active projects with count
155
+ 6. Shows summary of pruned projects
156
+
157
+ **When to use:**
158
+ - Discover what projects you have initialized
159
+ - Clean up metadata for deleted projects
160
+ - Verify project paths before cleanup
161
+
162
+ Example:
163
+ ```bash
164
+ swe-swe list
165
+ # Output:
166
+ # Initialized projects (2):
167
+ # /Users/alice/projects/myapp
168
+ # /Users/alice/projects/anotherapp
169
+ #
170
+ # Removed 1 stale project(s)
171
+ ```
172
+
173
+ #### `swe-swe proxy <command>`
174
+
175
+ Bridges the container-host boundary by letting containers execute specific host commands with real-time output streaming. This is the **secure alternative to `--with-docker`** when you only need access to specific commands rather than full Docker socket access.
176
+
177
+ **When to use proxy vs `--with-docker`:**
178
+
179
+ | Scenario | Solution |
180
+ |----------|----------|
181
+ | AI agent needs to run `make` with host toolchain | `swe-swe proxy make` |
182
+ | Need `docker build` without exposing full Docker socket | `swe-swe proxy docker` |
183
+ | Access host's SSH agent for git operations | `swe-swe proxy git` |
184
+ | Full Docker/container management needed | `--with-docker` flag |
185
+
186
+ **How it works:**
187
+ 1. Host runs `swe-swe proxy <command>` which watches `.swe-swe/proxy/` for requests
188
+ 2. A container script is generated at `.swe-swe/proxy/<command>`
189
+ 3. Container runs `.swe-swe/proxy/<command> [args...]` to submit a request
190
+ 4. Host executes the command and streams stdout/stderr back to container in real-time
191
+ 5. Container exits with the command's exit code
192
+
193
+ **Key features:**
194
+ - **Real-time streaming**: Output appears immediately, not after command completes
195
+ - **Exit code propagation**: Container script exits with the host command's exit code
196
+ - **Stdin forwarding**: Piped/redirected input is forwarded to the host command (e.g., `echo "data" | .swe-swe/proxy/cat`)
197
+ - **Concurrent requests**: Multiple container processes can use the same proxy simultaneously
198
+ - **Efficient file watching**: Uses inotify when available, falls back to polling
199
+
200
+ **Examples:**
201
+ ```bash
202
+ # Terminal 1: Start proxy for 'make' command
203
+ swe-swe proxy make
204
+ # [proxy] Listening for 'make' commands... (Ctrl+C to stop)
205
+
206
+ # Terminal 2 (inside container): Run make with arguments
207
+ .swe-swe/proxy/make build TARGET=hello
208
+ # Output streams in real-time, exits with make's exit code
209
+
210
+ # Multiple proxies (run each in separate terminals)
211
+ swe-swe proxy make
212
+ swe-swe proxy docker
213
+ swe-swe proxy npm
214
+ ```
215
+
216
+ **Real-world use cases:**
217
+ ```bash
218
+ # Cross-compile with host toolchain
219
+ swe-swe proxy make
220
+ # In container: .swe-swe/proxy/make build-linux-arm64
221
+
222
+ # Run Docker commands without --with-docker
223
+ swe-swe proxy docker
224
+ # In container: .swe-swe/proxy/docker build -t myapp .
225
+
226
+ # Use host's authenticated npm registry
227
+ swe-swe proxy npm
228
+ # In container: .swe-swe/proxy/npm publish
229
+ ```
230
+
231
+ **Environment Variables:**
232
+ - `PROXY_TIMEOUT`: Timeout in seconds for container script (default: 300)
233
+ - `PROXY_DIR`: Override proxy directory (default: `.swe-swe/proxy`)
234
+
235
+ **File protocol:**
236
+ ```
237
+ .swe-swe/proxy/
238
+ ├── <command> # Generated container script (executable)
239
+ ├── <command>.pid # Host PID file (prevents duplicate proxies)
240
+ ├── <uuid>.req # Request file (NUL-delimited args)
241
+ ├── <uuid>.stdin # Request stdin (if piped/redirected input)
242
+ ├── <uuid>.stdout # Response stdout (streamed)
243
+ ├── <uuid>.stderr # Response stderr (streamed)
244
+ └── <uuid>.exit # Exit code (signals completion)
245
+ ```
246
+
247
+ **Cleanup responsibilities:**
248
+
249
+ | File | Cleaned up by | When |
250
+ |------|---------------|------|
251
+ | `<uuid>.req` | Host | After reading (claims the request) |
252
+ | `<uuid>.stdout/stderr/exit` | Container | On script exit (via trap) |
253
+ | `<command>`, `<command>.pid` | Host | On proxy shutdown (Ctrl+C) |
254
+ | Orphan response files | Host | On startup (files older than 5 min) |
255
+
256
+ ### Docker Compose Pass-through
257
+
258
+ All commands other than `init`, `list`, and `proxy` are passed directly to `docker compose` using the project's generated `docker-compose.yml`. This means you can use any docker compose command:
259
+
260
+ ```bash
261
+ swe-swe up # docker compose up
262
+ swe-swe down # docker compose down
263
+ swe-swe build # docker compose build
264
+ swe-swe ps # docker compose ps
265
+ swe-swe logs -f swe-swe # docker compose logs -f swe-swe
266
+ swe-swe exec swe-swe bash # docker compose exec swe-swe bash
267
+ swe-swe restart chrome # docker compose restart chrome
268
+ ```
269
+
270
+ Use `--project-directory` to specify which project (defaults to current directory):
271
+ ```bash
272
+ swe-swe up --project-directory ~/my-project
273
+ swe-swe logs -f --project-directory ~/my-project
274
+ ```
275
+
276
+ #### `swe-swe up [--project-directory PATH]`
277
+
278
+ Starts the swe-swe environment using `docker compose up`. The environment includes:
279
+
280
+ 1. **swe-swe**: WebSocket-based AI terminal with session management
281
+ 2. **chrome**: Headless Chromium with CDP screencast for browser automation (used by MCP Playwright)
282
+ 3. **code-server**: VS Code IDE running in a container
283
+ 4. **vscode-proxy**: Nginx proxy for VSCode path routing
284
+ 5. **traefik**: HTTP reverse proxy with path-based routing rules
285
+ 6. **auth**: ForwardAuth service for unified password protection
286
+
287
+ The workspace is mounted at `/workspace` inside containers, allowing bidirectional file access.
288
+
289
+ Example:
290
+ ```bash
291
+ swe-swe up --project-directory ~/my-project
292
+ # Press Ctrl+C to stop
293
+ ```
294
+
295
+ **Environment Variables** (passed through automatically):
296
+ - `ANTHROPIC_API_KEY`: Claude API key (passed by default)
297
+ - `SWE_SWE_PASSWORD`: Authentication password for all services (defaults to `changeme`)
298
+ - `SWE_PORT`: External port (defaults to 1977)
299
+ - `NODE_EXTRA_CA_CERTS`: Enterprise CA certificate path (auto-copied during init)
300
+ - `SSL_CERT_FILE`: SSL certificate file path (auto-copied during init)
301
+ - `BROWSER_WS_ENDPOINT`: WebSocket endpoint for browser automation (auto-configured to `ws://chrome:9223`)
302
+
303
+ **Additional API keys** (uncomment in docker-compose.yml if needed):
304
+ - `OPENAI_API_KEY`: OpenAI API key (for Codex)
305
+ - `GEMINI_API_KEY`: Google Gemini API key
306
+
307
+ #### `swe-swe down [--project-directory PATH]`
308
+
309
+ Stops and removes the running Docker containers for the project.
310
+
311
+ Example:
312
+ ```bash
313
+ swe-swe down --project-directory ~/my-project
314
+ ```
315
+
316
+ #### `swe-swe build [--project-directory PATH]`
317
+
318
+ Rebuilds the Docker image from scratch (clears cache). Useful when:
319
+ - Updating the base image
320
+ - Installing new dependencies in Dockerfile
321
+ - Testing fresh builds
322
+
323
+ Example:
324
+ ```bash
325
+ swe-swe build --project-directory ~/my-project
326
+ ```
327
+
328
+ #### `swe-swe -h` / `swe-swe --help`
329
+
330
+ Displays the help message with all available commands.
331
+
332
+ ## Architecture
333
+
334
+ ### Directory Structure
335
+
336
+ Project metadata is stored in `$HOME/.swe-swe/projects/{sanitized-path}/`:
337
+
338
+ ```
339
+ $HOME/.swe-swe/projects/{sanitized-path}/
340
+ ├── Dockerfile # Container image definition (multi-stage build)
341
+ ├── docker-compose.yml # Service orchestration
342
+ ├── traefik-dynamic.yml # HTTP routing rules
343
+ ├── .dockerignore # Docker build exclusions
344
+ ├── .path # Original project path (for discovery)
345
+ ├── init.json # Saved init flags (for --previous-init-flags=reuse)
346
+ ├── entrypoint.sh # Container entrypoint script
347
+ ├── nginx-vscode.conf # Nginx config for VSCode proxy
348
+ ├── swe-swe-server/ # Server source code (built at docker-compose time)
349
+ │ ├── go.mod, go.sum
350
+ │ ├── main.go
351
+ │ └── static/
352
+ ├── auth/ # ForwardAuth service source code
353
+ ├── chrome-screencast/ # Chrome screencast service (Dockerfile, configs)
354
+ ├── home/ # Persistent VSCode/shell home (volume)
355
+ ├── certs/ # Enterprise certificates (if detected)
356
+ ├── tls/ # Self-signed TLS certificates (if --ssl=selfsign)
357
+ └── acme/ # Let's Encrypt ACME storage (if --ssl=letsencrypt@domain)
358
+ ```
359
+
360
+ **Why metadata is stored outside the project:**
361
+ - Prevents container access to infrastructure configuration
362
+ - Allows metadata cleanup via `swe-swe list` command
363
+ - Centralizes all metadata in one location
364
+
365
+ **Note**: A `.swe-swe/` directory is created inside your project for runtime data (worktrees, repos, proxy, uploads). This is separate from the metadata directory above.
366
+
367
+ ### Services
368
+
369
+ #### swe-swe-server
370
+ - **Port**: 9898 (inside container)
371
+ - **Purpose**: WebSocket-based AI coding assistant terminal
372
+ - **Features**:
373
+ - Real-time terminal with PTY support
374
+ - Session management (sessions persist until process exits)
375
+ - Multiple AI assistant detection (claude, gemini, codex, goose, aider, opencode)
376
+ - All process exits end the session (process replacement only via YOLO toggle)
377
+ - File upload via drag-and-drop (saved to `.swe-swe/uploads/` on tmpfs — files do not persist across container restarts)
378
+ - In-session chat for collaboration
379
+ - YOLO mode toggle for supported agents (auto-approve actions)
380
+ - **Split-pane UI**: Side panel with Preview, Browser, and Shell tabs for app preview and debugging
381
+ - **Session recordings**: Automatic terminal recording with playback (streaming mode for large recordings)
382
+ - **Debug channel**: Agents can receive console logs, errors, and network requests from the App Preview via `swe-swe-server --debug-listen`
383
+
384
+ #### chrome
385
+ - **Ports**: 9223 (CDP), 6080 (screencast)
386
+ - **Purpose**: Headless Chromium browser for AI-driven browser automation
387
+ - **Features**:
388
+ - Chrome DevTools Protocol (CDP) access via nginx proxy
389
+ - CDP screencast viewer at `/chrome` path (e.g., http://0.0.0.0:1977/chrome)
390
+ - Used by MCP Playwright for browser automation tasks
391
+ - Enterprise SSL certificate support via NSS database
392
+ - **Documentation**: See `docs/browser-automation.md`
393
+
394
+ #### code-server
395
+ - **Port**: 8080 (inside container)
396
+ - **Purpose**: Full VS Code IDE in the browser
397
+ - **Features**:
398
+ - Syntax highlighting, extensions support
399
+ - Direct file editing in `/workspace`
400
+ - Terminal integration
401
+
402
+ #### traefik
403
+ - **Port**: 7000 HTTP or 7443 HTTPS (external port 1977)
404
+ - **Purpose**: Reverse proxy and routing with path-based request matching
405
+ - **Routing Rules**:
406
+ - `/swe-swe-auth/*` path: Auth service for ForwardAuth (priority 200)
407
+ - `/vscode` path: Routes to code-server with path prefix stripped (priority 100)
408
+ - `/chrome` path: Routes to chrome service with path prefix stripped (priority 100)
409
+ - `/dashboard` path: Traefik dashboard (priority 100)
410
+ - `/` path: Routes to swe-swe-server (priority 10, catch-all)
411
+ - **Authentication**: All routes (except auth) protected by ForwardAuth middleware
412
+
413
+ #### auth
414
+ - **Port**: 4180 (internal)
415
+ - **Purpose**: ForwardAuth service for unified authentication
416
+ - **Features**:
417
+ - Cookie-based session management
418
+ - Redirect to original URL after login
419
+ - Mobile-responsive login page
420
+
421
+ ### Network
422
+
423
+ Each project gets its own isolated Docker network (`{PROJECT_NAME}_swe-network`). This provides:
424
+ - Service discovery by container name within each project
425
+ - True network isolation between projects (no DNS conflicts)
426
+ - Multiple stacks can run simultaneously without interference
427
+
428
+ ## Configuration
429
+
430
+ ### Customizing the Dockerfile
431
+
432
+ The Dockerfile is located in `$HOME/.swe-swe/projects/{sanitized-path}/Dockerfile`. Edit it to:
433
+ - Add Python: `apt-get install -y python3 python3-pip`
434
+ - Add Rust: `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y`
435
+ - Add other tools via `apt-get`
436
+
437
+ **UID:GID Placeholders**: The Dockerfile supports `{{UID}}` and `{{GID}}` placeholders that are replaced with the host user's UID/GID at init time. This ensures files created in the container have matching permissions on the host, eliminating permission conflicts when editing files from both sides.
438
+
439
+ ### API Keys
440
+
441
+ **Claude** (passed through by default):
442
+ ```bash
443
+ export ANTHROPIC_API_KEY=sk-ant-...
444
+ swe-swe up --project-directory ~/my-project
445
+ ```
446
+
447
+ **Gemini/Codex** (requires editing docker-compose.yml to uncomment the env vars):
448
+ ```bash
449
+ # First, edit $HOME/.swe-swe/projects/{sanitized-path}/docker-compose.yml
450
+ # Uncomment the GEMINI_API_KEY and/or OPENAI_API_KEY lines in the swe-swe service
451
+
452
+ export GEMINI_API_KEY=...
453
+ export OPENAI_API_KEY=sk-...
454
+ swe-swe up --project-directory ~/my-project
455
+ ```
456
+
457
+ Alternatively, create a `.env` file in `$HOME/.swe-swe/projects/{sanitized-path}/`:
458
+ ```
459
+ ANTHROPIC_API_KEY=sk-ant-...
460
+ GEMINI_API_KEY=...
461
+ OPENAI_API_KEY=sk-...
462
+ ```
463
+
464
+ ### Resource Limits
465
+
466
+ Edit `$HOME/.swe-swe/projects/{sanitized-path}/docker-compose.yml` to adjust resource constraints:
467
+
468
+ ```yaml
469
+ code-server:
470
+ deploy:
471
+ resources:
472
+ limits:
473
+ cpus: '2'
474
+ memory: 2G
475
+ ```
476
+
477
+ ### VSCode Password
478
+
479
+ The default VSCode password is `changeme`. To use a custom password, set the `SWE_SWE_PASSWORD` environment variable:
480
+
481
+ ```bash
482
+ # Default password (changeme)
483
+ swe-swe up --project-directory ~/my-project
484
+
485
+ # Custom password
486
+ SWE_SWE_PASSWORD='my-secure-password' swe-swe up --project-directory ~/my-project
487
+ ```
488
+
489
+ ## Development
490
+
491
+ > For detailed build architecture and troubleshooting, see [docs/cli-commands-and-binary-management.md](docs/cli-commands-and-binary-management.md).
492
+
493
+ ### Building from Source
494
+
495
+ ```bash
496
+ # Build CLI binaries for all platforms
497
+ make build
498
+
499
+ # Run tests
500
+ make test
501
+ ```
502
+
503
+ The swe-swe-server is built from source at `docker-compose build` time using a multi-stage Dockerfile. This means:
504
+ - No pre-compiled server binaries are embedded in the CLI
505
+ - The server is always compiled fresh when the Docker image is built
506
+ - Changes to server source code in `cmd/swe-swe/templates/host/swe-swe-server/` are reflected after `swe-swe build`
507
+
508
+ ### Project Structure
509
+
510
+ ```
511
+ .
512
+ ├── cmd/
513
+ │ └── swe-swe/ # CLI tool
514
+ │ ├── *.go # main, init, docker, templates, certs, paths, list, proxy, color, ansi
515
+ │ └── templates/ # Embedded Docker/server files
516
+ │ └── host/
517
+ │ ├── Dockerfile, docker-compose.yml, etc.
518
+ │ └── swe-swe-server/ # WebSocket server source
519
+ ├── docs/ # Technical documentation
520
+ ├── Makefile # Build targets
521
+ ├── go.mod, go.sum # Go dependencies
522
+ └── README.md # This file
523
+ ```
524
+
525
+ ### Key Dependencies
526
+
527
+ - **gorilla/websocket**: WebSocket support for real-time terminal
528
+ - **creack/pty**: PTY (pseudo-terminal) for shell sessions
529
+ - **vt10x**: VT100 terminal emulation
530
+ - **uuid**: Session ID generation
531
+
532
+ ## Troubleshooting
533
+
534
+ ### Binary Architecture Error
535
+
536
+ **Error**: `exec format error`
537
+
538
+ **Solution**: Rebuild the container image which compiles the server from source:
539
+ ```bash
540
+ swe-swe build --project-directory ~/my-project
541
+ ```
542
+
543
+ ### Port Already in Use
544
+
545
+ **Error**: `port 1977 already allocated`
546
+
547
+ **Solution**: Stop other projects or use a custom port via environment variable:
548
+ ```bash
549
+ # Use a different port
550
+ SWE_PORT=8080 swe-swe up --project-directory ~/my-project
551
+ ```
552
+
553
+ Alternatively, modify `$HOME/.swe-swe/projects/{sanitized-path}/docker-compose.yml` to change the port mapping.
554
+
555
+ ### API Key Not Found
556
+
557
+ **Error**: `no AI assistants available`
558
+
559
+ **Solution**: The server didn't detect an installed assistant:
560
+ 1. Set API keys: `ANTHROPIC_API_KEY` (Claude), `GEMINI_API_KEY` (Gemini), `OPENAI_API_KEY` (Codex)
561
+ 2. Or install CLI tools: `claude`, `gemini`, `codex`, `goose`, `aider`, `opencode`
562
+
563
+ ### Network Issues
564
+
565
+ **Error**: Service not accessible at configured port (default 1977)
566
+
567
+ **Solution**:
568
+ 1. Verify Docker is running: `docker ps`
569
+ 2. Check containers are healthy: `swe-swe ps --project-directory ~/my-project`
570
+ 3. Check Traefik logs: `swe-swe logs traefik --project-directory ~/my-project`
571
+ 4. Verify you're using correct paths: `http://0.0.0.0:1977/`, `http://0.0.0.0:1977/vscode`, `http://0.0.0.0:1977/chrome`
572
+
573
+ ### Let's Encrypt Certificate Issues
574
+
575
+ **Error**: `domain "example.com" does not resolve`
576
+
577
+ **Solution**: Ensure your domain's DNS is configured and points to your server before running init:
578
+ ```bash
579
+ # Verify DNS resolution
580
+ dig +short mydomain.com
581
+ ```
582
+
583
+ **Error**: ACME challenge fails / certificate not issued
584
+
585
+ **Solutions**:
586
+ 1. Ensure port 80 is accessible from the internet (firewall/security groups)
587
+ 2. Verify domain resolves to this server's public IP
588
+ 3. Try staging mode first to avoid rate limits: `--ssl=letsencrypt-staging@domain`
589
+
590
+ **Error**: Rate limited by Let's Encrypt
591
+
592
+ **Solution**: Let's Encrypt allows 50 certificates per domain per week. Use staging mode for testing, or wait before retrying.
593
+
594
+ **Certificate not renewing**
595
+
596
+ Let's Encrypt certs renew automatically via Traefik. If renewal fails:
597
+ 1. Ensure the container is running: `swe-swe ps`
598
+ 2. Check Traefik logs: `swe-swe logs traefik`
599
+ 3. Verify port 80 is still accessible
600
+
601
+ **iOS Safari works with Let's Encrypt**
602
+
603
+ Unlike self-signed certificates, Let's Encrypt certs work in iOS Safari without issues. This is the recommended approach for mobile access.
604
+
605
+ ### Persistent Home Issues
606
+
607
+ If VSCode settings/extensions don't persist:
608
+ 1. Verify `$HOME/.swe-swe/projects/{sanitized-path}/home/` exists and has correct permissions
609
+ 2. Check that the metadata directory wasn't accidentally deleted
610
+ 3. Reinitialize the project: `swe-swe init --project-directory /path/to/project`
611
+
612
+ ## Advanced Usage
613
+
614
+ ### Running Multiple Projects
615
+
616
+ Each project gets its own isolated environment. No conflicts:
617
+
618
+ ```bash
619
+ # Terminal 1
620
+ swe-swe init --project-directory ~/project1
621
+ swe-swe up --project-directory ~/project1
622
+
623
+ # Terminal 2
624
+ swe-swe init --project-directory ~/project2
625
+ swe-swe up --project-directory ~/project2
626
+ # Use different ports if accessing locally
627
+ ```
628
+
629
+ ### Custom Shell
630
+
631
+ Use a custom shell by modifying the Dockerfile CMD:
632
+
633
+ ```dockerfile
634
+ ENV SHELL=/bin/zsh
635
+ CMD ["/usr/local/bin/swe-swe-server", "-shell", "zsh", "-working-directory", "/workspace", "-addr", "0.0.0.0:9898"]
636
+ ```
637
+
638
+ ### Session Persistence
639
+
640
+ Sessions persist until the shell process exits. All exits (zero or non-zero) end the session. Process replacement only occurs via explicit user action (YOLO toggle).
641
+
642
+ To customize the shell command, modify the Dockerfile CMD:
643
+
644
+ ```dockerfile
645
+ CMD ["/usr/local/bin/swe-swe-server", "-shell", "bash", "-working-directory", "/workspace", "-addr", "0.0.0.0:9898"]
646
+ ```
647
+
648
+ ### YOLO Mode
649
+
650
+ YOLO mode allows agents to auto-approve actions without user confirmation. Toggle it via:
651
+ - **Status bar**: Click "Connected" text (changes to "YOLO" when active)
652
+ - **Settings panel**: Use the YOLO toggle switch
653
+
654
+ **Supported agents and their YOLO commands:**
655
+
656
+ | Agent | YOLO Command |
657
+ |-------|--------------|
658
+ | Claude | `--dangerously-skip-permissions` |
659
+ | Gemini | `--approval-mode=yolo` |
660
+ | Codex | `--yolo` |
661
+ | Goose | `GOOSE_MODE=auto` |
662
+ | Aider | `--yes-always` |
663
+ | OpenCode | Not supported (toggle hidden) |
664
+
665
+ When toggled, the agent restarts with the appropriate YOLO flag. The status bar shows "YOLO" with a border indicator when active.
666
+
667
+ ### App Preview Debugging
668
+
669
+ The App Preview (port 3000 by default) includes a debug channel that forwards browser events to agents. This lets agents debug what users see without needing visual access.
670
+
671
+ **Listening for debug messages:**
672
+ ```bash
673
+ # Inside container - receive console logs, errors, fetch/XHR requests
674
+ swe-swe-server --debug-listen
675
+ ```
676
+
677
+ Output is JSON lines:
678
+ ```json
679
+ {"t":"console","m":"log","args":["Hello!"],"ts":...}
680
+ {"t":"error","msg":"Uncaught TypeError","stack":"...","ts":...}
681
+ {"t":"fetch","url":"/api/users","method":"GET","status":200,"ms":45,"ts":...}
682
+ ```
683
+
684
+ **Querying DOM elements:**
685
+ ```bash
686
+ # Query element by CSS selector
687
+ swe-swe-server --debug-query ".error-message"
688
+ ```
689
+
690
+ Response:
691
+ ```json
692
+ {"t":"queryResult","found":true,"text":"Invalid email","visible":true}
693
+ ```
694
+
695
+ **Prerequisites:**
696
+ - User must have the Preview tab open in the split-pane UI
697
+ - App must be running on port 3000 (or `SWE_PREVIEW_TARGET_PORT`)
698
+
699
+ For detailed usage, see the `/debug-preview-page` slash command.
700
+
701
+ ### Custom Session Environment Variables
702
+
703
+ Create a `swe-swe/env` file in your project root to set custom environment variables for all sessions:
704
+
705
+ ```bash
706
+ # swe-swe/env
707
+ MY_API_KEY=abc123
708
+ DATABASE_URL=postgres://localhost/mydb
709
+ ```
710
+
711
+ These variables are loaded at session start and take precedence over defaults. Lines starting with `#` are comments. The `swe-swe/env` file is `@`-mentionable by agents.
712
+
713
+ ### Authentication
714
+
715
+ All services are protected by ForwardAuth by default. The authentication password is set via the `SWE_SWE_PASSWORD` environment variable (defaults to `changeme`).
716
+
717
+ ```bash
718
+ # Use default password
719
+ swe-swe up --project-directory ~/my-project
720
+
721
+ # Use custom password
722
+ SWE_SWE_PASSWORD='my-secure-password' swe-swe up --project-directory ~/my-project
723
+ ```
724
+
725
+ The auth service provides:
726
+ - Cookie-based session management (expires when browser closes)
727
+ - Redirect to original URL after login
728
+ - Mobile-responsive login page
729
+
730
+ ## API Reference
731
+
732
+ ### WebSocket Protocol
733
+
734
+ The swe-swe-server provides a WebSocket endpoint at `/ws/{sessionId}` for terminal communication.
735
+
736
+ **Message Format**:
737
+ ```json
738
+ {
739
+ "type": "input" | "resize",
740
+ "data": "shell input" | {"rows": 24, "cols": 80},
741
+ "sessionId": "uuid"
742
+ }
743
+ ```
744
+
745
+ See `docs/websocket-protocol.md` for detailed specification.
746
+
747
+ ## Contributing
748
+
749
+ ### Code Style
750
+
751
+ Follow Go conventions:
752
+ ```bash
753
+ make fmt # Format code
754
+ make test # Run tests
755
+ ```
756
+
757
+ ### Adding Features
758
+
759
+ 1. Modify the appropriate component (CLI in `cmd/swe-swe`, server in `cmd/swe-swe/templates/host/swe-swe-server`)
760
+ 2. Test locally: `make build && swe-swe init && swe-swe up`
761
+ 3. Commit with conventional commits: `fix:`, `feat:`, `docs:`, etc.
762
+
763
+ ## License
764
+
765
+ See LICENSE file for details.
package/bin/swe-swe.js ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "child_process";
4
+ import { createRequire } from "module";
5
+ import { existsSync, chmodSync } from "fs";
6
+ import { dirname, join } from "path";
7
+ import { fileURLToPath } from "url";
8
+
9
+ const require = createRequire(import.meta.url);
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+
12
+ const PLATFORM_MAP = {
13
+ linux: "linux",
14
+ darwin: "darwin",
15
+ win32: "win32",
16
+ };
17
+
18
+ const ARCH_MAP = {
19
+ x64: "x64",
20
+ arm64: "arm64",
21
+ };
22
+
23
+ const platform = PLATFORM_MAP[process.platform];
24
+ const arch = ARCH_MAP[process.arch];
25
+
26
+ if (!platform || !arch) {
27
+ console.error(
28
+ `Unsupported platform: ${process.platform}-${process.arch}\n` +
29
+ `swe-swe supports: linux-x64, linux-arm64, darwin-x64, darwin-arm64, win32-x64, win32-arm64`
30
+ );
31
+ process.exit(1);
32
+ }
33
+
34
+ const pkgName = `@choonkeat/swe-swe-${platform}-${arch}`;
35
+ const binName = process.platform === "win32" ? "swe-swe.exe" : "swe-swe";
36
+
37
+ let binPath;
38
+ try {
39
+ const pkgDir = dirname(require.resolve(`${pkgName}/package.json`));
40
+ binPath = join(pkgDir, "bin", binName);
41
+ } catch {
42
+ // Fallback: check for local build in npm-platforms/ (development)
43
+ const localPath = join(__dirname, "..", "npm-platforms", `${platform}-${arch}`, "bin", binName);
44
+ if (existsSync(localPath)) {
45
+ binPath = localPath;
46
+ } else {
47
+ console.error(
48
+ `Could not find package ${pkgName}.\n` +
49
+ `Make sure it is installed — this usually means your platform is supported\n` +
50
+ `but the optional dependency was not installed.\n\n` +
51
+ `Try: npm install ${pkgName}\n` +
52
+ `Or run: npx swe-swe`
53
+ );
54
+ process.exit(1);
55
+ }
56
+ }
57
+
58
+ if (!existsSync(binPath)) {
59
+ console.error(`Binary not found at ${binPath}`);
60
+ process.exit(1);
61
+ }
62
+
63
+ function run() {
64
+ const result = spawnSync(binPath, process.argv.slice(2), {
65
+ stdio: "inherit",
66
+ });
67
+
68
+ if (result.error) {
69
+ return result;
70
+ }
71
+ process.exit(result.status ?? 1);
72
+ }
73
+
74
+ let result = run();
75
+
76
+ // Handle EACCES by chmod +x and retrying
77
+ if (result.error && result.error.code === "EACCES") {
78
+ try {
79
+ chmodSync(binPath, 0o755);
80
+ } catch (e) {
81
+ console.error(`Failed to chmod +x ${binPath}: ${e.message}`);
82
+ process.exit(1);
83
+ }
84
+ result = run();
85
+ }
86
+
87
+ if (result.error) {
88
+ console.error(`Failed to start swe-swe: ${result.error.message}`);
89
+ process.exit(1);
90
+ }
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "swe-swe",
3
+ "version": "0.1.0",
4
+ "description": "Your agent: containerized with browser, terminal, and parallel sessions",
5
+ "type": "module",
6
+ "bin": {
7
+ "swe-swe": "bin/swe-swe.js"
8
+ },
9
+ "files": [
10
+ "bin"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/choonkeat/swe-swe.git"
15
+ },
16
+ "license": "MIT",
17
+ "optionalDependencies": {
18
+ "@choonkeat/swe-swe-linux-x64": "0.1.0",
19
+ "@choonkeat/swe-swe-linux-arm64": "0.1.0",
20
+ "@choonkeat/swe-swe-darwin-x64": "0.1.0",
21
+ "@choonkeat/swe-swe-darwin-arm64": "0.1.0",
22
+ "@choonkeat/swe-swe-win32-x64": "0.1.0",
23
+ "@choonkeat/swe-swe-win32-arm64": "0.1.0"
24
+ }
25
+ }