← Back to agents

AGENTS.md from assagman/opencode-dotenv

1 starsLast commit Jan 15, 2026

Agent Guide: opencode-dotenv

This document provides essential information for AI agents working on the opencode-dotenv project.

Project Overview

OpenCode plugin that loads `.env` files at startup. This is a Bun runtime plugin that parses environment files and makes variables available to the OpenCode environment.

**Runtime**: Bun (required - not compatible with Node.js) **Type**: OpenCode plugin **Language**: TypeScript (ESM)

Critical Architectural Limitation

This plugin **cannot** set environment variables for use in OpenCode's config file. OpenCode parses `opencode.jsonc` (including `{env:VAR}` resolution) **before** loading plugins. Therefore:

  • Variables set by this plugin are available to chat sessions and tool executions
  • Variables set by this plugin are **NOT** available to `{env:VAR}` references in `opencode.jsonc`
  • For config variables (API keys, etc.), users must set them in shell profile before starting OpenCode

See `docs/ARCHITECTURE.md` for detailed startup sequence diagrams.

Essential Commands

Build & Install

```bash

Install dependencies

bun install

or

make install

Build project (compiles src/index.ts to dist/)

bun run build

or

make build

Clean build artifacts and dependencies

make clean ```

Testing

```bash

Run tests

make test

or

bun test

Note: Uses bun:test framework

Test files are located alongside source files (src/index.test.ts)

```

Benchmarking

```bash

Run performance benchmarks

make bench

or

bun run bench/init.bench.ts ```

Code Quality

```bash

Lint code (placeholder - not configured)

make lint

Format code (placeholder - not configured)

make fmt ```

Publishing

```bash

Build and publish to npm

npm publish

or

make publish

Dry-run publish to preview

make publish-dry ```

Manual Testing (Bun-specific)

```bash

Run with Bun directly

bun run src/index.ts ```

Code Organization

``` opencode-dotenv/ ├── src/ │ ├── index.ts # Entry point, re-exports │ ├── plugin.ts # Main plugin implementation │ ├── test-utils.ts # Internal test utilities │ └── profiler/ │ ├── index.ts # Profiler exports │ └── profiler.ts # Performance profiler ├── bench/ │ ├── utils.ts # Benchmark utilities │ └── init.bench.ts # Initialization benchmarks ├── docs/ │ └── ARCHITECTURE.md # Plugin architecture and startup sequence ├── dist/ # Built output (generated, not in git) ├── package.json ├── Makefile └── README.md ```

**Key Points**:

  • Main plugin entry at `src/index.ts`
  • Profiler module at `src/profiler/` for performance tracking
  • Benchmarks in `bench/` directory
  • Tests co-located with source code
  • Only `dist/` directory is published to npm (see `package.json` `files` field)
  • Source files are excluded from npm package via `.npmignore`
  • Architecture docs in `docs/` directory

Naming Conventions & Style

Constants

Upper case with underscores: ```typescript const PLUGIN_NAME = "opencode-dotenv" const CONFIG_NAME = "dotenv.jsonc" const LOG_FILE = `${homedir()}/.local/share/opencode/dotenv.log` const LOAD_GUARD = "__opencodeDotenvLoaded" ```

Interfaces & Types

PascalCase: ```typescript interface DotEnvConfig { files: string[] load_cwd_env?: boolean logging?: { enabled?: boolean } } ```

Functions

camelCase: ```typescript function parseDotenv(content: unknown): Record<string, string> function expandPath(path: string): string async function loadConfig(): Promise<DotEnvConfig> ```

Exports

  • Named exports for utilities: `export { parseDotenv, globalProfiler }`
  • Default export for main plugin: `export default DotEnvPlugin`
  • Named export for plugin type: `export const DotEnvPlugin: Plugin`

Import Style

Named imports from packages: ```typescript import type { Plugin } from "@opencode-ai/plugin" import { homedir } from "node:os" import { parse } from "jsonc-parser" import { globalProfiler } from "./profiler" ```

Testing Approach

