See README.md for the library API (connect, Session, etc.)
For sandboxed execution, see the sandbox/ directory and docker-compose.yml
The sandbox requires gVisor (runsc) and Docker:
sudo just sandbox-up / sudo just sandbox-down
Docker is used instead of Podman to avoid gVisor cleanup issues (stale filestore files, overlay mount races on Ctrl+C)
Security tests: bun run playground/security-tests.ts sandbox (requires running sandbox)
Troubleshooting sandbox:
Stale gVisor state after failed starts: clean up with sudo rm -rf /var/run/runsc/* then retry
docker exec does not work with gVisor's runsc runtime — use the HTTP API instead
Port already in use: run sudo docker rm -af to clean up before restarting
The seccomp BPF filter is arch-specific and must be at /etc/seccomp/{arch}/net-block.bpf inside the container (handled by Containerfile)
DNS: gVisor doesn't work with Docker/Podman's embedded DNS proxy (127.0.0.11). The docker-compose.yml has explicit dns: [8.8.8.8, 8.8.4.4] to work around this — do not remove it
If git clone fails with "could not resolve host" inside the container, check that the dns entries in docker-compose.yml are present and reachable
Before Committing
Run bun run check (auto-fixes most lint and format issues)
Run bunx tsc --noEmit to verify type checking passes
Run bun test to ensure all tests pass
Manually fix any remaining errors
Commit
Code Architecture Guidelines
Refactoring Principles
Analyze dependencies first: Before extracting code, map closures, imports, and shared state to understand what needs to move together
Work incrementally: Extract one piece at a time, verifying after each change rather than making large sweeping changes
Pause to simplify: After extraction, review for patterns like repeated parameters or duplicated logic that can be consolidated
Prefer classes over closures: For objects with state and lifecycle, classes are more idiomatic in TypeScript and easier to test
Managing Complexity
Reduce parameter counts: When passing 4+ related parameters together, bundle them into a context object
Separate pure from stateful: Extract stateless helpers as module-level functions; keep state management in classes
Make dependencies explicit: Inject dependencies through constructors rather than importing globals, enabling testability
Type Design
Use discriminated unions for results: Prefer { ok: true; value: T } | { ok: false; error: E } over exceptions for expected failures
Avoid any and loose casts: Create specific types for external/untyped data rather than using as assertions. Enforced by Biome (suspicious/noExplicitAny).
Preserve literal hints in open string unions: When a type should suggest known values but still accept arbitrary strings, write Known | (string & {}) — not Known | string. The plain-string form collapses the literal arms during union simplification and kills autocomplete; intersecting with {} produces a structurally distinct type that blocks the collapse while still accepting every string at runtime. Same trick applies with (number & {}) for numeric unions.
Export types separately: Use type imports for types that don't need runtime presence
Exhaustiveness over switch discriminants: Biome 2.4 ships no useExhaustiveSwitchCases rule, so we rely on TypeScript: noFallthroughCasesInSwitch is on, and authors should add a default arm that assigns the discriminant to a const _: never = x when they want a compile-time exhaustiveness check.
Testing
Validate mocks against interfaces: Run type checking to ensure mock objects satisfy all required interface fields
Inject dependencies for testability: Design classes to accept their dependencies so tests can provide mocks
Separate fast from slow tests: Use setup hooks appropriately - expensive fixtures in beforeAll, isolation cleanup in afterEach
Documentation
Every new feature addition should update all relevant documentation
Always explain your reasoning briefly in comments when fixing a bug