refactor: migrated environment variable access from process.env to Bun.env across 71 files

- Migrated all environment variable access from Node.js `process.env` to Bun runtime `Bun.env` API across the entire codebase.
- Updated 71 files including source code, tests, documentation, and configuration to use Bun's native environment variable API.
- Added comprehensive environment variables documentation in `packages/coding-agent/docs/environment-variables.md` covering 100+ environment variables organized by category.
- Updated CHANGELOG entries to reflect migration from `process.env` to `Bun.env` and environment variable prefix changes (PI_* vs OMP_*).
- Maintained identical behavior and logic across all changes - only the environment variable access method was updated.
This commit is contained in:
can1357
2026-02-05 04:51:18 +01:00
parent be9357694f
commit 89e6479661
71 changed files with 494 additions and 196 deletions
+2 -2
View File
@@ -64,7 +64,7 @@ We run on Bun. Replace Node APIs only when Bun provides a better alternative.
**DO NOT replace (these work fine in Bun):**
- `os.homedir()` — do NOT replace with `process.env.HOME`, `Bun.env.HOME`, or literal `"~"`
- `os.homedir()` — do NOT replace with `Bun.env.HOME`, `Bun.env.HOME`, or literal `"~"`
- `os.tmpdir()` — do NOT replace with `Bun.env.TMPDIR || "/tmp"` or hardcoded paths
- `fs.mkdtempSync()` — do NOT replace with manual path construction
- `path.join()`, `path.resolve()`, etc. — these are fine
@@ -82,7 +82,7 @@ We run on Bun. Replace Node APIs only when Bun provides a better alternative.
```typescript
// BROKEN: env vars may be undefined, "~" is not expanded
const home = process.env.HOME || Bun.env.HOME || "~";
const home = Bun.env.HOME || Bun.env.HOME || "~";
const tmp = Bun.env.TMPDIR || "/tmp";
```
+43 -7
View File
@@ -3,9 +3,10 @@
## [Unreleased]
## [11.0.0] - 2026-02-05
### Changed
- Replaced direct `process.env` access with `getEnv()` utility from `@oh-my-pi/pi-utils` for consistent environment variable handling across all providers
- Replaced direct `Bun.env` access with `getEnv()` utility from `@oh-my-pi/pi-utils` for consistent environment variable handling across all providers
- Updated environment variable names from `OMP_*` prefix to `PI_*` prefix for consistency (e.g., `OMP_CODING_AGENT_DIR` → `PI_CODING_AGENT_DIR`)
### Removed
@@ -13,6 +14,7 @@
- Removed automatic environment variable migration from `PI_*` to `OMP_*` prefixes via `migrate-env.ts` module
## [10.5.0] - 2026-02-04
### Changed
- Updated @anthropic-ai/sdk to ^0.72.1
@@ -28,18 +30,20 @@
- Removed undici dependency
## [9.4.0] - 2026-01-31
### Added
- Added `getEnv()` function to retrieve environment variables from process.env, cwd/.env, or ~/.env
- Added `getEnv()` function to retrieve environment variables from Bun.env, cwd/.env, or ~/.env
- Added support for reading .env files from home directory and current working directory
- Added support for `exa` and `perplexity` as known providers in `getEnvApiKey()`
### Changed
- Changed `getEnvApiKey()` to check process.env, cwd/.env, and ~/.env files in order of precedence
- Changed `getEnvApiKey()` to check Bun.env, cwd/.env, and ~/.env files in order of precedence
- Refactored provider API key resolution to use a declarative service provider map
## [9.2.2] - 2026-01-31
### Added
- Added OpenCode Zen provider with API key authentication for accessing multiple AI models
@@ -86,31 +90,40 @@
## [8.6.0] - 2026-01-27
### Changed
- Replaced JSON5 dependency with Bun.JSON5 parsing
### Fixed
- Filtered empty user text blocks for OpenAI-compatible completions and normalized Kimi reasoning_content for OpenRouter tool-call messages
## [8.4.0] - 2026-01-25
### Added
- Added Azure OpenAI Responses provider with deployment mapping and resource-based base URL support
### Changed
- Added OpenRouter routing preferences for OpenAI-compatible completions
### Fixed
- Defaulted Google tool call arguments to empty objects when providers omit args
- Guarded Responses/Codex streaming deltas against missing content parts and handled arguments.done events
## [8.2.1] - 2026-01-24
### Fixed
- Fixed handling of streaming function call arguments in OpenAI responses to properly parse arguments when sent via `response.function_call_arguments.done` events
## [8.2.0] - 2026-01-24
### Changed
- Migrated node module imports from named to namespace imports across all packages for consistency with project guidelines
## [8.0.0] - 2026-01-23
### Fixed
@@ -119,6 +132,7 @@
- Fixed 400 errors when reading multiple images via GitHub Copilot's Claude models. Claude requires tool_use -> tool_result adjacency with no user messages interleaved. Images from consecutive tool results are now batched into a single user message
## [7.0.0] - 2026-01-21
### Added
- Added usage tracking system with normalized schema for provider quota/limit endpoints
@@ -152,12 +166,14 @@
- Fixed Gemini CLI usage windows to consistently label quota windows for display suppression
## [6.9.69] - 2026-01-21
### Added
- Added duration and time-to-first-token (ttft) metrics to all AI provider responses
- Added performance tracking for streaming responses across all providers
## [6.9.0] - 2026-01-21
### Removed
- Removed openai-codex provider exports from main package index
@@ -165,6 +181,7 @@
- Removed vitest configuration file
## [6.8.4] - 2026-01-21
### Changed
- Updated prompt caching strategy to follow Anthropic's recommended hierarchy
@@ -173,6 +190,7 @@
- Increased OAuth callback timeout from 120 seconds to 120,000 milliseconds
## [6.8.3] - 2026-01-21
### Added
- Added `headers` option to all providers for custom request headers
@@ -197,6 +215,7 @@
- Enhanced AWS credential detection to support ECS task roles and IRSA web identity tokens
## [6.8.2] - 2026-01-21
### Fixed
- Improved error handling for aborted requests in Google Gemini CLI provider
@@ -238,6 +257,7 @@
- Fixed token refresh for all OAuth providers
## [6.7.670] - 2026-01-19
### Changed
- Updated Claude Code compatibility headers and version
@@ -247,16 +267,19 @@
- Updated PKCE verifier generation for better security
## [5.7.67] - 2026-01-18
### Fixed
- Added error handling for unknown OAuth providers
## [5.6.77] - 2026-01-18
### Fixed
- Prevented duplicate tool results for errored or aborted messages when results already exist
## [5.6.7] - 2026-01-18
### Added
- Added automatic retry logic for OpenAI Codex responses with configurable delay and max retries
@@ -265,8 +288,8 @@
### Changed
- Updated environment variable prefix from PI_ to OMP_ for better consistency
- Added automatic migration for legacy PI_ environment variables to OMP_ equivalents
- Updated environment variable prefix from PI* to OMP* for better consistency
- Added automatic migration for legacy PI* environment variables to OMP* equivalents
- Adjusted Bedrock Claude thinking budgets to reserve output tokens when maxTokens is too low
### Fixed
@@ -281,6 +304,7 @@
- Fixed orphaned tool call handling to skip synthetic results for errored assistant messages
## [5.5.0] - 2026-01-18
### Changed
- Updated User-Agent header from 'opencode' to 'pi' for OpenAI Codex requests
@@ -288,6 +312,7 @@
- Removed bridge text override from Codex system prompt builder
## [5.3.0] - 2026-01-15
### Changed
- Replaced detailed Codex system instructions with simplified pi assistant instructions
@@ -307,6 +332,7 @@
- Fixed numbered list items showing "1." for all items when code blocks break list continuity (via `start` property)
## [5.0.0] - 2026-01-12
### Added
- Added support for `xhigh` thinking level in `thinkingBudgets` configuration
@@ -318,6 +344,7 @@
- Changed `supportsXhigh()` to return true for all Anthropic models
## [4.6.0] - 2026-01-12
### Fixed
- Fixed incorrect classification of thought signatures in Google Gemini responses—thought signatures are now correctly treated as metadata rather than thinking content indicators
@@ -325,22 +352,26 @@
- Fixed Google schema sanitization stripping property names that match schema keywords (e.g., "pattern", "format") from tool definitions
## [4.4.9] - 2026-01-12
### Fixed
- Fixed Google provider schema sanitization to strip additional unsupported JSON Schema fields (patternProperties, additionalProperties, min/max constraints, pattern, format)
## [4.4.8] - 2026-01-12
### Fixed
- Fixed Google provider schema sanitization to properly collapse `anyOf`/`oneOf` with const values into enum arrays
- Fixed const-to-enum conversion to infer type from the const value when type is not specified
## [4.4.6] - 2026-01-11
### Fixed
- Fixed tool parameter schema sanitization to only apply Google-specific transformations for Gemini models, preserving original schemas for other model types
## [4.4.5] - 2026-01-11
### Changed
- Exported `sanitizeSchemaForGoogle` utility function for external use
@@ -375,13 +406,14 @@
- Changed Cursor debug logging to use structured JSONL format with automatic MCP argument decoding
- Changed MCP tool argument decoding to use protobuf Value schema for improved type handling
- Changed tool advertisement to filter Cursor native tools (bash, read, write, delete, ls, grep, lsp) instead of only exposing mcp_ prefixed tools
- Changed tool advertisement to filter Cursor native tools (bash, read, write, delete, ls, grep, lsp) instead of only exposing mcp\_ prefixed tools
### Fixed
- Fixed Cursor conversation history serialization so subagents retain task context and can call complete
## [4.2.1] - 2026-01-11
### Changed
- Updated `reasoningSummary` option to accept only `"auto"`, `"concise"`, `"detailed"`, or `null` (removed `"off"` and `"on"` values)
@@ -393,6 +425,7 @@
- Fixed Cloud Code Assist tool schema conversion to avoid unsupported `const` fields
## [4.0.0] - 2026-01-10
### Added
- Added `betas` option in `AnthropicOptions` for passing custom Anthropic beta feature flags
@@ -434,6 +467,7 @@
- Crash when pasting text with trailing whitespace exceeding terminal width
## [3.37.1] - 2026-01-10
### Added
- Added automatic type coercion for tool arguments when LLMs return JSON-encoded strings instead of native types (numbers, booleans, arrays, objects)
@@ -444,11 +478,13 @@
- Changed validation error messages to include both original and normalized arguments when coercion was attempted
## [3.37.0] - 2026-01-10
### Changed
- Enabled type coercion in JSON schema validation to automatically convert compatible types
## [3.35.0] - 2026-01-09
### Added
- Enhanced error messages to include retry-after timing information from API rate limit headers
@@ -729,4 +765,4 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
## [0.9.4] - 2025-11-26
Initial release with multi-provider LLM support.
Initial release with multi-provider LLM support.
+1 -1
View File
@@ -105,7 +105,7 @@ const serviceProviderMap: Record<string, KeyResolver> = {
* Get API key for provider from known environment variables, e.g. OPENAI_API_KEY.
*
* Will not return API keys for providers that require OAuth tokens.
* Checks process.env, then cwd/.env, then ~/.env.
* Checks Bun.env, then cwd/.env, then ~/.env.
*/
export function getEnvApiKey(provider: string): string | undefined {
const resolver = serviceProviderMap[provider];
+16 -16
View File
@@ -100,7 +100,7 @@ describe("Context overflow error handling", () => {
describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Anthropic (API Key)", () => {
it("claude-3-5-haiku - should detect overflow via isContextOverflow", async () => {
const model = getModel("anthropic", "claude-3-5-haiku-20241022");
const result = await testContextOverflow(model, process.env.ANTHROPIC_API_KEY!);
const result = await testContextOverflow(model, Bun.env.ANTHROPIC_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -167,7 +167,7 @@ describe("Context overflow error handling", () => {
it("gpt-4o-mini - should detect overflow via isContextOverflow", async () => {
const model = { ...getModel("openai", "gpt-4o-mini") };
model.api = "openai-completions" as any;
const result = await testContextOverflow(model, process.env.OPENAI_API_KEY!);
const result = await testContextOverflow(model, Bun.env.OPENAI_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -179,7 +179,7 @@ describe("Context overflow error handling", () => {
describe.skipIf(!e2eApiKey("OPENAI_API_KEY"))("OpenAI Responses", () => {
it("gpt-4o - should detect overflow via isContextOverflow", async () => {
const model = getModel("openai", "gpt-4o");
const result = await testContextOverflow(model, process.env.OPENAI_API_KEY!);
const result = await testContextOverflow(model, Bun.env.OPENAI_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -196,7 +196,7 @@ describe("Context overflow error handling", () => {
describe.skipIf(!e2eApiKey("GEMINI_API_KEY"))("Google", () => {
it("gemini-2.0-flash - should detect overflow via isContextOverflow", async () => {
const model = getModel("google", "gemini-2.0-flash");
const result = await testContextOverflow(model, process.env.GEMINI_API_KEY!);
const result = await testContextOverflow(model, Bun.env.GEMINI_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -292,7 +292,7 @@ describe("Context overflow error handling", () => {
describe.skipIf(!e2eApiKey("XAI_API_KEY"))("xAI", () => {
it("grok-3-fast - should detect overflow via isContextOverflow", async () => {
const model = getModel("xai", "grok-3-fast");
const result = await testContextOverflow(model, process.env.XAI_API_KEY!);
const result = await testContextOverflow(model, Bun.env.XAI_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -309,7 +309,7 @@ describe("Context overflow error handling", () => {
describe.skipIf(!e2eApiKey("GROQ_API_KEY"))("Groq", () => {
it("llama-3.3-70b-versatile - should detect overflow via isContextOverflow", async () => {
const model = getModel("groq", "llama-3.3-70b-versatile");
const result = await testContextOverflow(model, process.env.GROQ_API_KEY!);
const result = await testContextOverflow(model, Bun.env.GROQ_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -326,7 +326,7 @@ describe("Context overflow error handling", () => {
describe.skipIf(!e2eApiKey("CEREBRAS_API_KEY"))("Cerebras", () => {
it("qwen-3-235b - should detect overflow via isContextOverflow", async () => {
const model = getModel("cerebras", "qwen-3-235b-a22b-instruct-2507");
const result = await testContextOverflow(model, process.env.CEREBRAS_API_KEY!);
const result = await testContextOverflow(model, Bun.env.CEREBRAS_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -345,7 +345,7 @@ describe("Context overflow error handling", () => {
describe.skipIf(!e2eApiKey("ZAI_API_KEY"))("z.ai", () => {
it("glm-4.5-flash - should detect overflow via isContextOverflow (silent overflow or rate limit)", async () => {
const model = getModel("zai", "glm-4.5-flash");
const result = await testContextOverflow(model, process.env.ZAI_API_KEY!);
const result = await testContextOverflow(model, Bun.env.ZAI_API_KEY!);
logResult(result);
// z.ai behavior is inconsistent:
@@ -371,7 +371,7 @@ describe("Context overflow error handling", () => {
describe.skipIf(!e2eApiKey("MISTRAL_API_KEY"))("Mistral", () => {
it("devstral-medium-latest - should detect overflow via isContextOverflow", async () => {
const model = getModel("mistral", "devstral-medium-latest");
const result = await testContextOverflow(model, process.env.MISTRAL_API_KEY!);
const result = await testContextOverflow(model, Bun.env.MISTRAL_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -388,7 +388,7 @@ describe("Context overflow error handling", () => {
// Anthropic backend
it("anthropic/claude-sonnet-4 via OpenRouter - should detect overflow via isContextOverflow", async () => {
const model = getModel("openrouter", "anthropic/claude-sonnet-4");
const result = await testContextOverflow(model, process.env.OPENROUTER_API_KEY!);
const result = await testContextOverflow(model, Bun.env.OPENROUTER_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -399,7 +399,7 @@ describe("Context overflow error handling", () => {
// DeepSeek backend
it("deepseek/deepseek-v3.2 via OpenRouter - should detect overflow via isContextOverflow", async () => {
const model = getModel("openrouter", "deepseek/deepseek-v3.2");
const result = await testContextOverflow(model, process.env.OPENROUTER_API_KEY!);
const result = await testContextOverflow(model, Bun.env.OPENROUTER_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -410,7 +410,7 @@ describe("Context overflow error handling", () => {
// Mistral backend
it("mistralai/mistral-large-2512 via OpenRouter - should detect overflow via isContextOverflow", async () => {
const model = getModel("openrouter", "mistralai/mistral-large-2512");
const result = await testContextOverflow(model, process.env.OPENROUTER_API_KEY!);
const result = await testContextOverflow(model, Bun.env.OPENROUTER_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -421,7 +421,7 @@ describe("Context overflow error handling", () => {
// Google backend
it("google/gemini-2.5-flash via OpenRouter - should detect overflow via isContextOverflow", async () => {
const model = getModel("openrouter", "google/gemini-2.5-flash");
const result = await testContextOverflow(model, process.env.OPENROUTER_API_KEY!);
const result = await testContextOverflow(model, Bun.env.OPENROUTER_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -432,7 +432,7 @@ describe("Context overflow error handling", () => {
// Meta/Llama backend
it("meta-llama/llama-4-maverick via OpenRouter - should detect overflow via isContextOverflow", async () => {
const model = getModel("openrouter", "meta-llama/llama-4-maverick");
const result = await testContextOverflow(model, process.env.OPENROUTER_API_KEY!);
const result = await testContextOverflow(model, Bun.env.OPENROUTER_API_KEY!);
logResult(result);
expect(result.stopReason).toBe("error");
@@ -447,7 +447,7 @@ describe("Context overflow error handling", () => {
// Check if ollama is installed and local LLM tests are enabled
let ollamaInstalled = false;
if (!process.env.PI_NO_LOCAL_LLM) {
if (!Bun.env.PI_NO_LOCAL_LLM) {
try {
execSync("which ollama", { stdio: "ignore" });
ollamaInstalled = true;
@@ -541,7 +541,7 @@ describe("Context overflow error handling", () => {
// =============================================================================
let lmStudioRunning = false;
if (!process.env.PI_NO_LOCAL_LLM) {
if (!Bun.env.PI_NO_LOCAL_LLM) {
try {
execSync("curl -s --max-time 1 http://localhost:1234/v1/models > /dev/null", { stdio: "ignore" });
lmStudioRunning = true;
+2 -2
View File
@@ -17,7 +17,7 @@ import { isEnoent } from "@oh-my-pi/pi-utils";
* E2E tests require explicit opt-in via E2E=1 environment variable.
* This prevents accidental API calls when keys happen to be in the environment.
*/
export const E2E_ENABLED = process.env.E2E === "1" || process.env.E2E === "true";
export const E2E_ENABLED = Bun.env.E2E === "1" || Bun.env.E2E === "true";
/**
* Get an API key from environment, but only if E2E tests are enabled.
@@ -25,7 +25,7 @@ export const E2E_ENABLED = process.env.E2E === "1" || process.env.E2E === "true"
*/
export function e2eApiKey(envVar: string): string | undefined {
if (!E2E_ENABLED) return undefined;
return process.env[envVar];
return Bun.env[envVar];
}
const AUTH_PATH = path.join(os.homedir(), ".pi", "agent", "auth.json");
+6 -6
View File
@@ -4,14 +4,14 @@ import type { Context, Model } from "@oh-my-pi/pi-ai/types";
import { TempDir } from "@oh-my-pi/pi-utils";
const originalFetch = global.fetch;
const originalAgentDir = process.env.PI_CODING_AGENT_DIR;
const originalAgentDir = Bun.env.PI_CODING_AGENT_DIR;
afterEach(() => {
global.fetch = originalFetch;
if (originalAgentDir === undefined) {
delete process.env.PI_CODING_AGENT_DIR;
delete Bun.env.PI_CODING_AGENT_DIR;
} else {
process.env.PI_CODING_AGENT_DIR = originalAgentDir;
Bun.env.PI_CODING_AGENT_DIR = originalAgentDir;
}
vi.restoreAllMocks();
});
@@ -19,7 +19,7 @@ afterEach(() => {
describe("openai-codex streaming", () => {
it("streams SSE responses into AssistantMessageEventStream", async () => {
const tempDir = TempDir.createSync("@pi-codex-stream-");
process.env.PI_CODING_AGENT_DIR = tempDir.path();
Bun.env.PI_CODING_AGENT_DIR = tempDir.path();
const payload = Buffer.from(
JSON.stringify({ "https://api.openai.com/auth": { chatgpt_account_id: "acc_test" } }),
@@ -130,7 +130,7 @@ describe("openai-codex streaming", () => {
it("sets conversation_id/session_id headers and prompt_cache_key when sessionId is provided", async () => {
const tempDir = TempDir.createSync("@pi-codex-stream-");
process.env.PI_CODING_AGENT_DIR = tempDir.path();
Bun.env.PI_CODING_AGENT_DIR = tempDir.path();
const payload = Buffer.from(
JSON.stringify({ "https://api.openai.com/auth": { chatgpt_account_id: "acc_test" } }),
@@ -231,7 +231,7 @@ describe("openai-codex streaming", () => {
it("does not set conversation_id/session_id headers when sessionId is not provided", async () => {
const tempDir = TempDir.createSync("@pi-codex-stream-");
process.env.PI_CODING_AGENT_DIR = tempDir.path();
Bun.env.PI_CODING_AGENT_DIR = tempDir.path();
const payload = Buffer.from(
JSON.stringify({ "https://api.openai.com/auth": { chatgpt_account_id: "acc_test" } }),
+3 -3
View File
@@ -392,8 +392,8 @@ describe("Generate E2E Tests", () => {
});
describe("Google Vertex Provider (gemini-3-flash-preview)", () => {
const vertexProject = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
const vertexLocation = process.env.GOOGLE_CLOUD_LOCATION;
const vertexProject = Bun.env.GOOGLE_CLOUD_PROJECT || Bun.env.GCLOUD_PROJECT;
const vertexLocation = Bun.env.GOOGLE_CLOUD_LOCATION;
const isVertexConfigured = Boolean(vertexProject && vertexLocation);
const vertexOptions = { project: vertexProject, location: vertexLocation } as const;
const llm = getModel("google-vertex", "gemini-3-flash-preview");
@@ -1350,7 +1350,7 @@ describe("Generate E2E Tests", () => {
// Check if ollama is installed and local LLM tests are enabled
let ollamaInstalled = false;
if (!process.env.PI_NO_LOCAL_LLM) {
if (!Bun.env.PI_NO_LOCAL_LLM) {
try {
execSync("which ollama", { stdio: "ignore" });
ollamaInstalled = true;
+11 -11
View File
@@ -106,7 +106,7 @@ describe("totalTokens field", () => {
const llm = getModel("anthropic", "claude-3-5-haiku-20241022");
console.log(`\nAnthropic / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.ANTHROPIC_API_KEY });
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: Bun.env.ANTHROPIC_API_KEY });
logUsage("First request", first);
logUsage("Second request", second);
@@ -224,7 +224,7 @@ describe("totalTokens field", () => {
const llm = getModel("xai", "grok-3-fast");
console.log(`\nxAI / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.XAI_API_KEY });
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: Bun.env.XAI_API_KEY });
logUsage("First request", first);
logUsage("Second request", second);
@@ -247,7 +247,7 @@ describe("totalTokens field", () => {
const llm = getModel("groq", "openai/gpt-oss-120b");
console.log(`\nGroq / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.GROQ_API_KEY });
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: Bun.env.GROQ_API_KEY });
logUsage("First request", first);
logUsage("Second request", second);
@@ -270,7 +270,7 @@ describe("totalTokens field", () => {
const llm = getModel("cerebras", "gpt-oss-120b");
console.log(`\nCerebras / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.CEREBRAS_API_KEY });
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: Bun.env.CEREBRAS_API_KEY });
logUsage("First request", first);
logUsage("Second request", second);
@@ -293,7 +293,7 @@ describe("totalTokens field", () => {
const llm = getModel("zai", "glm-4.5-flash");
console.log(`\nz.ai / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.ZAI_API_KEY });
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: Bun.env.ZAI_API_KEY });
logUsage("First request", first);
logUsage("Second request", second);
@@ -316,7 +316,7 @@ describe("totalTokens field", () => {
const llm = getModel("mistral", "devstral-medium-latest");
console.log(`\nMistral / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.MISTRAL_API_KEY });
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: Bun.env.MISTRAL_API_KEY });
logUsage("First request", first);
logUsage("Second request", second);
@@ -339,7 +339,7 @@ describe("totalTokens field", () => {
const llm = getModel("openrouter", "anthropic/claude-sonnet-4");
console.log(`\nOpenRouter / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.OPENROUTER_API_KEY });
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: Bun.env.OPENROUTER_API_KEY });
logUsage("First request", first);
logUsage("Second request", second);
@@ -356,7 +356,7 @@ describe("totalTokens field", () => {
const llm = getModel("openrouter", "deepseek/deepseek-chat");
console.log(`\nOpenRouter / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.OPENROUTER_API_KEY });
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: Bun.env.OPENROUTER_API_KEY });
logUsage("First request", first);
logUsage("Second request", second);
@@ -373,7 +373,7 @@ describe("totalTokens field", () => {
const llm = getModel("openrouter", "mistralai/mistral-small-3.1-24b-instruct");
console.log(`\nOpenRouter / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.OPENROUTER_API_KEY });
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: Bun.env.OPENROUTER_API_KEY });
logUsage("First request", first);
logUsage("Second request", second);
@@ -390,7 +390,7 @@ describe("totalTokens field", () => {
const llm = getModel("openrouter", "google/gemini-2.0-flash-001");
console.log(`\nOpenRouter / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.OPENROUTER_API_KEY });
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: Bun.env.OPENROUTER_API_KEY });
logUsage("First request", first);
logUsage("Second request", second);
@@ -407,7 +407,7 @@ describe("totalTokens field", () => {
const llm = getModel("openrouter", "meta-llama/llama-4-maverick");
console.log(`\nOpenRouter / ${llm.id}:`);
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: process.env.OPENROUTER_API_KEY });
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: Bun.env.OPENROUTER_API_KEY });
logUsage("First request", first);
logUsage("Second request", second);
+20
View File
@@ -1,6 +1,26 @@
# Changelog
## [Unreleased]
### Added
- Added comprehensive environment variables reference documentation at `docs/environment-variables.md` covering API keys, configuration, debugging, and testing variables
- Added theme system with 44 customizable color tokens, two built-in themes (dark/light), and auto-detection based on terminal background
- Added `/theme` command to interactively select and switch between themes
- Added support for custom themes in `~/.pi/agent/themes/*.json` with live editing - changes apply immediately when files are saved
- Added `userMessageText` theme token for customizing user message text color
- Added `toolTitle` and `toolOutput` theme tokens for separate coloring of tool execution box titles and output
### Changed
- Changed all environment variable access from `process.env` to `Bun.env` throughout the codebase for Bun runtime compatibility
- Updated documentation to reference `Bun.env` instead of `process.env` in examples and comments
### Fixed
- Fixed `Text` component to properly implement `invalidate()` method, ensuring theme changes apply correctly to all UI elements
- Fixed `TruncatedText` component to properly pad all lines to exactly match the specified width, preventing rendering artifacts
- Fixed `TruncatedText` component to stop at the first newline and only display the first line
- Fixed invalid or malformed themes to fall back gracefully to dark theme instead of crashing the application
## [11.0.2] - 2026-02-05
+1 -1
View File
@@ -368,7 +368,7 @@ When disabled, neither case triggers automatic compaction (use `/compact` manual
**Environment Variables (`env`):**
- Automatically sets environment variables when the application starts
- Only sets variables that aren't already present in `process.env`
- Only sets variables that aren't already present in `Bun.env`
- Supports any environment variable, not just API keys
- Order of precedence: existing env vars > settings.json env vars > auth.json env vars
@@ -0,0 +1,246 @@
# Environment Variables Reference
This document lists all environment variables used by the coding agent.
## API Keys
### Multi-Provider Support
| Variable | Description | Default |
|----------|-------------|---------|
| `OPENAI_API_KEY` | OpenAI API key for GPT models | - |
| `ANTHROPIC_API_KEY` | Anthropic API key for Claude models | - |
| `ANTHROPIC_OAUTH_TOKEN` | Anthropic OAuth token (takes precedence over `ANTHROPIC_API_KEY`) | - |
| `GOOGLE_API_KEY` | Google Gemini API key | - |
| `COPILOT_GITHUB_TOKEN` | GitHub Copilot personal access token | `$GH_TOKEN` or `$GITHUB_TOKEN` |
| `GH_TOKEN` | GitHub CLI token (fallback for Copilot) | - |
| `GITHUB_TOKEN` | GitHub token (fallback for Copilot and API access) | - |
| `EXA_API_KEY` | Exa search API key | - |
### Provider-Specific Configuration
#### AWS Bedrock
| Variable | Description | Default |
|----------|-------------|---------|
| `AWS_REGION` | AWS region for Bedrock | `$AWS_DEFAULT_REGION` or `us-east-1` |
| `AWS_DEFAULT_REGION` | AWS default region | - |
| `AWS_PROFILE` | AWS CLI profile name | - |
| `AWS_ACCESS_KEY_ID` | AWS access key ID | - |
| `AWS_SECRET_ACCESS_KEY` | AWS secret access key | - |
| `AWS_BEARER_TOKEN_BEDROCK` | AWS bearer token for Bedrock | - |
| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` | ECS container credentials URI | - |
| `AWS_CONTAINER_CREDENTIALS_FULL_URI` | ECS container credentials full URI | - |
| `AWS_WEB_IDENTITY_TOKEN_FILE` | Web identity token file path | - |
| `AWS_ROLE_ARN` | IAM role ARN for web identity | - |
#### Azure OpenAI
| Variable | Description | Default |
|----------|-------------|---------|
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API key (required) | - |
| `AZURE_OPENAI_API_VERSION` | Azure OpenAI API version | `2024-10-01-preview` |
| `AZURE_OPENAI_BASE_URL` | Azure OpenAI base URL | Constructed from resource name |
| `AZURE_OPENAI_RESOURCE_NAME` | Azure OpenAI resource name | - |
| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | JSON map of model IDs to deployment names | `{}` |
#### Google Cloud (Vertex AI)
| Variable | Description | Default |
|----------|-------------|---------|
| `GOOGLE_CLOUD_PROJECT` | Google Cloud project ID (required for Vertex AI) | `$GCLOUD_PROJECT` |
| `GCLOUD_PROJECT` | Google Cloud project ID (alternative) | - |
| `GOOGLE_CLOUD_PROJECT_ID` | Google Cloud project ID (used during OAuth discovery) | `$GOOGLE_CLOUD_PROJECT` |
| `GOOGLE_CLOUD_LOCATION` | Google Cloud location (required for Vertex AI) | - |
| `GOOGLE_APPLICATION_CREDENTIALS` | Path to Google service account JSON | - |
#### Anthropic Search & Custom Base URLs
| Variable | Description | Default |
|----------|-------------|---------|
| `ANTHROPIC_BASE_URL` | Custom Anthropic API base URL | - |
| `ANTHROPIC_SEARCH_API_KEY` | API key for Anthropic search (separate from main API) | - |
| `ANTHROPIC_SEARCH_BASE_URL` | Custom base URL for Anthropic search | - |
| `ANTHROPIC_SEARCH_MODEL` | Model to use for web search | `claude-sonnet-4` |
#### Kimi
| Variable | Description | Default |
|----------|-------------|---------|
| `KIMI_CODE_BASE_URL` | Kimi Code API base URL | `https://kimi.moonshot.cn` |
| `KIMI_CODE_OAUTH_HOST` | Kimi Code OAuth host | `$KIMI_OAUTH_HOST` or `https://kimi.moonshot.cn` |
| `KIMI_OAUTH_HOST` | Kimi OAuth host (fallback) | - |
## Model Configuration
### Model Role Overrides
Override model roles via environment variables (ephemeral, not persisted):
| Variable | Description | CLI Flag |
|----------|-------------|----------|
| `PI_SMOL_MODEL` | Fast model for lightweight tasks | `--smol` |
| `PI_SLOW_MODEL` | Reasoning model for thorough analysis | `--slow` |
| `PI_PLAN_MODEL` | Model for architectural planning | `--plan` |
## Agent Configuration
### Core Settings
| Variable | Description | Default |
|----------|-------------|---------|
| `PI_CODING_AGENT_DIR` | Directory for agent data (sessions, auth, cache) | `~/.omp/agent` |
| `PI_SUBPROCESS_CMD` | Custom command for spawning subagents | Auto-detected |
| `PI_NO_TITLE` | Disable automatic session title generation | `false` |
| `NULL_PROMPT` | Use empty system prompt (testing) | `false` |
| `PI_BLOCKED_AGENT` | Override agent type in task tool | - |
### Caching
| Variable | Description | Default |
|----------|-------------|---------|
| `PI_CACHE_RETENTION` | Prompt cache retention (`long` = 24h for OpenAI) | - |
## Python Configuration
### Python Kernel
| Variable | Description | Default |
|----------|-------------|---------|
| `VIRTUAL_ENV` | Python virtual environment path | Auto-detected (`.venv` or `venv`) |
| `PI_PY` | Python tool mode (`per-session`, `per-call`, `off`) | `per-session` |
| `PI_PYTHON_SKIP_CHECK` | Skip Python availability check (testing) | `false` |
### External Python Gateway
| Variable | Description | Default |
|----------|-------------|---------|
| `PI_PYTHON_GATEWAY_URL` | External Python gateway URL | - |
| `PI_PYTHON_GATEWAY_TOKEN` | Authentication token for gateway | - |
### Debugging
| Variable | Description | Default |
|----------|-------------|---------|
| `PI_PYTHON_IPC_TRACE` | Trace Python IPC messages (`1` = enabled) | `false` |
## Task & Subagent Configuration
| Variable | Description | Default |
|----------|-------------|---------|
| `PI_TASK_MAX_OUTPUT_BYTES` | Maximum output bytes per subagent | `500000` |
| `PI_TASK_MAX_OUTPUT_LINES` | Maximum output lines per subagent | `5000` |
## TUI & Terminal Configuration
### Terminal Capabilities
| Variable | Description | Auto-Detected |
|----------|-------------|---------------|
| `COLORTERM` | Terminal color support (`truecolor`, `24bit`) | Yes |
| `COLORFGBG` | Terminal foreground/background colors | Yes |
| `TERM` | Terminal type | Yes |
| `TERM_PROGRAM` | Terminal program name | Yes |
| `TERM_PROGRAM_VERSION` | Terminal program version | Yes |
| `TERMINAL_EMULATOR` | Terminal emulator name | Yes |
| `WT_SESSION` | Windows Terminal session ID | Yes |
### TUI Behavior
| Variable | Description | Default |
|----------|-------------|---------|
| `PI_NOTIFICATIONS` | Desktop notifications (`off`, `0`, `false` = disabled) | Enabled |
| `PI_TUI_WRITE_LOG` | Log all TUI write operations to file | - |
| `PI_HARDWARE_CURSOR` | Show hardware cursor (`1` = enabled) | `false` |
## Bash & Shell Configuration
### Shell Detection
| Variable | Description | Auto-Detected |
|----------|-------------|---------------|
| `SHELL` | User's default shell | Yes (Unix) |
| `ComSpec` | Command processor | Yes (Windows) |
### Editor
| Variable | Description | Fallback |
|----------|-------------|----------|
| `VISUAL` | Visual editor for external editing (Ctrl+G) | `$EDITOR` |
| `EDITOR` | Default text editor | - |
### Bash Tool Behavior
| Variable | Description | Default |
|----------|-------------|---------|
| `PI_BASH_NO_CI` | Don't set `CI=true` in bash environment | `$CLAUDE_BASH_NO_CI` |
| `CLAUDE_BASH_NO_CI` | Legacy name for `PI_BASH_NO_CI` | - |
| `PI_BASH_NO_LOGIN` | Don't use login shell for bash | `$CLAUDE_BASH_NO_LOGIN` |
| `CLAUDE_BASH_NO_LOGIN` | Legacy name for `PI_BASH_NO_LOGIN` | - |
| `PI_SHELL_PREFIX` | Prefix for bash commands | `$CLAUDE_CODE_SHELL_PREFIX` |
| `CLAUDE_CODE_SHELL_PREFIX` | Legacy name for `PI_SHELL_PREFIX` | - |
## Desktop Environment Detection
These are auto-detected for system prompt context:
| Variable | Purpose |
|----------|---------|
| `KDE_FULL_SESSION` | Detect KDE desktop |
| `XDG_CURRENT_DESKTOP` | Current desktop environment |
| `DESKTOP_SESSION` | Desktop session name |
| `XDG_SESSION_DESKTOP` | XDG desktop session |
| `GDMSESSION` | GDM session type |
| `WINDOWMANAGER` | Window manager name |
| `XDG_CONFIG_HOME` | User config directory |
| `APPDATA` | Windows app data directory |
| `HOME` | User home directory |
## LSP Configuration
| Variable | Description | Default |
|----------|-------------|---------|
| `PI_DISABLE_LSPMUX` | Disable lspmux integration (`1` = disabled) | `false` |
## Debugging & Development
### General Debugging
| Variable | Description | Default |
|----------|-------------|---------|
| `DEBUG` | Enable debug logging | `false` |
| `PI_DEV` | Development mode (verbose native addon loading) | `false` |
| `PI_TIMING` | Log tool factory and operation timings (`1` = enabled) | `false` |
### Startup Debugging
| Variable | Description | Default |
|----------|-------------|---------|
| `PI_DEBUG_STARTUP` | Print startup stage timings to stderr | `false` |
### Provider-Specific Debugging
| Variable | Description | Default |
|----------|-------------|---------|
| `DEBUG_CURSOR` | Cursor provider debug logging (`1` = basic, `2` or `verbose` = detailed) | `false` |
| `DEBUG_CURSOR_LOG` | Path to write Cursor debug log file | - |
| `PI_CODEX_DEBUG` | OpenAI Codex debug logging (`1` or `true` = enabled) | `false` |
## Commit Message Generation
| Variable | Description | Default |
|----------|-------------|---------|
| `PI_COMMIT_TEST_FALLBACK` | Force fallback commit generation (testing) | `false` |
| `PI_COMMIT_NO_FALLBACK` | Disable fallback commit generation | `false` |
| `PI_COMMIT_MAP_REDUCE` | Enable/disable map-reduce for large diffs (`false` = disabled) | Enabled |
## Testing & CI
These are auto-detected but documented for completeness:
| Variable | Description |
|----------|-------------|
| `BUN_ENV` | Bun environment (`test` skips certain checks) |
| `NODE_ENV` | Node environment (`test` skips certain checks) |
| `E2E` | Enable end-to-end tests (`1` or `true`) |
| `PI_NO_LOCAL_LLM` | Skip local LLM tests (Ollama, LM Studio) |
+6 -10
View File
@@ -87,7 +87,7 @@ interface AgentSession {
prompt(text: string, options?: PromptOptions): Promise<void>;
sendUserMessage(
content: string | (TextContent | ImageContent)[],
options?: { deliverAs?: "steer" | "followUp" },
options?: { deliverAs?: "steer" | "followUp" }
): Promise<void>;
steer(text: string): void;
followUp(text: string): void;
@@ -106,7 +106,7 @@ interface AgentSession {
setThinkingLevel(level: ThinkingLevel): void;
cycleModel(direction?: "forward" | "backward"): Promise<ModelCycleResult | undefined>;
cycleRoleModels(
direction?: "forward" | "backward",
direction?: "forward" | "backward"
): Promise<{ model: Model; thinkingLevel: ThinkingLevel; role: ModelRole } | undefined>;
cycleThinkingLevel(): ThinkingLevel | undefined;
@@ -141,7 +141,7 @@ interface AgentSession {
// Compaction
compact(
customInstructions?: string,
options?: { onComplete?: (result: CompactionResult) => void; onError?: (error: Error) => void },
options?: { onComplete?: (result: CompactionResult) => void; onError?: (error: Error) => void }
): Promise<CompactionResult>;
abortCompaction(): void;
@@ -491,11 +491,7 @@ const { session } = await createAgentSession({
Extensions intercept agent events and can register custom tools/commands. Hooks remain for legacy compatibility.
```typescript
import {
createAgentSession,
discoverExtensions,
type ExtensionFactory,
} from "@oh-my-pi/pi-coding-agent";
import { createAgentSession, discoverExtensions, type ExtensionFactory } from "@oh-my-pi/pi-coding-agent";
// Inline extension
const loggingExtension: ExtensionFactory = (api) => {
@@ -878,8 +874,8 @@ import systemPrompt from "./SYSTEM.md" with { type: "text" };
const authStorage = await discoverAuthStorage();
// Runtime API key override (not persisted)
if (process.env.MY_KEY) {
authStorage.setRuntimeApiKey("anthropic", process.env.MY_KEY);
if (Bun.env.MY_KEY) {
authStorage.setRuntimeApiKey("anthropic", Bun.env.MY_KEY);
}
// Model registry
+1 -1
View File
@@ -78,7 +78,7 @@ const { session } = await createAgentSession({
// Full control
const customAuth = new AuthStorage("/my/app/auth.json");
customAuth.setRuntimeApiKey("anthropic", process.env.MY_KEY!);
customAuth.setRuntimeApiKey("anthropic", Bun.env.MY_KEY!);
const customRegistry = new ModelRegistry(customAuth);
const { session } = await createAgentSession({
@@ -150,7 +150,7 @@ function emptyCustomModelsResult(error?: string): CustomModelsResult {
* Checks environment variable first, then treats as literal.
*/
function resolveApiKeyConfig(keyConfig: string): string | undefined {
const envValue = process.env[keyConfig];
const envValue = Bun.env[keyConfig];
if (envValue) return envValue;
return keyConfig;
}
@@ -86,7 +86,7 @@ export async function createReportBundle(options: ReportBundleOptions): Promise<
files.push("system.json");
// Sanitized environment
data["env.json"] = JSON.stringify(sanitizeEnv(process.env as Record<string, string>), null, 2);
data["env.json"] = JSON.stringify(sanitizeEnv(Bun.env as Record<string, string>), null, 2);
files.push("env.json");
// Settings/config
@@ -28,8 +28,8 @@ export async function collectSystemInfo(): Promise<SystemInfo> {
const cpuModel = cpus[0]?.model ?? "Unknown CPU";
// Try to get shell from environment
const shell = process.env.SHELL ?? process.env.ComSpec ?? "unknown";
const terminal = process.env.TERM_PROGRAM ?? process.env.TERM ?? undefined;
const shell = Bun.env.SHELL ?? Bun.env.ComSpec ?? "unknown";
const terminal = Bun.env.TERM_PROGRAM ?? Bun.env.TERM ?? undefined;
return {
os: `${os.type()} ${os.release()} (${os.platform()})`,
+3 -3
View File
@@ -160,7 +160,7 @@ function extractMCPServersFromToml(toml: Record<string, unknown>): Record<string
const env: Record<string, string> = { ...config.env };
if (config.env_vars) {
for (const varName of config.env_vars) {
const value = process.env[varName];
const value = Bun.env[varName];
if (value !== undefined) {
env[varName] = value;
}
@@ -174,14 +174,14 @@ function extractMCPServersFromToml(toml: Record<string, unknown>): Record<string
const headers: Record<string, string> = { ...config.http_headers };
if (config.env_http_headers) {
for (const [headerName, envVarName] of Object.entries(config.env_http_headers)) {
const value = process.env[envVarName];
const value = Bun.env[envVarName];
if (value !== undefined) {
headers[headerName] = value;
}
}
}
if (config.bearer_token_env_var) {
const token = process.env[config.bearer_token_env_var];
const token = Bun.env[config.bearer_token_env_var];
if (token) {
headers.Authorization = `Bearer ${token}`;
}
@@ -294,7 +294,7 @@ export async function loadSkillsFromDir(
*/
export function expandEnvVars(value: string, extraEnv?: Record<string, string>): string {
return value.replace(/\$\{([^}:]+)(?::-([^}]*))?\}/g, (_, varName: string, defaultValue?: string) => {
const envValue = extraEnv?.[varName] ?? process.env[varName];
const envValue = extraEnv?.[varName] ?? Bun.env[varName];
if (envValue !== undefined) return envValue;
if (defaultValue !== undefined) return defaultValue;
return `\${${varName}}`;
+2 -2
View File
@@ -18,9 +18,9 @@ import type {
MCPToolWrapperConfig,
} from "./types";
/** Find EXA_API_KEY from process.env or .env files */
/** Find EXA_API_KEY from Bun.env or .env files */
export async function findApiKey(): Promise<string | null> {
// Check process.env first
// Check Bun.env first
if ($env.EXA_API_KEY) {
return $env.EXA_API_KEY;
}
@@ -29,7 +29,7 @@ export async function runDoctorChecks(): Promise<DoctorCheck[]> {
];
for (const key of apiKeys) {
const hasKey = !!process.env[key.name];
const hasKey = !!Bun.env[key.name];
checks.push({
name: key.name,
status: hasKey ? "ok" : "warning",
+1 -1
View File
@@ -225,7 +225,7 @@ export async function warmPythonEnvironment(
useSharedGateway?: boolean,
sessionFile?: string,
): Promise<{ ok: boolean; reason?: string; docs: PreludeHelper[] }> {
const isTestEnv = process.env.BUN_ENV === "test" || process.env.NODE_ENV === "test";
const isTestEnv = Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
let cacheState: PreludeCacheState | null = null;
try {
debugStartup("warmPython:ensureKernel:start");
+1 -1
View File
@@ -108,7 +108,7 @@ export interface PythonKernelAvailability {
}
export async function checkPythonKernelAvailability(cwd: string): Promise<PythonKernelAvailability> {
if (process.env.BUN_ENV === "test" || process.env.NODE_ENV === "test" || $env.PI_PYTHON_SKIP_CHECK === "1") {
if (Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test" || $env.PI_PYTHON_SKIP_CHECK === "1") {
return { ok: true };
}
+1 -1
View File
@@ -412,7 +412,7 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string, initT
const proc = ptree.spawn([command, ...args], {
cwd,
stdin: "pipe",
env: env ? { ...process.env, ...env } : undefined,
env: env ? { ...Bun.env, ...env } : undefined,
});
const client: LspClient = {
+2 -2
View File
@@ -66,11 +66,11 @@ function getConfigPath(): string {
const home = os.homedir();
switch (os.platform()) {
case "win32":
return path.join(process.env.APPDATA ?? path.join(home, "AppData", "Roaming"), "lspmux", "config.toml");
return path.join(Bun.env.APPDATA ?? path.join(home, "AppData", "Roaming"), "lspmux", "config.toml");
case "darwin":
return path.join(home, "Library", "Application Support", "lspmux", "config.toml");
default:
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(home, ".config"), "lspmux", "config.toml");
return path.join(Bun.env.XDG_CONFIG_HOME ?? path.join(home, ".config"), "lspmux", "config.toml");
}
}
@@ -47,7 +47,7 @@ export class StdioTransport implements MCPTransport {
const args = this.config.args ?? [];
const env = {
...process.env,
...Bun.env,
...this.config.env,
};
@@ -114,7 +114,7 @@ export class RpcClient {
this.process = ptree.spawn(["bun", cliPath, ...args], {
cwd: this.options.cwd,
env: { ...process.env, ...this.options.env },
env: { ...Bun.env, ...this.options.env },
stdin: "pipe",
});
@@ -1053,15 +1053,15 @@ type ColorMode = "truecolor" | "256color";
// ============================================================================
function detectColorMode(): ColorMode {
const colorterm = process.env.COLORTERM;
const colorterm = Bun.env.COLORTERM;
if (colorterm === "truecolor" || colorterm === "24bit") {
return "truecolor";
}
// Windows Terminal supports truecolor
if (process.env.WT_SESSION) {
if (Bun.env.WT_SESSION) {
return "truecolor";
}
const term = process.env.TERM || "";
const term = Bun.env.TERM || "";
// Only fall back to 256color for truly limited terminals
if (term === "dumb" || term === "" || term === "linux") {
return "256color";
@@ -1706,7 +1706,7 @@ export async function getThemeByName(name: string): Promise<Theme | undefined> {
}
function detectTerminalBackground(): "dark" | "light" {
const colorfgbg = process.env.COLORFGBG || "";
const colorfgbg = Bun.env.COLORFGBG || "";
if (colorfgbg) {
const parts = colorfgbg.split(";");
if (parts.length >= 2) {
+1 -1
View File
@@ -218,7 +218,7 @@ export class EditTool implements AgentTool<TInput> {
PI_EDIT_FUZZY: editFuzzy = "auto",
PI_EDIT_FUZZY_THRESHOLD: editFuzzyThreshold = "auto",
PI_EDIT_VARIANT: envEditVariant = "auto",
} = process.env;
} = Bun.env;
this.envEditVariant = envEditVariant;
if (envEditVariant !== "replace" && envEditVariant !== "patch" && envEditVariant !== "auto") {
+3 -3
View File
@@ -17,7 +17,7 @@
* // Full control
* const session = await createAgentSession({
* model: myModel,
* getApiKey: async () => process.env.MY_KEY,
* getApiKey: async () => Bun.env.MY_KEY,
* toolNames: ["read", "bash", "edit", "write"], // Filter tools
* extensions: [],
* skills: [],
@@ -566,7 +566,7 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
* // Full control
* const { session } = await createAgentSession({
* model: myModel,
* getApiKey: async () => process.env.MY_KEY,
* getApiKey: async () => Bun.env.MY_KEY,
* systemPrompt: 'You are helpful.',
* tools: codingTools({ cwd: process.cwd() }),
* skills: [],
@@ -814,7 +814,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
// If we extracted Exa API keys from MCP configs and EXA_API_KEY isn't set, use the first one
if (mcpResult.exaApiKeys.length > 0 && !$env.EXA_API_KEY) {
process.env.EXA_API_KEY = mcpResult.exaApiKeys[0];
Bun.env.EXA_API_KEY = mcpResult.exaApiKeys[0];
}
// Log MCP errors
+11 -11
View File
@@ -193,15 +193,15 @@ async function getGpuModel(): Promise<string | null> {
}
function getTerminalName(): string {
const termProgram = process.env.TERM_PROGRAM;
const termProgramVersion = process.env.TERM_PROGRAM_VERSION;
const termProgram = Bun.env.TERM_PROGRAM;
const termProgramVersion = Bun.env.TERM_PROGRAM_VERSION;
if (termProgram) {
return termProgramVersion ? `${termProgram} ${termProgramVersion}` : termProgram;
}
if (process.env.WT_SESSION) return "Windows Terminal";
if (Bun.env.WT_SESSION) return "Windows Terminal";
const term = firstNonEmpty(process.env.TERM, process.env.COLORTERM, process.env.TERMINAL_EMULATOR);
const term = firstNonEmpty(Bun.env.TERM, Bun.env.COLORTERM, Bun.env.TERMINAL_EMULATOR);
return term ?? "unknown";
}
@@ -216,12 +216,12 @@ function normalizeDesktopValue(value: string): string {
}
function getDesktopEnvironment(): string {
if (process.env.KDE_FULL_SESSION === "true") return "KDE";
if (Bun.env.KDE_FULL_SESSION === "true") return "KDE";
const raw = firstNonEmpty(
process.env.XDG_CURRENT_DESKTOP,
process.env.DESKTOP_SESSION,
process.env.XDG_SESSION_DESKTOP,
process.env.GDMSESSION,
Bun.env.XDG_CURRENT_DESKTOP,
Bun.env.DESKTOP_SESSION,
Bun.env.XDG_SESSION_DESKTOP,
Bun.env.GDMSESSION,
);
return raw ? normalizeDesktopValue(raw) : "unknown";
}
@@ -252,10 +252,10 @@ function matchKnownWindowManager(value: string): string | null {
}
function getWindowManager(): string {
const explicit = firstNonEmpty(process.env.WINDOWMANAGER);
const explicit = firstNonEmpty(Bun.env.WINDOWMANAGER);
if (explicit) return explicit;
const desktop = firstNonEmpty(process.env.XDG_CURRENT_DESKTOP, process.env.DESKTOP_SESSION);
const desktop = firstNonEmpty(Bun.env.XDG_CURRENT_DESKTOP, Bun.env.DESKTOP_SESSION);
if (desktop) {
const matched = matchKnownWindowManager(desktop);
if (matched) return matched;
+1 -1
View File
@@ -239,7 +239,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
!skipPythonPreflight &&
pythonMode !== "bash-only" &&
(requestedTools === undefined || requestedTools.includes("python"));
const isTestEnv = process.env.BUN_ENV === "test" || process.env.NODE_ENV === "test";
const isTestEnv = Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
const skipPythonWarm = isTestEnv || $env.PI_PYTHON_SKIP_CHECK === "1";
if (shouldCheckPython) {
const availability = await checkPythonKernelAvailability(session.cwd);
@@ -161,7 +161,7 @@ describe("executeBash", () => {
if (process.platform === "win32") {
return;
}
const bashPath = process.env.SHELL?.includes("bash") ? process.env.SHELL : "/bin/bash";
const bashPath = Bun.env.SHELL?.includes("bash") ? Bun.env.SHELL : "/bin/bash";
if (!fs.existsSync(bashPath)) {
return;
}
@@ -171,8 +171,8 @@ describe("executeBash", () => {
shell: bashPath,
args: ["-l", "-c"],
env: {
PATH: process.env.PATH ?? "",
HOME: process.env.HOME ?? tempDir,
PATH: Bun.env.PATH ?? "",
HOME: Bun.env.HOME ?? tempDir,
},
prefix: undefined,
});
@@ -11,7 +11,7 @@ interface KernelStub {
describe("executePython (per-call)", () => {
it("shuts down kernel on timed-out cancellation", async () => {
process.env.PI_PYTHON_SKIP_CHECK = "1";
Bun.env.PI_PYTHON_SKIP_CHECK = "1";
using tempDir = TempDir.createSync("@omp-python-executor-per-call-");
let shutdownCalls = 0;
@@ -6,7 +6,7 @@ import {
PythonKernel,
} from "@oh-my-pi/pi-coding-agent/ipy/kernel";
process.env.PI_PYTHON_SKIP_CHECK = "1";
Bun.env.PI_PYTHON_SKIP_CHECK = "1";
class FakeKernel {
private result: KernelExecuteResult;
@@ -160,8 +160,8 @@ afterEach(async () => {
describe("warmPythonEnvironment", () => {
it("caches prelude docs on warmup", async () => {
const previousSkip = process.env.PI_PYTHON_SKIP_CHECK;
process.env.PI_PYTHON_SKIP_CHECK = "1";
const previousSkip = Bun.env.PI_PYTHON_SKIP_CHECK;
Bun.env.PI_PYTHON_SKIP_CHECK = "1";
using tempDir = TempDir.createSync("@python-executor-");
const docs: PreludeHelper[] = [
{
@@ -188,9 +188,9 @@ describe("warmPythonEnvironment", () => {
startSpy.mockRestore();
if (previousSkip === undefined) {
delete process.env.PI_PYTHON_SKIP_CHECK;
delete Bun.env.PI_PYTHON_SKIP_CHECK;
} else {
process.env.PI_PYTHON_SKIP_CHECK = previousSkip;
Bun.env.PI_PYTHON_SKIP_CHECK = previousSkip;
}
});
});
@@ -40,7 +40,7 @@ describe("executePython kernel reuse", () => {
let kernels: FakeKernel[] = [];
beforeEach(() => {
process.env.PI_PYTHON_SKIP_CHECK = "1";
Bun.env.PI_PYTHON_SKIP_CHECK = "1";
startCalls = 0;
kernels = [];
PythonKernel.start = (async () => {
@@ -80,9 +80,9 @@ describe("PythonKernel gateway lifecycle", () => {
const originalSleep = Bun.sleep;
const originalWhich = Bun.which;
const originalExecute = PythonKernel.prototype.execute;
const originalGatewayUrl = process.env.PI_PYTHON_GATEWAY_URL;
const originalGatewayToken = process.env.PI_PYTHON_GATEWAY_TOKEN;
const originalBunEnv = process.env.BUN_ENV;
const originalGatewayUrl = Bun.env.PI_PYTHON_GATEWAY_URL;
const originalGatewayToken = Bun.env.PI_PYTHON_GATEWAY_TOKEN;
const originalBunEnv = Bun.env.BUN_ENV;
let tempDir: TempDir;
let env: MockEnvironment;
@@ -91,9 +91,9 @@ describe("PythonKernel gateway lifecycle", () => {
tempDir = TempDir.createSync("@omp-python-kernel-");
env = { fetchCalls: [], spawnCalls: [] };
process.env.BUN_ENV = "test";
delete process.env.PI_PYTHON_GATEWAY_URL;
delete process.env.PI_PYTHON_GATEWAY_TOKEN;
Bun.env.BUN_ENV = "test";
delete Bun.env.PI_PYTHON_GATEWAY_URL;
delete Bun.env.PI_PYTHON_GATEWAY_TOKEN;
FakeWebSocket.instances = [];
globalThis.WebSocket = FakeWebSocket as unknown as typeof WebSocket;
@@ -125,19 +125,19 @@ describe("PythonKernel gateway lifecycle", () => {
}
if (originalBunEnv === undefined) {
delete process.env.BUN_ENV;
delete Bun.env.BUN_ENV;
} else {
process.env.BUN_ENV = originalBunEnv;
Bun.env.BUN_ENV = originalBunEnv;
}
if (originalGatewayUrl === undefined) {
delete process.env.PI_PYTHON_GATEWAY_URL;
delete Bun.env.PI_PYTHON_GATEWAY_URL;
} else {
process.env.PI_PYTHON_GATEWAY_URL = originalGatewayUrl;
Bun.env.PI_PYTHON_GATEWAY_URL = originalGatewayUrl;
}
if (originalGatewayToken === undefined) {
delete process.env.PI_PYTHON_GATEWAY_TOKEN;
delete Bun.env.PI_PYTHON_GATEWAY_TOKEN;
} else {
process.env.PI_PYTHON_GATEWAY_TOKEN = originalGatewayToken;
Bun.env.PI_PYTHON_GATEWAY_TOKEN = originalGatewayToken;
}
globalThis.fetch = originalFetch;
@@ -145,24 +145,24 @@ class FakeWebSocket {
}
describe("PythonKernel (external gateway)", () => {
const originalEnv = { ...process.env };
const originalEnv = { ...Bun.env };
const originalFetch = globalThis.fetch;
const originalWebSocket = globalThis.WebSocket;
beforeEach(() => {
process.env.PI_PYTHON_GATEWAY_URL = "http://gateway.test";
process.env.PI_PYTHON_SKIP_CHECK = "1";
Bun.env.PI_PYTHON_GATEWAY_URL = "http://gateway.test";
Bun.env.PI_PYTHON_SKIP_CHECK = "1";
globalThis.WebSocket = FakeWebSocket as unknown as typeof WebSocket;
});
afterEach(() => {
for (const key of Object.keys(process.env)) {
for (const key of Object.keys(Bun.env)) {
if (!(key in originalEnv)) {
delete process.env[key];
delete Bun.env[key];
}
}
for (const [key, value] of Object.entries(originalEnv)) {
process.env[key] = value;
Bun.env[key] = value;
}
globalThis.fetch = originalFetch;
globalThis.WebSocket = originalWebSocket;
@@ -6,7 +6,7 @@ import { resetPreludeDocsCache, warmPythonEnvironment } from "@oh-my-pi/pi-codin
import { getPythonToolDescription, PythonTool } from "@oh-my-pi/pi-coding-agent/tools/python";
const resolvePythonPath = (): string | null => {
const venvPath = process.env.VIRTUAL_ENV;
const venvPath = Bun.env.VIRTUAL_ENV;
const candidates = [venvPath, path.join(process.cwd(), ".venv"), path.join(process.cwd(), "venv")].filter(
Boolean,
) as string[];
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@ import { ProcessTerminal, TUI } from "@oh-my-pi/pi-tui";
import { sleep } from "bun";
// Initialize dark theme with full color support
process.env.COLORTERM = "truecolor";
Bun.env.COLORTERM = "truecolor";
initTheme("dark");
async function main() {
@@ -1,7 +1,7 @@
import { initTheme, theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
// Initialize with dark theme explicitly
process.env.COLORTERM = "truecolor";
Bun.env.COLORTERM = "truecolor";
initTheme("dark");
console.log("\n=== Foreground Colors ===\n");
@@ -68,8 +68,8 @@ console.log("Direct #00d7ff test: \x1b[38;2;0;215;255mBRIGHT CYAN\x1b[0m");
console.log("Theme cyan (should match above):", theme.fg("accent", "BRIGHT CYAN"));
console.log("\n=== Environment ===\n");
console.log("TERM:", process.env.TERM);
console.log("COLORTERM:", process.env.COLORTERM);
console.log("TERM:", Bun.env.TERM);
console.log("COLORTERM:", Bun.env.COLORTERM);
console.log("Color mode:", theme.getColorMode());
console.log("\n");
+9 -9
View File
@@ -46,8 +46,8 @@ describe("Coding Agent Tools", () => {
beforeEach(() => {
// Force replace mode for edit tool tests using old_text/new_text
originalEditVariant = process.env.PI_EDIT_VARIANT;
process.env.PI_EDIT_VARIANT = "replace";
originalEditVariant = Bun.env.PI_EDIT_VARIANT;
Bun.env.PI_EDIT_VARIANT = "replace";
// Create a unique temporary directory for each test
testDir = path.join(os.tmpdir(), `coding-agent-test-${Snowflake.next()}`);
@@ -69,9 +69,9 @@ describe("Coding Agent Tools", () => {
// Restore original edit variant
if (originalEditVariant === undefined) {
delete process.env.PI_EDIT_VARIANT;
delete Bun.env.PI_EDIT_VARIANT;
} else {
process.env.PI_EDIT_VARIANT = originalEditVariant;
Bun.env.PI_EDIT_VARIANT = originalEditVariant;
}
});
@@ -419,7 +419,7 @@ function b() {
});
it("should persist environment variables between commands", async () => {
if (process.platform === "win32" || process.env.PI_SHELL_PERSIST !== "1") {
if (process.platform === "win32" || Bun.env.PI_SHELL_PERSIST !== "1") {
return;
}
@@ -575,8 +575,8 @@ describe("edit tool CRLF handling", () => {
beforeEach(() => {
// Force replace mode for edit tool tests using old_text/new_text
originalEditVariant = process.env.PI_EDIT_VARIANT;
process.env.PI_EDIT_VARIANT = "replace";
originalEditVariant = Bun.env.PI_EDIT_VARIANT;
Bun.env.PI_EDIT_VARIANT = "replace";
testDir = path.join(os.tmpdir(), `coding-agent-crlf-test-${Snowflake.next()}`);
fs.mkdirSync(testDir, { recursive: true });
@@ -588,9 +588,9 @@ describe("edit tool CRLF handling", () => {
// Restore original edit variant
if (originalEditVariant === undefined) {
delete process.env.PI_EDIT_VARIANT;
delete Bun.env.PI_EDIT_VARIANT;
} else {
process.env.PI_EDIT_VARIANT = originalEditVariant;
Bun.env.PI_EDIT_VARIANT = originalEditVariant;
}
});
@@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test";
import { type SettingPath, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { createTools, HIDDEN_TOOLS, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
process.env.PI_PYTHON_SKIP_CHECK = "1";
Bun.env.PI_PYTHON_SKIP_CHECK = "1";
function createTestSession(overrides: Partial<ToolSession> = {}): ToolSession {
return {
@@ -19,8 +19,8 @@ function createSession(overrides: Partial<ToolSession> = {}): ToolSession {
describe("createTools python fallback", () => {
it("falls back to bash when python is requested but disabled", async () => {
const previous = process.env.PI_PYTHON_SKIP_CHECK;
process.env.PI_PYTHON_SKIP_CHECK = "1";
const previous = Bun.env.PI_PYTHON_SKIP_CHECK;
Bun.env.PI_PYTHON_SKIP_CHECK = "1";
const session = createSession();
const tools = await createTools(session, ["python"]);
const names = tools.map(tool => tool.name).sort();
@@ -28,9 +28,9 @@ describe("createTools python fallback", () => {
expect(names).toEqual(["bash", "exit_plan_mode"]);
if (previous === undefined) {
delete process.env.PI_PYTHON_SKIP_CHECK;
delete Bun.env.PI_PYTHON_SKIP_CHECK;
} else {
process.env.PI_PYTHON_SKIP_CHECK = previous;
Bun.env.PI_PYTHON_SKIP_CHECK = previous;
}
});
});
@@ -9,16 +9,16 @@ let previousSkipCheck: string | undefined;
let tempDir: TempDir;
beforeAll(() => {
tempDir = TempDir.createSync("@omp-python-test-");
previousSkipCheck = process.env.PI_PYTHON_SKIP_CHECK;
process.env.PI_PYTHON_SKIP_CHECK = "1";
previousSkipCheck = Bun.env.PI_PYTHON_SKIP_CHECK;
Bun.env.PI_PYTHON_SKIP_CHECK = "1";
});
afterAll(() => {
if (previousSkipCheck === undefined) {
delete process.env.PI_PYTHON_SKIP_CHECK;
delete Bun.env.PI_PYTHON_SKIP_CHECK;
return;
}
process.env.PI_PYTHON_SKIP_CHECK = previousSkipCheck;
Bun.env.PI_PYTHON_SKIP_CHECK = previousSkipCheck;
tempDir.removeSync();
});
@@ -4,7 +4,7 @@ import { handleIacr } from "@oh-my-pi/pi-coding-agent/web/scrapers/iacr";
import { handlePubMed } from "@oh-my-pi/pi-coding-agent/web/scrapers/pubmed";
import { handleSemanticScholar } from "@oh-my-pi/pi-coding-agent/web/scrapers/semantic-scholar";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleSemanticScholar", () => {
it("returns null for non-S2 URLs", async () => {
@@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test";
import { handleOpenCorporates } from "@oh-my-pi/pi-coding-agent/web/scrapers/opencorporates";
import { handleSecEdgar } from "@oh-my-pi/pi-coding-agent/web/scrapers/sec-edgar";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleSecEdgar", () => {
it("returns null for non-matching URLs", async () => {
@@ -4,7 +4,7 @@ import { handleGitLab } from "@oh-my-pi/pi-coding-agent/web/scrapers/gitlab";
import { handleHackerNews } from "@oh-my-pi/pi-coding-agent/web/scrapers/hackernews";
import { handleLobsters } from "@oh-my-pi/pi-coding-agent/web/scrapers/lobsters";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
// =============================================================================
// HackerNews Tests
@@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test";
import { handleMDN } from "@oh-my-pi/pi-coding-agent/web/scrapers/mdn";
import { handleReadTheDocs } from "@oh-my-pi/pi-coding-agent/web/scrapers/readthedocs";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleMDN", () => {
it("returns null for non-MDN URLs", async () => {
@@ -3,7 +3,7 @@ import { handleArtifactHub } from "@oh-my-pi/pi-coding-agent/web/scrapers/artifa
import { handleCoinGecko } from "@oh-my-pi/pi-coding-agent/web/scrapers/coingecko";
import { handleDiscogs } from "@oh-my-pi/pi-coding-agent/web/scrapers/discogs";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleCoinGecko", () => {
it("returns null for non-CoinGecko URLs", async () => {
@@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test";
import { handleGitHub } from "@oh-my-pi/pi-coding-agent/web/scrapers/github";
import { handleGitHubGist } from "@oh-my-pi/pi-coding-agent/web/scrapers/github-gist";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
// =============================================================================
// GitHub Tests
@@ -3,7 +3,7 @@ import { handleHuggingFace } from "@oh-my-pi/pi-coding-agent/web/scrapers/huggin
import { handleSpotify } from "@oh-my-pi/pi-coding-agent/web/scrapers/spotify";
import { handleVimeo } from "@oh-my-pi/pi-coding-agent/web/scrapers/vimeo";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleVimeo", () => {
it("returns null for non-Vimeo URLs", async () => {
@@ -6,7 +6,7 @@ import { handleMetaCPAN } from "@oh-my-pi/pi-coding-agent/web/scrapers/metacpan"
import { handleRepology } from "@oh-my-pi/pi-coding-agent/web/scrapers/repology";
import { handleTerraform } from "@oh-my-pi/pi-coding-agent/web/scrapers/terraform";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleMetaCPAN", () => {
it("returns null for non-MetaCPAN URLs", async () => {
@@ -6,7 +6,7 @@ import { handleNuGet } from "@oh-my-pi/pi-coding-agent/web/scrapers/nuget";
import { handlePackagist } from "@oh-my-pi/pi-coding-agent/web/scrapers/packagist";
import { handleRubyGems } from "@oh-my-pi/pi-coding-agent/web/scrapers/rubygems";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleBrew", () => {
it("returns null for non-Homebrew URLs", async () => {
@@ -6,7 +6,7 @@ import { handleNpm } from "@oh-my-pi/pi-coding-agent/web/scrapers/npm";
import { handlePubDev } from "@oh-my-pi/pi-coding-agent/web/scrapers/pub-dev";
import { handlePyPI } from "@oh-my-pi/pi-coding-agent/web/scrapers/pypi";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handlePyPI", () => {
it("returns null for non-PyPI URLs", async () => {
@@ -3,7 +3,7 @@ import { handleBiorxiv } from "@oh-my-pi/pi-coding-agent/web/scrapers/biorxiv";
import { handleOpenLibrary } from "@oh-my-pi/pi-coding-agent/web/scrapers/openlibrary";
import { handleWikidata } from "@oh-my-pi/pi-coding-agent/web/scrapers/wikidata";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleWikidata", () => {
it("returns null for non-matching URLs", async () => {
@@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test";
import { handleNvd } from "@oh-my-pi/pi-coding-agent/web/scrapers/nvd";
import { handleOsv } from "@oh-my-pi/pi-coding-agent/web/scrapers/osv";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleNvd", () => {
it("returns null for non-NVD URLs", async () => {
@@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test";
import { handleBluesky } from "@oh-my-pi/pi-coding-agent/web/scrapers/bluesky";
import { handleMastodon } from "@oh-my-pi/pi-coding-agent/web/scrapers/mastodon";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleMastodon", () => {
it("returns null for non-Mastodon URLs", async () => {
@@ -3,7 +3,7 @@ import { handleReddit } from "@oh-my-pi/pi-coding-agent/web/scrapers/reddit";
import { handleStackOverflow } from "@oh-my-pi/pi-coding-agent/web/scrapers/stackoverflow";
import { handleTwitter } from "@oh-my-pi/pi-coding-agent/web/scrapers/twitter";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleTwitter", () => {
it("returns null for non-Twitter URLs", async () => {
@@ -1,7 +1,7 @@
import { describe, expect, it } from "bun:test";
import { handleStackOverflow } from "@oh-my-pi/pi-coding-agent/web/scrapers/stackoverflow";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleStackOverflow", () => {
it("returns null for non-SE URLs", async () => {
@@ -3,7 +3,7 @@ import { handleCheatSh } from "@oh-my-pi/pi-coding-agent/web/scrapers/cheatsh";
import { handleRfc } from "@oh-my-pi/pi-coding-agent/web/scrapers/rfc";
import { handleTldr } from "@oh-my-pi/pi-coding-agent/web/scrapers/tldr";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleRfc", () => {
it("returns null for non-RFC URLs", async () => {
@@ -1,7 +1,7 @@
import { describe, expect, it } from "bun:test";
import { handleWikipedia } from "@oh-my-pi/pi-coding-agent/web/scrapers/wikipedia";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleWikipedia", () => {
it("returns null for non-Wikipedia URLs", async () => {
@@ -1,7 +1,7 @@
import { describe, expect, it } from "bun:test";
import { handleYouTube } from "@oh-my-pi/pi-coding-agent/web/scrapers/youtube";
const SKIP = !process.env.WEB_FETCH_INTEGRATION;
const SKIP = !Bun.env.WEB_FETCH_INTEGRATION;
describe.skipIf(SKIP)("handleYouTube", () => {
it("returns null for non-YouTube URLs", async () => {
+4 -4
View File
@@ -7,9 +7,9 @@ const rustDir = path.join(repoRoot, "crates/pi-natives");
const nativeDir = path.join(import.meta.dir, "../native");
const isDev = process.argv.includes("--dev");
const crossTarget = process.env.CROSS_TARGET;
const targetPlatform = process.env.TARGET_PLATFORM || process.platform;
const targetArch = process.env.TARGET_ARCH || process.arch;
const crossTarget = Bun.env.CROSS_TARGET;
const targetPlatform = Bun.env.TARGET_PLATFORM || process.platform;
const targetArch = Bun.env.TARGET_ARCH || process.arch;
const isCrossCompile =
Boolean(crossTarget) ||
targetPlatform !== process.platform ||
@@ -79,7 +79,7 @@ if (buildResult.exitCode !== 0) {
const profile = isDev ? "debug" : "release";
const targetRoots = [
process.env.CARGO_TARGET_DIR ? path.resolve(process.env.CARGO_TARGET_DIR) : undefined,
Bun.env.CARGO_TARGET_DIR ? path.resolve(Bun.env.CARGO_TARGET_DIR) : undefined,
path.join(repoRoot, "target"),
path.join(rustDir, "target"),
].filter((v): v is string => Boolean(v));
+2 -2
View File
@@ -12,8 +12,8 @@ if (reset) {
process.exit(0);
}
const targetPlatform = process.env.TARGET_PLATFORM || process.platform;
const targetArch = process.env.TARGET_ARCH || process.arch;
const targetPlatform = Bun.env.TARGET_PLATFORM || process.platform;
const targetArch = Bun.env.TARGET_ARCH || process.arch;
const platformTag = `${targetPlatform}-${targetArch}`;
const addonFilename = `pi_natives.${platformTag}.node`;
+1 -1
View File
@@ -40,7 +40,7 @@ const versionedDir = path.join(os.homedir(), ".omp", "natives", packageVersion);
const versionedAddonPath = path.join(versionedDir, addonFilename);
const legacyUserDataDir =
process.platform === "win32"
? path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "omp")
? path.join(Bun.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "omp")
: path.join(os.homedir(), ".local", "bin");
const downloadUrl = `https://github.com/can1357/oh-my-pi/releases/latest/download/${addonFilename}`;
const isCompiledBinary =
+1 -1
View File
@@ -75,7 +75,7 @@ export const TERMINAL_ID: TerminalId = (() => {
TERM_PROGRAM,
TERM,
COLORTERM,
} = process.env;
} = Bun.env;
if (KITTY_WINDOW_ID) return "kitty";
if (GHOSTTY_RESOURCES_DIR) return "ghostty";
+4 -4
View File
@@ -50,17 +50,17 @@ const projectEnv = parseEnvFile(path.join(process.cwd(), ".env"));
for (const file of [projectEnv, homeEnv]) {
for (const [key, value] of Object.entries(file)) {
if (!process.env[key]) {
process.env[key] = value;
if (!Bun.env[key]) {
Bun.env[key] = value;
}
}
}
/**
* Intentional re-export of process.env.
* Intentional re-export of Bun.env.
*
* All users should import this env module (import { $env } from "@oh-my-pi/pi-utils")
* before using environment variables. This ensures that .env files have been loaded and
* overrides (project, home) have been applied, so $env always reflects the correct values.
*/
export const $env: Record<string, string> = process.env as Record<string, string>;
export const $env: Record<string, string> = Bun.env as Record<string, string>;
+4 -4
View File
@@ -34,7 +34,7 @@ function isExecutable(path: string): boolean {
function buildSpawnEnv(shell: string): Record<string, string> {
const noCI = $env.PI_BASH_NO_CI || $env.CLAUDE_BASH_NO_CI;
return {
...process.env,
...Bun.env,
SHELL: shell,
GIT_EDITOR: "true",
GPG_TTY: "not a tty",
@@ -135,11 +135,11 @@ export function getShellConfig(customShellPath?: string): ShellConfig {
if (process.platform === "win32") {
// 2. Try Git Bash in known locations
const paths: string[] = [];
const programFiles = process.env.ProgramFiles;
const programFiles = Bun.env.ProgramFiles;
if (programFiles) {
paths.push(`${programFiles}\\Git\\bin\\bash.exe`);
}
const programFilesX86 = process.env["ProgramFiles(x86)"];
const programFilesX86 = Bun.env["ProgramFiles(x86)"];
if (programFilesX86) {
paths.push(`${programFilesX86}\\Git\\bin\\bash.exe`);
}
@@ -168,7 +168,7 @@ export function getShellConfig(customShellPath?: string): ShellConfig {
}
// Unix: prefer user's shell from $SHELL if it's bash/zsh and executable
const userShell = process.env.SHELL;
const userShell = Bun.env.SHELL;
const isValidShell = userShell && (userShell.includes("bash") || userShell.includes("zsh"));
if (isValidShell && isExecutable(userShell)) {
cachedShellConfig = buildConfig(userShell);
+1 -1
View File
@@ -77,7 +77,7 @@ async function spawnBatch(count: number, basePort: number, timeout: number): Pro
stdout: "pipe",
stderr: "pipe",
stdin: "pipe",
env: { ...process.env, NO_COLOR: "1", PI_DEBUG_STARTUP: "1" },
env: { ...Bun.env, NO_COLOR: "1", PI_DEBUG_STARTUP: "1" },
});
instances.push({ proc, port, stdout: "", stderr: "", status: "pending" });
}