Thank you for your interest in contributing to EnvHaven!
git clone https://github.com/envhaven/envhaven.git
cd envhaven/dev
bun run setup # Install, build, and link globally
eh # Launch development TUIPress b to build, s to start container, t to test.
- Docker 20.10+
- Bun 1.1+ (for extension, CLI, and dev TUI)
- Git
envhaven/
├── .github/workflows/ # CI/CD
├── cli/ # Haven CLI (local editor + remote AI)
│ ├── src/ # TypeScript source
│ ├── scripts/ # Build scripts
│ ├── test/ # Unit tests
│ └── AGENTS.md # Architecture docs
├── dev/ # Development CLI (eh)
│ ├── scripts/ # Standalone scripts (source of truth)
│ │ ├── lib/ # Shared utilities
│ │ ├── build.ts # docker build
│ │ ├── start.ts # docker run
│ │ ├── test-*.ts # Test scripts
│ │ └── README.md # Script documentation
│ ├── src/ # TUI source (Ink/React)
│ ├── .env.example # Config template
│ ├── AGENTS.md # Dev tooling architecture
│ └── README.md # Dev CLI docs
├── docs/ # User documentation
├── extension/ # VS Code extension source
│ ├── src/ # Extension host
│ ├── webview/ # React webview
│ └── package.json
├── runtime/ # Packaged INTO Docker image
│ ├── scripts/ # s6-overlay init scripts
│ ├── overrides/ # code-server UI patches
│ └── templates/ # Config templates
├── Dockerfile
├── docker-compose.yml
└── eh # Dev CLI entry point
runtime/— Files copied into the Docker image (runs inside container)dev/— Development TUI and scripts (runs on your machine)
cd dev && bun run setup # One-time: install, build, link
eh # Launch development TUI from anywhere| Key | Action |
|---|---|
s |
Start Container |
b |
Build Image |
w |
Watch Extension (live dev) |
r |
Release (tag → Image + CLI) |
t |
Test → Image / CLI / Extension |
l |
View Logs |
x |
Shell (SSH into container) |
, |
Settings |
For custom settings (different ports, host IP, etc.):
cp dev/.env.example dev/.env.dev
# Edit dev/.env.devThe EnvHaven VS Code extension provides the sidebar panel. We use Bun for all JavaScript development.
eh # Then press 'w' for Watch ExtensionEdit files → watcher rebuilds → reload browser.
cd extension
bun install
bun run dev # Watch mode
cd webview
bun install
bun run build # Build React webview| Command | Description |
|---|---|
bun run build |
Compile TypeScript |
bun run dev |
Watch mode |
bun run build:webview |
Build webview |
bun run package |
Create .vsix |
The Haven CLI is a Bun/TypeScript project.
cd cli
bun install
bun run dev connect . # Run in dev mode
bun test # Run tests
bun run typecheck # Type check
bun run build # Build binarySee cli/AGENTS.md for architecture.
The Dockerfile is organized into sections:
- Extension build (Stage 1)
- System dependencies
- Runtime installations (Node.js, Python, Go, Rust)
- AI tool installations
- VS Code configuration
- s6-overlay services
When adding new tools, follow existing patterns and add tests.
Scripts in runtime/scripts/ run at container startup via s6-overlay:
| Script | Purpose |
|---|---|
init-extensions-run |
Install VS Code extensions |
init-vscode-settings-run |
Apply VS Code settings |
init-agents-md-run |
Generate AGENTS.md |
init-user-config-run |
Configure git, SSH, user shell |
init-zsh-config-run |
Configure zsh |
svc-sshd-run |
Run SSH daemon |
User-facing scripts in runtime/scripts/ (installed to /opt/envhaven/bin/):
| Script | Purpose |
|---|---|
envhaven-status |
Full status display (envhaven command) |
envhaven-welcome.sh |
Shell init (auto-attach to tmux) |
runtime/templates/AGENTS.md.template- Template for generated AGENTS.mdruntime/templates/settings.json- VS Code settingsruntime/templates/tmux.conf- Terminal session configuration
-
Test your changes
# Via TUI eh # Press 't' → select test type # Or run scripts directly bun dev/scripts/test-image.ts # Validate image bun dev/scripts/test-extension.ts # Test extension build bun dev/scripts/test-cli.ts --ci # Test Haven CLI
-
Update documentation if adding features
-
Keep commits focused with clear messages
-
Follow existing patterns in the codebase
All commits must follow Conventional Commits.
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
| Type | Description |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
style |
Formatting, whitespace (no code change) |
refactor |
Code change that neither fixes nor adds |
perf |
Performance improvement |
test |
Adding or correcting tests |
build |
Build system or external dependencies |
ci |
CI configuration |
chore |
Maintenance tasks |
revert |
Reverts a previous commit |
Common scopes for this project:
cli- Haven CLIextension- VS Code extensionimage- Docker image / Dockerfiledev- Development tooling (eh TUI, scripts)runtime- Runtime scripts and templates
feat(cli): add connect command
fix(extension): resolve sidebar crash on reload
docs: update contributing guide
build(image): add rust toolchain
chore: bump dependencies
- Use lowercase for type and description
- No period at the end of description
- Keep description under 72 characters
- Use imperative mood ("add" not "added")
- Breaking changes: add
!after type/scope (e.g.,feat(cli)!: change config format)
- Shell scripts: Use
shellcheckfor linting - TypeScript: Follow existing patterns
- Dockerfile: Comment each section, use multi-line for readability
- CLI TypeScript: Minimal comments, self-documenting code
When reporting issues, please include:
- Docker version (
docker --version) - Host OS and architecture
- Steps to reproduce
- Expected vs actual behavior
- Relevant logs (
docker logs envhaven)
You can develop EnvHaven while running inside a container (like EnvHaven itself, code-server, or a DevContainer). This is common for "dogfooding" the environment.
Since the development scripts (invoked by the eh TUI) need to spawn sibling containers (like envhaven-test) alongside your current container, you must configure Docker-in-Docker (DinD) via socket mounting.
Your development container must have access to the host's Docker socket.
Docker Compose:
services:
envhaven:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# ... other volumesDocker CLI:
docker run -v /var/run/docker.sock:/var/run/docker.sock ...The Docker daemon runs on the host system. When the dev scripts request to mount a volume (like /config/workspace/envhaven/extension), the daemon looks for that path on the host, not inside your container.
You must tell the scripts where the repository resides on the host filesystem.
-
Find the host path: Run this on the host machine (or inspect via
docker inspect):# Example: Inspect the container to see where /config is mounted from docker inspect envhaven --format '{{ range .Mounts }}{{ if eq .Destination "/config" }}{{ .Source }}{{ end }}{{ end }}'
-
Configure
dev/.env.dev:cp dev/.env.example dev/.env.dev
Edit
dev/.env.dev:# 1. Set the Host Path (Host's view of your repo) # If your repo is at /config/workspace/envhaven inside the container, # and /config is mounted from /home/user/data on the host: ENVHAVEN_HOST_REPO_PATH=/home/user/data/workspace/envhaven # 2. Set the Host IP # 'localhost' refers to the container itself. Use the host's IP for access. ENVHAVEN_HOST=192.168.1.50
Now you can use the eh TUI as normal (which invokes the configured scripts):
eh- Build (
b): Builds the image using the host's Docker daemon. - Start (
s): Starts a sibling container on the host. - Watch (
w): Mounts the extension from the host path into the test container.
graph TD
subgraph Host["Host Machine (Docker Daemon)"]
HostRepo["/home/user/data/workspace/envhaven"]
Socket["/var/run/docker.sock"]
end
subgraph DevContainer["Your Dev Container"]
DevRepo["/config/workspace/envhaven"]
DevScripts["Dev Scripts (via eh)"]
SocketMount["/var/run/docker.sock"]
end
subgraph TestContainer["envhaven-test (Sibling)"]
ExtMount["/extension"]
end
DevRepo -.->|Mounted from| HostRepo
Socket -.->|Mounted to| SocketMount
DevScripts -->|Talk to| SocketMount
SocketMount -->|Commands| Host
Host --"run -v /home/user/...:/extension"--> TestContainer
"Cannot connect to test container": Make sure ENVHAVEN_HOST is set to your host's IP address, not localhost. From inside a container, localhost refers to the container itself.
"Extension not mounted": Verify ENVHAVEN_HOST_REPO_PATH is correct. The path should be what the Docker daemon sees, not what you see inside your container.
Password issues: Check ENVHAVEN_PASSWORD in dev/.env.dev. Default is test.
Open an issue for questions or join the discussion in existing issues.