# Develop locally

> Monorepo bootstrap, turbo dev servers, package boundaries, targeted Vitest runs, e2e boot recipes, and release-check scripts for contributors.

- Repository: RhysSullivan/executor
- GitHub: https://github.com/RhysSullivan/executor
- Human docs: https://grok-wiki.com/public/docs/rhyssullivan-executor-564383868052
- Complete Markdown: https://grok-wiki.com/public/docs/rhyssullivan-executor-564383868052/llms-full.txt

## Source Files

- `RUNNING.md`
- `AGENTS.md`
- `package.json`
- `turbo.json`
- `e2e/AGENTS.md`
- `RELEASING.md`
- `vitest.config.ts`

---

---
title: "Develop locally"
description: "Monorepo bootstrap, turbo dev servers, package boundaries, targeted Vitest runs, e2e boot recipes, and release-check scripts for contributors."
---

The Executor monorepo is a Bun workspace (`executor-workspace`) whose root scripts orchestrate bootstrap, Turbo dev servers, Vitest across packages, and release gates. Run `bun run bootstrap` on every fresh checkout or worktree before any other command; without it, Vite dev servers fail to resolve `@executor-js/vite-plugin`.

## Prerequisites

| Requirement | Version / note |
|-------------|----------------|
| Bun | `1.3.11` (pinned in root `packageManager`) |
| Node tooling | Turbo, Vitest, oxlint, oxfmt via workspace devDependencies |
| E2E browser tests | Playwright Chromium (installed by bootstrap) |
| Cloud local dev | 1Password CLI (`op`) and `.env.op` for real WorkOS/Autumn credentials, or use e2e emulators |

<Note>
`RUNNING.md` tracks day-to-day mechanics and may lag the code. `AGENTS.md` holds stable contributor contracts; verify boot paths against `e2e/setup/*.globalsetup.ts` and `e2e/setup/*.boot.ts` when behavior drifts.
</Note>

## Bootstrap a fresh checkout

<Steps>
<Step title="Install dependencies and build artifacts">

From the repo root:

```bash
bun run bootstrap
```

Bootstrap is idempotent. It runs `bun install` (whose `prepare` hook builds `@executor-js/vite-plugin` and `@executor-js/react`) and `bunx playwright install chromium` for e2e browser scenarios. A worktree that skips bootstrap dies with `Failed to resolve entry for package '@executor-js/vite-plugin'`.

</Step>

<Step title="Verify the toolchain">

Confirm Vitest is on the path:

```bash
ls node_modules/.bin/vitest
```

Bootstrap throws if Vitest is missing after install.

</Step>
</Steps>

Upstream forks `@executor-js/emulate` and `@executor-js/mcporter` are consumed as published npm packages only. There are no `vendor/` submodules; develop those in their standalone repos and bump versions here.

## Monorepo layout

:::files
executor/
├── apps/
│   ├── cli/              # Published `executor` npm package
│   ├── local/            # Single-user local web app
│   ├── cloud/            # Executor Cloud (Workers + PGlite dev DB)
│   ├── host-selfhost/    # Self-host Docker/worker target
│   ├── host-cloudflare/  # Cloudflare self-host worker
│   ├── desktop/          # Electron app
│   └── marketing/        # Marketing site
├── packages/
│   ├── core/             # sdk, api, config, execution, storage-*, vite-plugin
│   ├── plugins/          # Protocol and credential provider plugins
│   ├── kernel/           # Execution runtimes (QuickJS, IR, dynamic worker)
│   ├── hosts/            # MCP and Cloudflare host surfaces
│   ├── react/            # Shared React UI
│   └── app/              # Shared app shell
├── examples/             # SDK and plugin wiring examples
├── e2e/                  # Cross-target scenario suite
└── tests/                # Root-level smoke tests (release bootstrap)
:::

### Package roles

| Area | Path | Responsibility |
|------|------|----------------|
| Core contracts | `packages/core/sdk` | Plugin wiring, scopes, sources, secrets, policies, test fixtures |
| HTTP API types | `packages/core/api` | Typed API groups: tools, integrations, connections, providers, executions, oauth, policies |
| Storage | `packages/core/storage-*` | Storage adapters and test support |
| Plugins | `packages/plugins/*` | Protocol (OpenAPI, GraphQL, MCP) and provider plugins |
| React UI | `packages/react` | Shared UI and effect-atom integration |
| MCP host | `packages/hosts/mcp` | MCP surface for exposing Executor |
| Runtimes | `packages/kernel/*` | Code execution substrate |
| Entry points | `apps/local`, `apps/cloud`, `apps/cli`, `apps/desktop` | Product compositions |

### Package boundaries

Workspace packages import each other through published package exports, not relative paths across package roots.

```ts
// Good
import { createExecutor } from "@executor-js/sdk";

// Bad: reaches into another package's private tree
import { createExecutor } from "../../../core/sdk/src";
```