Framework

  • Uses `bun:test` (Bun's built-in test framework)
  • Test files use `*.test.ts` naming pattern
  • Tests located in same directory as source code

Test Patterns

Comprehensive coverage including:

  • Edge cases (empty lines, comments, mixed whitespace)
  • Quote handling (single, double, unquoted values)
  • Error handling (non-string content, type safety)
  • Complex scenarios (multiple variables, special characters)

Example test structure: ```typescript import { test, expect } from "bun:test" import { parseDotenv } from "../src/index"

test("descriptive test name", () => { const result = parseDotenv("KEY=value") expect(result.KEY).toBe("value") }) ```

Running Tests

```bash make test

or directly

bun test ```

Key Code Patterns

Async/Await Pattern

All file operations use async/await with Bun API: ```typescript const file = Bun.file(configPath) const content = await file.text() ```

Non-Blocking Logging

Fire-and-forget async logging (never blocks): ```typescript function log(message: string): void { if (!loggingEnabled || isTestEnv) return

const timestamp = new Date().toISOString() const line = `[${timestamp}] ${message}\n`

// Fire and forget - never block mkdir(LOG_DIR, { recursive: true }) .then(() => appendFile(LOG_FILE, line)) .catch(() => { // Ignore errors - never block }) } ```

Configuration Loading

Multi-path search with first-match strategy: 1. Local config: `./dotenv.jsonc` 2. Global config: `~/.config/opencode/dotenv.jsonc`

First found file is used; no merging.

Path Expansion

Tilde (`~`) expansion for home directory: ```typescript function expandPath(path: string): string { return path.replace(/^~/, homedir()) } ```

Load Guard Pattern

Prevents double plugin initialization: ```typescript if ((globalThis as any)[LOAD_GUARD]) { return {} } (globalThis as any)[LOAD_GUARD] = true ```

Profiler Integration

Performance tracking with the profiler module: ```typescript import { globalProfiler } from "./profiler"

// Start timing globalProfiler.initStart()

// Record file load metrics globalProfiler.recordFileLoad(filePath, duration, varCount, success)

// Complete initialization globalProfiler.initComplete("ready")

// Export performance report const report = globalProfiler.export() ```

Performance Optimizations

The plugin is optimized for fast startup (<1ms typical):

Sequential Config Loading

  • Config files are checked in order (local first, then global)
  • First successful config is used; no merging
  • Fast failure on missing/invalid configs

Sequential File Loading

  • .env files are loaded sequentially to maintain order
  • Later files override earlier ones (important for variable precedence)
  • Performance metrics recorded for each file

Non-Blocking Logging

  • Logging is **disabled by default** for maximum performance
  • When enabled, uses async fire-and-forget writes
  • Never blocks the main plugin execution
  • Skipped entirely in test environments

Load Guard

  • Early return if plugin already loaded (`globalThis.__opencodeDotenvLoaded`)
  • Prevents redundant initialization (subsequent calls: ~0.01ms)
  • Essential for performance when plugin is called multiple times

Profiler

  • Built-in profiler for performance tracking
  • Records config load times, file load times, and total initialization
  • Can be exported for analysis via `getPerformanceReport()`

**To run benchmarks:** ```bash make bench ```

Important Gotchas

Runtime Requirement

  • **Must use Bun runtime** - this plugin uses Bun-specific APIs (`Bun.file()`)
  • Not compatible with Node.js

Configuration Format

  • Uses JSONC format (JSON with Comments)
  • Supports trailing commas and inline comments
  • Parsed with `jsonc-parser` library
  • **Config file name**: `dotenv.jsonc`

Logging Behavior

  • Defaults to **disabled** for performance
  • Only enabled when explicitly set to `true` in config (`logging.enabled = true`)
  • Uses async fire-and-forget writes (never blocks)
  • Skipped in test environments (`NODE_ENV=test` or `BUN_TEST` set)
  • Writes to `~/.local/share/opencode/dotenv.log`
  • Silent failures in logging - won't crash if log file is unwritable

Makefile Targets

The following targets are **stubs/placeholders**:

  • `make lint` - only echoes "Linting code..."
  • `make fmt` - only echoes "Formatting code..."

Functional targets:

  • `make test` - runs `bun test`
  • `make bench` - runs `bun run bench/init.bench.ts`

Type Assertions

Code uses type assertions in some places: ```typescript const config = parse(content, [], { allowTrailingComma: true }) as DotEnvConfig (globalThis as any)[LOAD_GUARD] = true ```

Config File Search Order

Config files are searched in this order; first found wins: 1. `./dotenv.jsonc` (project-specific) 2. `~/.config/opencode/dotenv.jsonc` (global)

No merging between config files.

Environment Variable Load Order

Variables are loaded in this order; later values override earlier ones: 1. Files from `config.files` array (in specified order) 2. `.env` from current working directory (if `load_cwd_env !== false`)

Process.cwd() Usage

The plugin uses `process.cwd()` to determine the current working directory, which is where OpenCode was launched from.

Dependencies

Production

  • `jsonc-parser` - Parses JSONC configuration files with comments support

Peer Dependencies

  • `@opencode-ai/plugin` - OpenCode plugin type definitions

Dev Dependencies

None specified (uses Bun's built-in test runner)

Publishing

The package is published to npm with:

  • Main entry: `./dist/index.js`
  • Includes only: `dist/`, `README.md`, `LICENSE`
  • Prepublish hook: automatically builds before publishing (`prepublishOnly: "bun run build"`)

Development Workflow

1. Make changes to `src/index.ts` 2. Add/update tests in `src/index.test.ts` 3. Run `bun test` to verify 4. Run `make bench` to check performance 5. Run `make build` to compile 6. Test locally with Bun 7. Publish with `make publish`

Debugging

View plugin activity logs: ```bash tail -f ~/.local/share/opencode/dotenv.log ```

Logs include:

  • Plugin startup/shutdown
  • Config file loading
  • File loading attempts
  • Variable counts
  • Errors (with details)

Get performance report programmatically: ```typescript import { getPerformanceReport } from "opencode-dotenv"

const report = getPerformanceReport() console.log(JSON.stringify(report, null, 2)) ```