Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Local full-stack development against real GitHub + sandbox services.
# Copy to .env.local, fill values, then run `npm run dev:env`.

# GitHub App OAuth
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

# GitHub App installation access
GITHUB_APP_ID=
# Use escaped newlines for this single-line env file, for example:
# GITHUB_APP_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
GITHUB_APP_PRIVATE_KEY=
GITHUB_APP_INSTALLATION_ID=

# Sandbox provider. Supported values: modal, daytona.
SANDBOX_PROVIDER=modal

# Modal sandbox provider. Required when SANDBOX_PROVIDER=modal.
MODAL_TOKEN_ID=
MODAL_TOKEN_SECRET=
MODAL_WORKSPACE=
MODAL_API_SECRET=

# Daytona sandbox provider. Required when SANDBOX_PROVIDER=daytona.
DAYTONA_API_KEY=
DAYTONA_API_URL=
DAYTONA_BASE_SNAPSHOT=

# LLM keys injected by Modal
ANTHROPIC_API_KEY=

# Shared app secrets
TOKEN_ENCRYPTION_KEY=
REPO_SECRETS_ENCRYPTION_KEY=
INTERNAL_CALLBACK_SECRET=
NEXTAUTH_SECRET=

# Local service URLs
NEXTAUTH_URL=http://localhost:3000
# Modal sandboxes run remotely and cannot call back to localhost on your machine.
# For Path C, expose the local control plane with a tunnel (for example `ngrok http 8787`) and set:
# CONTROL_PLANE_URL=https://<your-ngrok-host>
# NEXT_PUBLIC_WS_URL=wss://<your-ngrok-host>
# WORKER_URL=https://<your-ngrok-host>
CONTROL_PLANE_URL=http://localhost:8787
NEXT_PUBLIC_WS_URL=ws://localhost:8787
WEB_APP_URL=http://localhost:3000
WORKER_URL=http://localhost:8787
DEPLOYMENT_NAME=local

# Access control. Prefer ALLOWED_USERS for local real-service runs.
ALLOWED_USERS=
ALLOWED_EMAIL_DOMAINS=
UNSAFE_ALLOW_ALL_USERS=false

# Optional
SCM_PROVIDER=github
LOG_LEVEL=debug
169 changes: 169 additions & 0 deletions docs/LOCAL_WEB_CONTROL_PLANE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Local Web And Control Plane

Run the web app and control plane on your machine while using real GitHub App credentials and real
sandbox services.

## Services

- Web: `http://localhost:3000`
- Control plane: `http://localhost:8787`
- Modal or Daytona: deployed sandbox backend
- GitHub: real GitHub App OAuth and installation APIs

## Setup