If a needed symbol is not exported, add the smallest public export on the owning package. Keep relative imports within a single package root only.

## Dev servers

Turbo runs `dev` across workspace packages. The root `dev` script excludes desktop and cloud:

```bash
bun run dev
```

Equivalent to `turbo run dev --filter='!@executor-js/desktop' --filter='!@executor-js/cloud'`. Turbo's `dev` task depends on `@executor-js/vite-plugin#build`, is uncached, and persistent.

| Target | Command | Notes |
|--------|---------|-------|
| All (except desktop/cloud) | `bun run dev` (repo root) | Turbo parallel dev |
| Single app | `bun run dev` from `apps/<name>` | Boots only that app |
| CLI in dev mode | `bun run dev:cli` (repo root) | Sets `EXECUTOR_DEV=1`, data dir `apps/local/.executor-dev` |
| Desktop + cloud | `bun run dev:desktop` | Includes filtered-out apps |
| Self-host | `cd apps/host-selfhost && bun run dev` | Standalone Vite dev |
| Local app | `cd apps/local && bun run dev` | Portless proxy on 1355, data in `.executor-dev` |
| Cloud app | `cd apps/cloud && bun run dev` | PGlite dev DB + Vite; needs `.env.op` or emulators |

<Warning>
Cloud's default `dev` script uses 1Password (`op run --env-file=.env.op`). For a no-`.env` boot, use the e2e cloud recipe with emulated WorkOS and Autumn (see E2E boot recipes below).
</Warning>

### Kill leaked dev servers

When a session dies without teardown, orphaned Vite or dev-db processes can squat e2e ports:

```bash
bun run reap          # Kill orphans (removed worktrees) and list live ones
bun run reap --all    # Also kill live-checkout servers
bun run reap --dry-run
```

## Unit and package tests

### Test runner rules

- Use **Effect Vitest**: import `describe`, `it`, `expect` from `@effect/vitest`, not raw `vitest` (except config/tooling files).
- **Never** run `bun test`.
- Run the narrowest useful verification for scoped work; use full gates before merge.

### Targeted Vitest runs

Each package owns a `vitest.config.ts` with `include: ["src/**/*.test.ts"]` (or `tests/**/*.test.ts` at root). Run from the package directory:

```bash
cd packages/core/sdk
vitest run src/some-area.test.ts

cd packages/plugins/openapi
vitest run

cd apps/cli
bun run test
```

From the repo root, Turbo fans out package tests (e2e excluded):

```bash
bun run test
```

Root-only smoke:

```bash
bun run test:release:bootstrap
```

### Merge-ready verification gates

```bash
bun run format:check
bun run lint
bun run typecheck
bun run test
```

`bun run ci` runs lint, typecheck, and test together. `typecheck:slow` runs full `tsc` where packages define it.

## E2E boot recipes

The e2e suite boots real dev stacks (or attaches to running ones), drives the product through public surfaces only (API, web UI, MCP, CLI), and writes artifacts under `e2e/runs/<target>/<scenario-slug>/`.

### Port allocation

Each checkout hashes its repo root into a preferred port block (`42000`–`45999`). Globalsetup calls `claimPorts`, atomically locks the block, and walks forward if squatted. Concurrent worktrees do not collide.

```bash
cd e2e
bun run ports
```

<ParamField body="E2E_*_PORT" type="number">
Pin a specific port (no probing). Example: `E2E_SELFHOST_PORT`, `E2E_CLOUD_PORT`.
</ParamField>

<ParamField body="E2E_<TARGET>_URL" type="string">
Attach to an already-running instance instead of booting. Example: `E2E_CLOUD_URL=http://127.0.0.1:<port>`.
</ParamField>

### Self-host boot contract

Canonical recipe: `e2e/setup/selfhost.boot.ts` (shared by vitest globalsetup and `bun run cli up selfhost`).

| Variable | Purpose |
|----------|---------|
| `EXECUTOR_DATA_DIR` | Fresh data dir per suite run (wiped by default) |
| `EXECUTOR_BOOTSTRAP_ADMIN_EMAIL` | Bootstrap admin email |
| `EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD` | Bootstrap admin password |
| `EXECUTOR_WEB_BASE_URL` | Advertised URL for cookies and redirects |
| `EXECUTOR_ALLOW_LOCAL_NETWORK` | `true` for hermetic e2e (allows localhost MCP/OAuth test servers) |
| `BETTER_AUTH_SECRET` | Auth secret for self-host |

### Cloud boot contract

Canonical recipe: `e2e/setup/cloud.boot.ts`. Boots emulated WorkOS + Autumn + the app's real dev stack with PGlite. No `.env` required. Optional motel OTLP store captures exported spans for telemetry scenarios.

### Run scenarios

