# Commands Reference (/docs/developer-guide/commands)

## Development [#development]

```bash
# Run all dev servers (turbo)
pnpm dev

# Run only the web app
pnpm --filter @workspace/web dev

# Run only the worker
pnpm --filter @workspace/worker dev
```

## Building [#building]

```bash
# Build all packages
pnpm build

# Build only the web app
pnpm --filter @workspace/web build

# Type check every package
pnpm turbo check-types
```

## Testing [#testing]

```bash
# Run the unit tests across every package
pnpm test

# Run the tests for scripts/, which sits outside the workspace
pnpm test:scripts
```

Storybook stories double as browser tests. They run in Vitest's browser mode, so
they need a browser installed before the first run:

```bash
pnpm --filter @workspace/web exec playwright install chromium
pnpm --filter @workspace/web test:storybook
```

The analytics stats are checked against a real Postgres. These tests migrate the
database in `TEST_DATABASE_URL` and write to it, so they refuse to run unless it
points at a local database whose name ends in `_test`:

```bash
createdb elmo_test
TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5432/elmo_test \
  pnpm --filter @workspace/web test:integration
```

### End-to-End Tests [#end-to-end-tests]

`.github/workflows/e2e.yaml` is the source of truth for E2E orchestration — it
stands up one Dockerized container per deployment mode, seeds them, and runs the
suites in order. Replay the whole thing locally with
[act](https://github.com/nektos/act):

```bash
pnpm test:e2e-local
```

The scripts below each run one mode's suite against a stack that workflow has
already brought up, which is what you want when iterating on a single spec:

```bash
# Playwright specs
pnpm test:e2e
pnpm test:e2e:cloud
pnpm test:e2e:whitelabel
pnpm test:e2e:demo

# Bruno API suites
pnpm test:api
pnpm test:api:cloud
pnpm test:api:demo
```

## Code Quality [#code-quality]

```bash
# Check formatting, lint and import order — the same check CI runs
pnpm lint

# Fix everything Biome can fix on its own
pnpm lint:fix

# Format code with Biome
pnpm format

# Find unused exports and dependencies
pnpm knip

# Lint the hand-written OpenAPI document
pnpm lint:spec

# Check every dependency's license against the MIT-compatible allow-list
pnpm license-check
```

Always run `pnpm format` before committing to ensure consistent code style across the codebase.

## Database [#database]

`drizzle.config.ts` and the migrations live in `packages/lib`, so Drizzle runs
against that package:

```bash
# Generate a new migration after schema changes
pnpm --filter @workspace/lib exec drizzle-kit generate

# Run pending migrations
pnpm --filter @workspace/lib exec drizzle-kit migrate
```

Apply schema changes with `drizzle-kit migrate` only. See
[Architecture](/docs/developer-guide/architecture#database) for why `drizzle-kit
push` is unsafe against a deployment.

## Component Stories [#component-stories]

```bash
# Start the Storybook dev server on port 6006
pnpm --filter @workspace/web storybook

# Build the static Storybook
pnpm --filter @workspace/web storybook:build
```

## CLI Development [#cli-development]

```bash
# Run CLI directly from source
pnpm elmo <command>

# Build and link CLI globally for testing
pnpm cli:link

# Unlink global CLI
pnpm cli:unlink
```

## Docker [#docker]

```bash
# Build Docker images locally
elmo compose build

# Build without cache
elmo compose build --no-cache

# Build only web or worker
elmo compose build web
elmo compose build worker

# Run other docker compose commands
elmo compose up -d
elmo compose ps
elmo compose logs -f web
elmo compose down
```

## Versioning [#versioning]

```bash
# Create a changeset (do this before every PR)
pnpm changeset

# Apply changesets and bump versions (maintainers only)
pnpm version-packages
```

## Turborepo [#turborepo]

The monorepo uses Turborepo for parallel task execution and caching. Most commands are defined in `turbo.json` and can be run from the repo root via `pnpm <script>`.

To run a command for a specific package:

```bash
pnpm --filter @workspace/<package-name> <command>
```

## Query fan-out access [#query-fan-out-access]

Query fan-out is an organization add-on, disabled by default in every deployment
mode, including WorkOS/local deployments. Platform admins follow the same flag;
there is no admin bypass. Everyone in the organization shares its access.

After applying the database migrations through your normal deployment process,
run this command from the repository root. It loads `.env`; an existing
`DATABASE_URL` environment variable takes precedence. Check that it names the
intended database before changing customer access.

```bash
# Enable for an agency that has purchased the add-on, or your development org
pnpm set-query-fanout --org default --enabled true

# Disable to see the clinic experience, or revoke the add-on
pnpm set-query-fanout --org default --enabled false
```

`--org` accepts an organization ID or URL slug. The command creates its settings
row if needed and preserves billing settings. It changes only
`organization_settings.query_fanout_enabled`; it does not charge the customer.
Pricing and checkout must be agreed before launching paid access.

Reload the app after a toggle to refresh navigation. Server checks use the current
flag on each request. When disabled, the fan-out page and data endpoints are
blocked, Web Queries are hidden in prompt history and responses, and raw provider
payloads are withheld because they can contain embedded searches. Answer text,
mentions, and citations remain available. Collection and stored history continue,
so enabling the add-on also makes existing query history available.

## LLM settings access [#llm-settings-access]

The LLMs settings page is disabled by default for every organization, including
organizations created on a new Google sign-in. Platform admins follow the same
flag. Clients cannot enable it themselves.

From the repository root, use the backend command to grant access only to an
organization that should manage its platform choices:

```bash
pnpm set-llm-settings --org default --enabled true
pnpm set-llm-settings --org default --enabled false
```

Like `set-query-fanout`, this accepts an organization ID or URL slug and loads
`.env`. It updates only `organization_settings.llm_settings_enabled`, preserving
query fan-out access and billing settings. Reload the app after toggling.

When disabled, the sidebar link is hidden, direct page access is blocked, and
model-picker reads and saves are rejected server-side. Onboarding skips platform
selection and uses the configured defaults; manually supplied platform choices
at brand creation are also rejected. Existing tracking keeps running unchanged.
