# How Feynman Loads Environment Variables from .env Files: Native CLI Implementation

> Discover how Feynman loads environment variables from .env files with its native CLI implementation. Learn about the custom parser and synchronous file reading for seamless variable injection.

- Repository: [Advait Paliwal/feynman](https://github.com/advaitpaliwal/feynman)
- Tags: internals
- Published: 2026-09-08

---

**Feynman loads environment variables from .env files using a custom-built parser in [`src/cli.ts`](https://github.com/advaitpaliwal/feynman/blob/main/src/cli.ts) that synchronously reads the file, parses key-value pairs while handling quotes and comments, and injects them into `process.env` before any application modules initialize.**

Unlike many Node.js CLIs that rely on the external `dotenv` package, the `advaitpaliwal/feynman` repository implements its own lightweight loader directly in the entry point. This native approach eliminates a production dependency—documented in the [[`CHANGELOG.md`](https://github.com/advaitpaliwal/feynman/blob/main/CHANGELOG.md)](https://github.com/advaitpaliwal/feynman/blob/main/CHANGELOG.md)—while ensuring API keys and configuration values are available immediately when the `feynman` command executes.

## The Native .env Loader in src/cli.ts

The environment loading logic lives entirely within the CLI entry point at [`src/cli.ts`](https://github.com/advaitpaliwal/feynman/blob/main/src/cli.ts). When you invoke the `feynman` command, the loader executes before any other imports, guaranteeing that `process.env` contains your local configuration before Pi sub-agents or workbench components start.

### File Detection and Synchronous Reading

The loader first resolves the target path using the current working directory:

```typescript
import { readFileSync, existsSync } from 'node:fs';
import { resolve } from 'node:path';

const envPath = resolve(process.cwd(), '.env');
if (existsSync(envPath)) {
  const raw = readFileSync(envPath, 'utf8');
  // ... parsing logic
}

```

This **synchronous** approach ensures that environment variables are fully populated before any asynchronous module loading begins.

### Parsing Logic and Value Sanitization

For each line in the raw file content, Feynman applies a strict parsing protocol:

- **Blank lines** and lines beginning with `#` are ignored
- Each valid line is split on the **first** `=` character only (`indexOf('=')`)
- Both keys and values are trimmed of surrounding whitespace
- Surrounding quotes—whether `"`, `'`, or `` ` ``—are stripped from values
- Escaped sequences such as `\n` are processed during parsing

This implementation mirrors standard dotenv behavior but handles the processing natively without external libraries.

### Conditional Injection into process.env

Crucially, Feynman respects pre-existing environment variables:

```typescript
if (process.env[key] === undefined) {
  process.env[key] = value;
}

```

Variables are assigned **only if they are not already defined**, ensuring that system-level exports or command-line overrides take precedence over `.env` file values.

## Why Feynman Dropped the dotenv Dependency

According to the repository's release notes, the external `dotenv` package was removed in recent releases. The native implementation provides three advantages:

- **Zero dependency overhead** for configuration loading
- **Faster cold-start times** by avoiding an extra require() resolution
- **Predictable behavior** baked directly into the CLI bootstrap sequence

## Complete Loader Implementation

Here is the simplified parsing logic as implemented in [`src/cli.ts`](https://github.com/advaitpaliwal/feynman/blob/main/src/cli.ts):

```typescript
import { readFileSync, existsSync } from 'node:fs';
import { resolve } from 'node:path';

const envPath = resolve(process.cwd(), '.env');
if (existsSync(envPath)) {
  const raw = readFileSync(envPath, 'utf8');

  raw.split('\n').forEach(line => {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith('#')) return;

    const idx = trimmed.indexOf('=');
    if (idx === -1) return;

    const key = trimmed.slice(0, idx).trim();
    let value = trimmed.slice(idx + 1).trim();

    // Strip surrounding quotes if present
    if (
      (value.startsWith('"') && value.endsWith('"')) ||
      (value.startsWith("'") && value.endsWith("'")) ||
      (value.startsWith('`') && value.endsWith('`'))
    ) {
      value = value.slice(1, -1);
    }

    if (process.env[key] === undefined) {
      process.env[key] = value;
    }
  });
}

```

## Key Files in the Execution Chain

Understanding the full execution flow requires examining three specific files:

- **[`bin/feynman.js`](https://github.com/advaitpaliwal/feynman/blob/main/bin/feynman.js)** – The executable wrapper that invokes the CLI and triggers the loader
- **[`src/cli.ts`](https://github.com/advaitpaliwal/feynman/blob/main/src/cli.ts)** – Contains the native `.env` parsing logic that runs before any other code
- **[`src/workbench/settings-store.ts`](https://github.com/advaitpaliwal/feynman/blob/main/src/workbench/settings-store.ts)** – Example consumer module that reads the injected variables after initialization

If the `.env` file is missing from `process.cwd()`, the loader silently skips initialization without throwing errors or warnings.

## Summary

- Feynman implements a **native .env parser** in [`src/cli.ts`](https://github.com/advaitpaliwal/feynman/blob/main/src/cli.ts) rather than using the `dotenv` npm package
- The loader runs **synchronously** before module imports using `fs.readFileSync` and `process.cwd()`
- Values are parsed by splitting on the first `=`, trimming whitespace, and stripping surrounding quotes
- Variables are injected into `process.env` **only if undefined**, preserving existing environment values
- The implementation supports comments (`#`), blank lines, and escaped sequences while maintaining zero external dependencies

## Frequently Asked Questions

### Does Feynman require a .env file to run?

No. The loader checks for file existence using `existsSync` and silently skips parsing if no `.env` file is present in the current working directory. The application will rely entirely on system environment variables or use default configurations where available.

### What happens if a variable is already defined in the environment?

Feynman preserves pre-existing environment variables. During the injection phase, the code explicitly checks `if (process.env[key] === undefined)` before assignment, ensuring that shell-exported variables or Docker-injected secrets take precedence over values defined in the `.env` file.

### Where is the .env loader implemented in the Feynman source code?

The loading logic is implemented directly in [`src/cli.ts`](https://github.com/advaitpaliwal/feynman/blob/main/src/cli.ts), which serves as the main entry point for the CLI. This file is invoked by [`bin/feynman.js`](https://github.com/advaitpaliwal/feynman/blob/main/bin/feynman.js), and the environment setup occurs at the top of the module before any Pi sub-agents or workbench components are imported.

### Does Feynman support multiline values in .env files?

The current implementation splits the file content on newline characters (`\n`), which means complex multiline values are not supported in the same way as the standard `dotenv` package. For configuration requiring multiline strings, users should rely on system environment variables or external configuration files handled after the CLI initializes.