Complete [Step 0 in SETUP_GUIDE.md](./SETUP_GUIDE.md#step-0-bootstrap-the-repo) first.

1. Copy the root env template:

```bash
cp .env.example .env.local
```

2. Fill in `.env.local` with the real GitHub App, sandbox provider, and app secrets.

Use escaped newlines for `GITHUB_APP_PRIVATE_KEY` because `.env.local` is a single-line env file:

```bash
GITHUB_APP_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
```

3. Expose the local control plane to Modal.

Modal sandboxes run remotely, so they cannot connect back to `localhost:8787` on your machine. Start
a tunnel to the local control plane port:

```bash
ngrok http 8787
```

Update `.env.local` with the tunnel URL:

```bash
CONTROL_PLANE_URL=https://<your-ngrok-host>
NEXT_PUBLIC_WS_URL=wss://<your-ngrok-host>
WORKER_URL=https://<your-ngrok-host>
```

Keep `NEXTAUTH_URL=http://localhost:3000`; the web app still runs locally.

4. Generate package env files:

```bash
npm run dev:env
```

This writes:

- `packages/web/.env.local`
- `packages/control-plane/.dev.vars`

5. Configure Modal secrets from `.env.local`:

```bash
npm run dev:modal-secrets
```

At minimum Modal needs:

- `llm-api-keys`: `ANTHROPIC_API_KEY`
- `github-app`: `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY`, `GITHUB_APP_INSTALLATION_ID`
- `internal-api`: `MODAL_API_SECRET`, `INTERNAL_CALLBACK_SECRET`, `ALLOWED_CONTROL_PLANE_HOSTS`,
`CONTROL_PLANE_URL`

For remote Modal callbacks, `ALLOWED_CONTROL_PLANE_HOSTS` must include the tunnel host. The
`dev:modal-secrets` script derives this from `CONTROL_PLANE_URL` unless you set
`ALLOWED_CONTROL_PLANE_HOSTS` explicitly.

Skip this step when `SANDBOX_PROVIDER=daytona`; `dev:modal-secrets` is only needed for Modal.

6. Deploy Modal:

```bash
cd packages/modal-infra
uv sync --frozen --extra dev
uv run modal deploy deploy.py
```

7. Apply local D1 migrations:

```bash
npm run dev:db:local
```

8. Run the control plane:

```bash
npm run dev:control-plane
```

9. In another terminal, run the web app:

```bash
npm run dev:web
```

10. Open `http://localhost:3000`.

## GitHub App Callback

Your real GitHub App must include this callback URL for local web auth:

```text
http://localhost:3000/api/auth/callback/github
```

## Env File Management

Local full-stack development uses the root `.env.local` as the only editable env file. Do not edit
the generated service-specific files directly.

Source file:

- `.env.local` — copied from `.env.example` and edited by you

Generated files:

- `packages/web/.env.local` — consumed by Next.js
- `packages/control-plane/.dev.vars` — consumed by Wrangler

Run this after changing `.env.local`:

```bash
npm run dev:env
```

`dev:env` validates required values, writes the generated files, and stamps them with:

```text
# Generated by npm run dev:env. Do not edit directly.
```

Validation is provider-aware:

- `SANDBOX_PROVIDER=modal` requires Modal credentials and Modal callback secrets
- `SANDBOX_PROVIDER=daytona` requires Daytona credentials instead and does not require Modal tokens

The generator also derives public provider values for the web app:

- `NEXT_PUBLIC_SANDBOX_PROVIDER` is derived from `SANDBOX_PROVIDER`
- `NEXT_PUBLIC_SCM_PROVIDER` is derived from `SCM_PROVIDER`

Only set the `NEXT_PUBLIC_*` provider values in `.env.local` if you intentionally need to override
the derived value.

Modal secrets are synced separately because Modal stores them in its own secret manager:

```bash
npm run dev:modal-secrets
```

This command reads `.env.local`, validates Modal-specific values, and creates or updates the Modal
secrets used by the deployed Modal app. It exits without making changes when `SANDBOX_PROVIDER` is
not `modal`.

## Notes

- The control plane uses local Wrangler storage for D1, KV, R2, and Durable Objects.
- Modal remains remote, so sandboxes must be able to call back through the tunnel. If the ngrok URL
changes, update `.env.local`, then rerun `npm run dev:env` and `npm run dev:modal-secrets`.
41 changes: 35 additions & 6 deletions docs/SETUP_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ This is the primary setup guide for users and contributors.

It is organized by goal so you can pick the fastest path:

| Path | Best For | Time |
| ------ | -------------------------------------------------------- | ---------- |
| Path A | Run the web app locally against an existing backend | ~10-20 min |
| Path B | Contribute code locally (lint/typecheck/tests) | ~15-30 min |
| Path C | Deploy your own full stack (Cloudflare + Modal + Vercel) | ~1-3 hours |
| Path | Best For | Time |
| ------ | --------------------------------------------------------- | ---------- |
| Path A | Run the web app locally against an existing backend | ~10-20 min |
| Path B | Contribute code locally (lint/typecheck/tests) | ~15-30 min |
| Path C | Run local web + control plane against real GitHub + Modal | ~30-60 min |
| Path D | Deploy your own full stack (Cloudflare + Modal + Vercel) | ~1-3 hours |

## Important Context

Expand All @@ -30,6 +31,12 @@ Optional (needed for `modal-infra` development):
- `uv` (recommended) or `pip`
- Modal CLI (`modal`)

Optional (needed for Path C local web + control plane):

- Wrangler CLI (`npx wrangler` from repo dependencies is fine)
- `jq` for local D1 migration scripts
- A tunnel tool such as `ngrok` so remote Modal sandboxes can call back to the local control plane

Optional (needed for full deployment):

- Terraform `1.6+`
Expand All @@ -41,6 +48,12 @@ Quick check:
node -v
npm -v
git --version
python --version
uv --version
modal --version
npx wrangler --version
jq --version
ngrok --version
```

## Step 0: Bootstrap the Repo
Expand Down Expand Up @@ -183,7 +196,22 @@ pip install -e ".[dev]"
pytest tests/ -v
```

## Path C: Full Self-Hosted Deployment
## Path C: Run Local Web And Control Plane

Use this when developing or debugging behavior that requires the web app and control plane to run
together locally, while still using real GitHub App credentials and real Modal sandboxes.

This path is most useful for control-plane and session-flow changes that need real end-to-end
behavior: auth, session creation, Durable Objects, WebSockets, Modal sandbox creation, bridge
callbacks, and streamed events.

The detailed guide is here [LOCAL_WEB_CONTROL_PLANE.md](./LOCAL_WEB_CONTROL_PLANE.md)

Because Modal sandboxes run remotely, this path requires a tunnel from Modal back to the local
control plane. The detailed guide covers the required `CONTROL_PLANE_URL`, `NEXT_PUBLIC_WS_URL`, and
`WORKER_URL` settings.

## Path D: Full Self-Hosted Deployment

For full infrastructure setup, use:

Expand Down Expand Up @@ -223,5 +251,6 @@ Control plane cannot reach Modal (or Modal is not properly configured/deployed).
- Architecture and internals: [docs/HOW_IT_WORKS.md](./HOW_IT_WORKS.md)
- Full production deployment: [docs/GETTING_STARTED.md](./GETTING_STARTED.md)
- Debugging and observability: [docs/DEBUGGING_PLAYBOOK.md](./DEBUGGING_PLAYBOOK.md)
- Local web and control plane: [docs/LOCAL_WEB_CONTROL_PLANE.md](./LOCAL_WEB_CONTROL_PLANE.md)
- OpenAI model setup: [docs/OPENAI_MODELS.md](./OPENAI_MODELS.md)
- Contribution workflow: [CONTRIBUTING.md](../CONTRIBUTING.md)
14 changes: 14 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,20 @@ export default tseslint.config(
},
},

// Root maintenance scripts run in Node.js.
{
files: ["scripts/**/*.{js,mjs,cjs,ts}"],
languageOptions: {
globals: {
...globals.node,
...globals.es2022,
},
},
rules: {
"no-console": "off",
},
},

// Test files configuration
{
files: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"],
Expand Down
Loading