```bash
cd e2e
bun run test                    # cloud + selfhost (default)
bun run test:cloud              # cloud only
bun run test:selfhost           # selfhost only
bun run test:selfhost-docker    # production Docker artifact (release gate)
bun run test:cloudflare         # Cloudflare worker host
```

Attach while iterating:

```bash
E2E_CLOUD_URL=http://127.0.0.1:<port> ../node_modules/.bin/vitest run --project cloud <file>
E2E_SELFHOST_URL=http://localhost:<port> ../node_modules/.bin/vitest run --project selfhost <file>
```

### Interactive dev CLI

Same boot primitives as scenarios, as commands:

```bash
cd e2e
bun run cli up selfhost --share   # boot, tailnet-reachable, stays up
bun run cli up cloud --share      # emulated WorkOS+Autumn
bun run cli status
bun run cli identity selfhost
bun run cli api selfhost tools.list
bun run cli mcp selfhost call execute '{"code":"return 1+1;"}'
bun run cli down selfhost
```

Instances persist until `down`. State files in `e2e/.dev/` mark deliberate long-lived instances.

### View and share run artifacts

```bash
cd e2e
bun run serve
```

Serves the scenario × target matrix over HTTP (prefers port 8901, walks forward if busy). Individual runs: `#/<target>/<slug>`. Browser scenarios produce `session.mp4`, `trace.zip`, and step screenshots.

```bash
bun e2e/scripts/pr-media.ts e2e/runs/<target>/<slug>
```

Converts a recording to GIF and uploads to the `e2e-media` branch for PR-ready markdown.

### Writing scenarios

See `e2e/AGENTS.md`. Key rules:

- Black-box only: no app internals, no DB pokes.
- Import `expect` from `@effect/vitest`; use Effect generators in scenario bodies.
- Selfhost: prefix resources with scenario slug (shared bootstrap admin).
- Cloud: `newIdentity()` gives fresh user+org isolation.
- Clean up with `Effect.ensuring`, not trailing statements.

## Release-check scripts

Releases use Changesets. Local dry runs validate publish payloads without uploading.

| Script | Purpose |
|--------|---------|
| `bun run release:check` | CLI typecheck + `test:release:bootstrap` + `release:publish:dry-run` |
| `bun run release:publish:dry-run` | Full CLI release build to `apps/cli/dist` |
| `bun run release:publish:packages:dry-run` | Pack `@executor-js/*` library packages |
| `bun run release:publish:packages` | Publish library packages to npm |
| `bun run release:beta:start` | Enter Changesets prerelease mode (`beta` dist-tag) |
| `bun run release:beta:stop` | Exit prerelease mode |
| `bun run changeset` | Add a changeset in your PR |

`release:check` is what CI runs before CLI publish. Self-host Docker validation:

```bash
docker build -f apps/host-selfhost/Dockerfile -t executor-selfhost:local .
```

Changeset bodies are the changelog entries. Write user-visible summaries in `.changeset/*.md`; `changeset version` compiles them into per-package `CHANGELOG.md`.

## Common gotchas

<AccordionGroup>
<Accordion title="Stale Vite dep-optimizer cache after rebase">

Symptom: dev servers show pre-rebase behavior while unit tests pass. Kill the server, clear `node_modules/.vite` and `.tanstack`-adjacent caches, reboot.

</Accordion>

<Accordion title="Port squatting across worktrees">

If boot attaches to another checkout's server with baffling auth errors, an old dev server leaked. Run `bun run reap`, check `e2e/.dev/<target>.json` for deliberate instances, then `bun run cli down <target>`.

</Accordion>

<Accordion title="Cloud login over plain HTTP from non-localhost">

Cloud sets `secure: true` auth cookies. Login breaks over plain HTTP from non-localhost origins ("Invalid login state"). The e2e `cli up cloud --share` path fronts both app and WorkOS emulator with `tailscale serve` HTTPS.

</Accordion>

<Accordion title="bun.lock conflicts on rebase">

Take either side, re-run `bun install`. Never hand-merge the lockfile.

</Accordion>

<Accordion title="Scratch scripts outside the repo">

Do not write probe scripts to `/tmp`; they cannot resolve workspace packages. Use `scratch/` (gitignored) under the repo root.

</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Bootstrap a development checkout and verify the background service starts with expected ports and data directories.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Daemon port conflicts, stale Vite caches, missing bootstrap builds, and recovery commands.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`EXECUTOR_DATA_DIR`, `EXECUTOR_SCOPE_DIR`, bootstrap admin env vars, ports, and client overrides.
</Card>
<Card title="Plugin catalog" href="/plugin-catalog">
Published protocol and provider plugins and how `examples/all-plugins` wires them.
</Card>
<Card title="SDK quickstart example" href="/sdk-quickstart-example">
Walkthrough of `examples/docs-sdk-quickstart` for in-process SDK development.
</Card>
</CardGroup>
