This document provides essential information for AI agents working on the opencode-dotenv project.
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)
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 inopencode.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.
# 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# Run tests
make test
# or
bun test
# Note: Uses bun:test framework
# Test files are located alongside source files (src/index.test.ts)# Run performance benchmarks
make bench
# or
bun run bench/init.bench.ts# Lint code (placeholder - not configured)
make lint
# Format code (placeholder - not configured)
make fmt# Build and publish to npm
npm publish
# or
make publish
# Dry-run publish to preview
make publish-dry# Run with Bun directly
bun run src/index.tsopencode-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 (seepackage.jsonfilesfield) - Source files are excluded from npm package via
.npmignore - Architecture docs in
docs/directory
Upper case with underscores:
const PLUGIN_NAME = "opencode-dotenv"
const CONFIG_NAME = "dotenv.jsonc"
const LOG_FILE = `${homedir()}/.local/share/opencode/dotenv.log`
const LOAD_GUARD = "__opencodeDotenvLoaded"PascalCase:
interface DotEnvConfig {
files: string[]
load_cwd_env?: boolean
logging?: {
enabled?: boolean
}
}camelCase:
function parseDotenv(content: unknown): Record<string, string>
function expandPath(path: string): string
async function loadConfig(): Promise<DotEnvConfig>- Named exports for utilities:
export { parseDotenv, globalProfiler } - Default export for main plugin:
export default DotEnvPlugin - Named export for plugin type:
export const DotEnvPlugin: Plugin
Named imports from packages:
import type { Plugin } from "@opencode-ai/plugin"
import { homedir } from "node:os"
import { parse } from "jsonc-parser"
import { globalProfiler } from "./profiler"- Uses
bun:test(Bun's built-in test framework) - Test files use
*.test.tsnaming pattern - Tests located in same directory as source code
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:
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")
})make test
# or directly
bun testAll file operations use async/await with Bun API:
const file = Bun.file(configPath)
const content = await file.text()Fire-and-forget async logging (never blocks):
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
})
}Multi-path search with first-match strategy:
- Local config:
./dotenv.jsonc - Global config:
~/.config/opencode/dotenv.jsonc
First found file is used; no merging.
Tilde (~) expansion for home directory:
function expandPath(path: string): string {
return path.replace(/^~/, homedir())
}Prevents double plugin initialization:
if ((globalThis as any)[LOAD_GUARD]) {
return {}
}
(globalThis as any)[LOAD_GUARD] = truePerformance tracking with the profiler module:
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()The plugin is optimized for fast startup (<1ms typical):
- Config files are checked in order (local first, then global)
- First successful config is used; no merging
- Fast failure on missing/invalid configs
- .env files are loaded sequentially to maintain order
- Later files override earlier ones (important for variable precedence)
- Performance metrics recorded for each file
- 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
- Early return if plugin already loaded (
globalThis.__opencodeDotenvLoaded) - Prevents redundant initialization (subsequent calls: ~0.01ms)
- Essential for performance when plugin is called multiple times
- 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:
make bench- Must use Bun runtime - this plugin uses Bun-specific APIs (
Bun.file()) - Not compatible with Node.js
- Uses JSONC format (JSON with Comments)
- Supports trailing commas and inline comments
- Parsed with
jsonc-parserlibrary - Config file name:
dotenv.jsonc
- Defaults to disabled for performance
- Only enabled when explicitly set to
truein config (logging.enabled = true) - Uses async fire-and-forget writes (never blocks)
- Skipped in test environments (
NODE_ENV=testorBUN_TESTset) - Writes to
~/.local/share/opencode/dotenv.log - Silent failures in logging - won't crash if log file is unwritable
The following targets are stubs/placeholders:
make lint- only echoes "Linting code..."make fmt- only echoes "Formatting code..."
Functional targets:
make test- runsbun testmake bench- runsbun run bench/init.bench.ts
Code uses type assertions in some places:
const config = parse(content, [], { allowTrailingComma: true }) as DotEnvConfig
(globalThis as any)[LOAD_GUARD] = trueConfig files are searched in this order; first found wins:
./dotenv.jsonc(project-specific)~/.config/opencode/dotenv.jsonc(global)
No merging between config files.
Variables are loaded in this order; later values override earlier ones:
- Files from
config.filesarray (in specified order) .envfrom current working directory (ifload_cwd_env !== false)
The plugin uses process.cwd() to determine the current working directory, which is where OpenCode was launched from.
jsonc-parser- Parses JSONC configuration files with comments support
@opencode-ai/plugin- OpenCode plugin type definitions
None specified (uses Bun's built-in test runner)
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")
- Make changes to
src/index.ts - Add/update tests in
src/index.test.ts - Run
bun testto verify - Run
make benchto check performance - Run
make buildto compile - Test locally with Bun
- Publish with
make publish
View plugin activity logs:
tail -f ~/.local/share/opencode/dotenv.logLogs include:
- Plugin startup/shutdown
- Config file loading
- File loading attempts
- Variable counts
- Errors (with details)
Get performance report programmatically:
import { getPerformanceReport } from "opencode-dotenv"
const report = getPerformanceReport()
console.log(JSON.stringify(report, null, 2))