refactor(env): migrated environment variables from OMP_ to PI_ prefix and centralized access via getEnv()

- Migrated environment variable access from direct process.env to centralized getEnv() utility function across all packages.
- Renamed environment variable prefix from OMP_ to PI_ throughout codebase (e.g., OMP_CODING_AGENT_DIR -> PI_CODING_AGENT_DIR).
- Removed automatic environment variable migration from PI_ to OMP_ prefixes via migrate-env.ts module.
- Removed env setting from configuration schema and applyEnvironmentVariables() method from settings.
- Updated CI/CD build configuration to use PI_COMPILED flag instead of OMP_COMPILED.
- Changed venvPath property in PythonRuntime from nullable (string | null) to optional (string | undefined).
This commit is contained in:
can1357
2026-02-05 02:55:01 +01:00
parent 2c34b641cd
commit ec801665bc
83 changed files with 400 additions and 352 deletions
+1 -1
View File
@@ -166,7 +166,7 @@ jobs:
run: |
mkdir -p packages/coding-agent/binaries
ENTRYPOINT="./packages/coding-agent/src/cli.ts"
DEFINES="--define OMP_COMPILED=true --root ."
DEFINES="--define PI_COMPILED=true --root ."
build_binary() {
PLATFORM="$1"
ARCH="$2"
+1 -1
View File
@@ -181,7 +181,7 @@ Configure different models for different purposes with automatic discovery:
- **Three roles**: `default` (main model), `smol` (fast/cheap), `slow` (comprehensive reasoning)
- **Auto-discovery**: Smol finds haiku → flash → mini; Slow finds codex → gpt → opus → pro
- **Role-based selection**: Task tool agents can use `model: pi/smol` for cost-effective exploration
- CLI args (`--smol`, `--slow`) and env vars (`OMP_SMOL_MODEL`, `OMP_SLOW_MODEL`)
- CLI args (`--smol`, `--slow`) and env vars (`PI_SMOL_MODEL`, `PI_SLOW_MODEL`)
- Configure via `/model` selector with keybindings (Enter=default, S=smol, L=slow)
## + Todo Tool (Task Tracking)
+1
View File
@@ -154,6 +154,7 @@
"version": "10.6.2",
"dependencies": {
"@oh-my-pi/pi-natives": "workspace:*",
"@oh-my-pi/pi-utils": "workspace:*",
"@types/mime-types": "^3.0.1",
"chalk": "^5.6.2",
"marked": "^17.0.1",
+23 -10
View File
@@ -17,11 +17,13 @@ Avoid ports that depend on JS-only state or dynamic imports. N-API exports shoul
## Anatomy of a native export
**Rust side:**
- Implementation lives in `crates/pi-natives/src/<module>.rs`. If you add a new module, register it in `crates/pi-natives/src/lib.rs`.
- Export with `#[napi]` and `#[napi(js_name = "...")]` to keep JS-facing camelCase names. Use `#[napi(object)]` for structs.
- Use `task::blocking(tag, cancel_token, work)` (see `crates/pi-natives/src/task.rs`) for CPU-bound or blocking work. Use `task::future(env, tag, work)` for async work that needs Tokio (e.g., shell sessions). Pass a `CancelToken` when you expose `timeoutMs` or `AbortSignal`.
**JS side:**
- `packages/natives/src/bindings.ts` holds the base `NativeBindings` interface.
- `packages/natives/src/<module>/types.ts` defines TS types and augments `NativeBindings` via declaration merging.
- `packages/natives/src/native.ts` imports each `<module>/types.ts` file to activate the declarations.
@@ -31,43 +33,51 @@ Avoid ports that depend on JS-only state or dynamic imports. N-API exports shoul
## Porting checklist
1) **Add the Rust implementation**
1. **Add the Rust implementation**
- Put the core logic in a plain Rust function.
- If it’s a new module, add it to `crates/pi-natives/src/lib.rs`.
- Expose it with `#[napi(js_name = "...")]` to keep camelCase names stable.
- Keep signatures owned and simple: `String`, `Vec<String>`, `Uint8Array`, or `Either<JsString, Uint8Array>` for large string/byte inputs.
- For CPU-bound or blocking work, use `task::blocking`; for async work, use `task::future`. Pass a `CancelToken` and call `heartbeat()` inside long loops.
2) **Wire JS bindings**
2. **Wire JS bindings**
- Add the types and `NativeBindings` augmentation in `packages/natives/src/<module>/types.ts`.
- Import `./<module>/types` in `packages/natives/src/native.ts` to trigger declaration merging.
- Add a wrapper in `packages/natives/src/<module>/index.ts` that calls `native`.
- Re-export from `packages/natives/src/index.ts`.
3) **Update native validation**
3. **Update native validation**
- Add `checkFn("newExport")` in `validateNative` (`packages/natives/src/native.ts`).
4) **Add benchmarks**
4. **Add benchmarks**
- Put benchmarks next to the owning package (`packages/tui/bench`, `packages/natives/bench`, or `packages/coding-agent/bench`).
- Include a JS baseline and native version in the same run.
- Use `performance.now()` and a fixed iteration count.
- Keep the benchmark inputs small and realistic (actual data seen in the hot path).
5) **Build the native binary**
- `bun --cwd=packages/natives run build:native`
- Use `bun --cwd=packages/natives run dev:native` for debug builds (`pi_natives.dev.node`) and set `OMP_DEV=1` when loading it.
5. **Build the native binary**
- `bun --cwd=packages/natives run build:native`
- Use `bun --cwd=packages/natives run dev:native` for debug builds (`pi_natives.dev.node`) and set `PI_DEV=1` when loading it.
6. **Run the benchmark**
6) **Run the benchmark**
- `bun run packages/<pkg>/bench/<bench>.ts` (or `bun --cwd=packages/natives run bench`)
7) **Decide on usage**
7. **Decide on usage**
- If native is slower, **keep JS** and leave the native export unused.
- If native is faster, switch call sites to the native wrapper.
## Pain points and how to avoid them
### 1) Stale `pi_natives.node` prevents new exports
The loader prefers the platform-tagged binary in `packages/natives/native` (`pi_natives.<platform>-<arch>.node`). When `OMP_DEV=1`, it will load `pi_natives.dev.node` instead. There is also a fallback `pi_natives.node`. Compiled binaries extract to `~/.omp/natives/<version>/pi_natives.<platform>-<arch>.node`. If any of these are stale, exports won’t update.
The loader prefers the platform-tagged binary in `packages/natives/native` (`pi_natives.<platform>-<arch>.node`). When `PI_DEV=1`, it will load `pi_natives.dev.node` instead. There is also a fallback `pi_natives.node`. Compiled binaries extract to `~/.omp/natives/<version>/pi_natives.<platform>-<arch>.node`. If any of these are stale, exports won’t update.
**Fix:** remove the stale file before rebuilding.
@@ -90,6 +100,7 @@ bun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`
```
### 2) “Missing exports” errors from `validateNative`
This is **good** — it prevents silent mismatches. When you see this:
```
@@ -99,9 +110,11 @@ Native addon missing exports ... Missing: visibleWidth
it means your binary is stale, the Rust `#[napi(js_name = "...")]` doesn’t match the JS name, or the export never compiled in. Fix the build and the naming mismatch, don’t weaken validation.
### 3) Rust signature mismatch
Keep it simple and owned. `String`, `Vec<String>`, and `Uint8Array` work. Avoid references like `&str` in public exports. If you need structured data, wrap it in `#[napi(object)]` structs.
### 4) Benchmarking mistakes
- Don’t compare different inputs or allocations.
- Keep JS and native using identical input arrays.
- Run both in the same benchmark file to avoid skew.
+8
View File
@@ -1,6 +1,14 @@
# Changelog
## [Unreleased]
### Changed
- Replaced direct `process.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
- Removed automatic environment variable migration from `PI_*` to `OMP_*` prefixes via `migrate-env.ts` module
## [10.5.0] - 2026-02-04
### Changed
+1 -1
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env bun
import { join } from "node:path";
import { getEnv } from "@oh-my-pi/pi-utils";
import type { Api, KnownProvider, Model } from "../src/types";
const packageRoot = join(import.meta.dir, "..");
@@ -191,7 +192,6 @@ interface KimiModelInfo {
async function fetchKimiCodeModels(): Promise<Model<"openai-completions">[]> {
// Kimi Code /models endpoint requires authentication
// Use KIMI_API_KEY env var if available, otherwise return fallback models
const apiKey = process.env.KIMI_API_KEY;
if (apiKey) {
try {
-1
View File
@@ -1,7 +1,6 @@
#!/usr/bin/env bun
import { createInterface } from "readline";
import { CliAuthStorage } from "./storage";
import "./utils/migrate-env";
import { getOAuthProviders } from "./utils/oauth";
import { loginAnthropic } from "./utils/oauth/anthropic";
import { loginCursor } from "./utils/oauth/cursor";
-2
View File
@@ -1,5 +1,3 @@
import "./utils/migrate-env";
export * from "./models";
export * from "./providers/anthropic";
export * from "./providers/azure-openai-responses";
+2 -1
View File
@@ -18,6 +18,7 @@ import {
type ToolConfiguration,
ToolResultStatus,
} from "@aws-sdk/client-bedrock-runtime";
import { getEnv } from "@oh-my-pi/pi-utils";
import { calculateCost } from "../models";
import type {
Api,
@@ -97,7 +98,7 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
// in Node.js/Bun environment only
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
config.region = config.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
config.region = config.region || getEnv("AWS_REGION") || getEnv("AWS_DEFAULT_REGION");
}
config.region = config.region || "us-east-1";
+5 -4
View File
@@ -3,6 +3,7 @@ import * as fs from "node:fs/promises";
import http2 from "node:http2";
import { create, fromBinary, fromJson, type JsonValue, toBinary, toJson } from "@bufbuild/protobuf";
import { ValueSchema } from "@bufbuild/protobuf/wkt";
import { getEnv } from "@oh-my-pi/pi-utils";
import { calculateCost } from "../models";
import type {
Api,
@@ -138,7 +139,7 @@ interface CursorLogEntry {
}
async function appendCursorDebugLog(entry: CursorLogEntry): Promise<void> {
const logPath = process.env.DEBUG_CURSOR_LOG;
const logPath = getEnv("DEBUG_CURSOR_LOG");
if (!logPath) return;
try {
await fs.appendFile(logPath, `${JSON.stringify(entry, debugReplacer)}\n`);
@@ -148,10 +149,10 @@ async function appendCursorDebugLog(entry: CursorLogEntry): Promise<void> {
}
function log(type: string, subtype?: string, data?: unknown): void {
if (!process.env.DEBUG_CURSOR) return;
if (!getEnv("DEBUG_CURSOR")) return;
const normalizedData = data ? decodeLogData(data) : data;
const entry: CursorLogEntry = { ts: Date.now(), type, subtype, data: normalizedData };
const verbose = process.env.DEBUG_CURSOR === "2" || process.env.DEBUG_CURSOR === "verbose";
const verbose = getEnv("DEBUG_CURSOR") === "2" || getEnv("DEBUG_CURSOR") === "verbose";
const dataStr = verbose && normalizedData ? ` ${JSON.stringify(normalizedData, debugReplacer)?.slice(0, 500)}` : "";
console.error(`[CURSOR] ${type}${subtype ? `: ${subtype}` : ""}${dataStr}`);
void appendCursorDebugLog(entry);
@@ -2065,7 +2066,7 @@ function buildGrpcRequest(
const toolNames = context.tools?.map(tool => tool.name) ?? [];
const detail =
process.env.DEBUG_CURSOR === "2"
getEnv("DEBUG_CURSOR") === "2"
? ` ${JSON.stringify(clientMessage.message.value, debugReplacer, 2)?.slice(0, 2000)}`
: "";
log("info", "builtRunRequest", {
+3 -2
View File
@@ -6,6 +6,7 @@ import {
ThinkingLevel,
} from "@google/genai";
import { calculateCost } from "../models";
import { getEnv } from "@oh-my-pi/pi-utils";
import type {
Api,
AssistantMessage,
@@ -304,7 +305,7 @@ function createClient(model: Model<"google-vertex">, project: string, location:
}
function resolveProject(options?: GoogleVertexOptions): string {
const project = options?.project || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
const project = options?.project || getEnv("GOOGLE_CLOUD_PROJECT") || getEnv("GCLOUD_PROJECT");
if (!project) {
throw new Error(
"Vertex AI requires a project ID. Set GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT or pass project in options.",
@@ -314,7 +315,7 @@ function resolveProject(options?: GoogleVertexOptions): string {
}
function resolveLocation(options?: GoogleVertexOptions): string {
const location = options?.location || process.env.GOOGLE_CLOUD_LOCATION;
const location = options?.location || getEnv("GOOGLE_CLOUD_LOCATION");
if (!location) {
throw new Error("Vertex AI requires a location. Set GOOGLE_CLOUD_LOCATION or pass location in options.");
}
@@ -1,5 +1,5 @@
import * as os from "node:os";
import { abortableSleep } from "@oh-my-pi/pi-utils";
import { abortableSleep, getEnv } from "@oh-my-pi/pi-utils";
import type {
ResponseFunctionToolCall,
ResponseInput,
@@ -71,7 +71,7 @@ export function buildCodexSystemPrompt(args: { userSystemPrompt?: string }): Cod
};
}
const CODEX_DEBUG = process.env.OMP_CODEX_DEBUG === "1" || process.env.OMP_CODEX_DEBUG === "true";
const CODEX_DEBUG = getEnv("PI_CODEX_DEBUG") === "1" || getEnv("PI_CODEX_DEBUG") === "true";
const CODEX_MAX_RETRIES = 2;
const CODEX_RETRYABLE_STATUS = new Set([408, 429, 500, 502, 503, 504]);
const CODEX_RETRY_DELAY_MS = 500;
@@ -10,6 +10,7 @@ import type {
} from "openai/resources/chat/completions";
import { calculateCost } from "../models";
import { getEnvApiKey } from "../stream";
import { getEnv } from "@oh-my-pi/pi-utils";
import type {
AssistantMessage,
Context,
@@ -348,12 +349,12 @@ async function createClient(
extraHeaders?: Record<string, string>,
) {
if (!apiKey) {
if (!process.env.OPENAI_API_KEY) {
if (!getEnv("OPENAI_API_KEY")) {
throw new Error(
"OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass it as an argument.",
);
}
apiKey = process.env.OPENAI_API_KEY;
apiKey = getEnv("OPENAI_API_KEY");
}
let headers = { ...(model.headers ?? {}), ...(extraHeaders ?? {}) };
@@ -357,12 +357,12 @@ function createClient(
extraHeaders?: Record<string, string>,
) {
if (!apiKey) {
if (!process.env.OPENAI_API_KEY) {
if (!getEnv("OPENAI_API_KEY")) {
throw new Error(
"OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass it as an argument.",
);
}
apiKey = process.env.OPENAI_API_KEY;
apiKey = getEnv("OPENAI_API_KEY");
}
const headers = { ...(model.headers ?? {}), ...(extraHeaders ?? {}) };
+2 -1
View File
@@ -7,6 +7,7 @@ import { Database } from "bun:sqlite";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { getEnv } from "@oh-my-pi/pi-utils";
import type { OAuthCredentials } from "./utils/oauth/types";
type AuthCredential = { type: "api_key"; key: string } | ({ type: "oauth" } & OAuthCredentials);
@@ -24,7 +25,7 @@ type AuthRow = {
* Get the agent config directory (e.g., ~/.omp/agent/)
*/
function getAgentDir(): string {
const configDir = process.env.OMP_CODING_AGENT_DIR || path.join(os.homedir(), ".omp", "agent");
const configDir = getEnv("PI_CODING_AGENT_DIR") || path.join(os.homedir(), ".omp", "agent");
return configDir;
}
+2 -1
View File
@@ -10,6 +10,7 @@ import type {
UsageWindow,
} from "../usage";
import { getKimiCommonHeaders, refreshKimiToken } from "../utils/oauth/kimi";
import { getEnv } from "@oh-my-pi/pi-utils";
const DEFAULT_BASE_URL = "https://api.kimi.com/coding/v1";
const USAGE_PATH = "usages";
@@ -45,7 +46,7 @@ const toNumber = (value: unknown): number | undefined => {
};
function normalizeBaseUrl(baseUrl?: string): string {
const envBase = process.env.KIMI_CODE_BASE_URL?.trim();
const envBase = getEnv("KIMI_CODE_BASE_URL")?.trim();
const candidate = baseUrl?.trim() || envBase || DEFAULT_BASE_URL;
return candidate.replace(/\/+$/, "");
}
-8
View File
@@ -1,8 +0,0 @@
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith("PI_") && value !== undefined) {
const ompKey = `OMP_${key.slice(3)}`; // PI_FOO -> OMP_FOO
if (process.env[ompKey] === undefined) {
process.env[ompKey] = value;
}
}
}
@@ -5,6 +5,7 @@
import { OAuthCallbackFlow } from "./callback-server";
import { generatePKCE } from "./pkce";
import type { OAuthController, OAuthCredentials } from "./types";
import { getEnv } from "@oh-my-pi/pi-utils";
const decode = (s: string) => atob(s);
const CLIENT_ID = decode(
@@ -91,7 +92,7 @@ async function pollOperation(
}
async function discoverProject(accessToken: string, onProgress?: (message: string) => void): Promise<string> {
const envProjectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID;
const envProjectId = getEnv("GOOGLE_CLOUD_PROJECT") || getEnv("GOOGLE_CLOUD_PROJECT_ID");
const headers = {
Authorization: `Bearer ${accessToken}`,
+3 -3
View File
@@ -6,7 +6,7 @@ import * as crypto from "node:crypto";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { abortableSleep, isEnoent } from "@oh-my-pi/pi-utils";
import { abortableSleep, getEnv, isEnoent } from "@oh-my-pi/pi-utils";
import packageJson from "../../../package.json" with { type: "json" };
import type { OAuthController, OAuthCredentials } from "./types";
@@ -37,12 +37,12 @@ interface TokenResponse {
}
function getAgentDir(): string {
const configDir = process.env.OMP_CODING_AGENT_DIR || path.join(os.homedir(), ".omp", "agent");
const configDir = getEnv("PI_CODING_AGENT_DIR") || path.join(os.homedir(), ".omp", "agent");
return configDir;
}
function resolveOAuthHost(): string {
return process.env.KIMI_CODE_OAUTH_HOST || process.env.KIMI_OAUTH_HOST || DEFAULT_OAUTH_HOST;
return getEnv("KIMI_CODE_OAUTH_HOST") || getEnv("KIMI_OAUTH_HOST") || DEFAULT_OAUTH_HOST;
}
function formatDeviceModel(system: string, release: string, arch: string): string {
+2 -2
View File
@@ -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.OMP_NO_LOCAL_LLM) {
if (!process.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.OMP_NO_LOCAL_LLM) {
if (!process.env.PI_NO_LOCAL_LLM) {
try {
execSync("curl -s --max-time 1 http://localhost:1234/v1/models > /dev/null", { stdio: "ignore" });
lmStudioRunning = true;
+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.OMP_CODING_AGENT_DIR;
const originalAgentDir = process.env.PI_CODING_AGENT_DIR;
afterEach(() => {
global.fetch = originalFetch;
if (originalAgentDir === undefined) {
delete process.env.OMP_CODING_AGENT_DIR;
delete process.env.PI_CODING_AGENT_DIR;
} else {
process.env.OMP_CODING_AGENT_DIR = originalAgentDir;
process.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.OMP_CODING_AGENT_DIR = tempDir.path();
process.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.OMP_CODING_AGENT_DIR = tempDir.path();
process.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.OMP_CODING_AGENT_DIR = tempDir.path();
process.env.PI_CODING_AGENT_DIR = tempDir.path();
const payload = Buffer.from(
JSON.stringify({ "https://api.openai.com/auth": { chatgpt_account_id: "acc_test" } }),
+1 -1
View File
@@ -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.OMP_NO_LOCAL_LLM) {
if (!process.env.PI_NO_LOCAL_LLM) {
try {
execSync("which ollama", { stdio: "ignore" });
ollamaInstalled = true;
+4
View File
@@ -1,6 +1,7 @@
# Changelog
## [Unreleased]
### Added
- Added `task.maxRecursionDepth` setting to control how many levels deep subagents can spawn their own subagents (0=none, 1=one level, 2=two levels, -1=unlimited)
@@ -12,6 +13,9 @@
### Changed
- Renamed environment variable prefix from `OMP_` to `PI_` throughout codebase (e.g., `OMP_DEBUG_STARTUP` → `PI_DEBUG_STARTUP`, `OMP_PYTHON_GATEWAY_URL` → `PI_PYTHON_GATEWAY_URL`)
- Removed `env` setting from configuration schema; environment variables are no longer automatically applied from settings
- Changed `venvPath` property in PythonRuntime from nullable to optional (returns `undefined` instead of `null`)
- Simplified notification settings from protocol-specific options (bell, osc99, osc9) to simple on/off toggle for `completion.notify` and `ask.notify`
- Moved notification protocol detection and sending to `TERMINAL` API from local utility functions
- Changed task tool spawns configuration from "explore" to "*" to allow subagents to spawn any agent type
+80 -79
View File
@@ -59,13 +59,13 @@ npm install -g @oh-my-pi/pi-coding-agent
Download from [GitHub Releases](https://github.com/can1357/oh-my-pi/releases):
| Platform | Binary |
| ------------------- | ------------------------ |
| macOS Apple Silicon | `omp-darwin-arm64` |
| macOS Intel | `omp-darwin-x64` |
| Linux x64 | `omp-linux-x64` |
| Linux ARM64 | `omp-linux-arm64` |
| Windows x64 | `omp-windows-x64.exe` |
| Platform | Binary |
| ------------------- | --------------------- |
| macOS Apple Silicon | `omp-darwin-arm64` |
| macOS Intel | `omp-darwin-x64` |
| Linux x64 | `omp-linux-x64` |
| Linux ARM64 | `omp-linux-arm64` |
| Windows x64 | `omp-windows-x64.exe` |
```bash
# macOS/Linux
@@ -201,24 +201,24 @@ The agent reads, writes, and edits files, and executes commands via bash.
### Slash Commands
| Command | Description |
| ------------------------- | --------------------------------------------------------------------------- |
| `/settings` | Open settings menu (thinking, theme, queue mode, toggles) |
| Command | Description |
| ------------------------- | ----------------------------------------------------------------------------------------------- |
| `/settings` | Open settings menu (thinking, theme, queue mode, toggles) |
| `/model` | Switch models mid-session. Use `/model <search>` or `provider/model` to prefilter/disambiguate. |
| `/export [file]` | Export session to HTML file |
| `/dump` | Copy session transcript to clipboard |
| `/share` | Upload session as secret GitHub gist, get shareable URL (requires `gh` CLI) |
| `/session` | Show session info: path, message counts, token usage, cost |
| `/hotkeys` | Show all keyboard shortcuts |
| `/changelog` | Display full version history |
| `/tree` | Navigate session tree in-place (search, filter, label entries) |
| `/branch` | Create new conversation branch from a previous message |
| `/resume` | Switch to a different session (interactive selector) |
| `/login` | OAuth login for subscription-based models |
| `/logout` | Clear OAuth tokens |
| `/new` | Start a new session |
| `/copy` | Copy last agent message to clipboard |
| `/compact [instructions]` | Manually compact conversation context |
| `/export [file]` | Export session to HTML file |
| `/dump` | Copy session transcript to clipboard |
| `/share` | Upload session as secret GitHub gist, get shareable URL (requires `gh` CLI) |
| `/session` | Show session info: path, message counts, token usage, cost |
| `/hotkeys` | Show all keyboard shortcuts |
| `/changelog` | Display full version history |
| `/tree` | Navigate session tree in-place (search, filter, label entries) |
| `/branch` | Create new conversation branch from a previous message |
| `/resume` | Switch to a different session (interactive selector) |
| `/login` | OAuth login for subscription-based models |
| `/logout` | Clear OAuth tokens |
| `/new` | Start a new session |
| `/copy` | Copy last agent message to clipboard |
| `/compact [instructions]` | Manually compact conversation context |
### Editor Features
@@ -245,13 +245,13 @@ The agent reads, writes, and edits files, and executes commands via bash.
**Editing:**
| Key | Action |
| ------------------------- | ---------------------------- |
| Enter | Send message |
| Key | Action |
| ------------------------- | ----------------------------------------- |
| Enter | Send message |
| Shift+Enter | New line (Ctrl+Enter on Windows Terminal) |
| Ctrl+W / Option+Backspace | Delete word backwards |
| Ctrl+U | Delete to start of line |
| Ctrl+K | Delete to end of line |
| Ctrl+W / Option+Backspace | Delete word backwards |
| Ctrl+U | Delete to start of line |
| Ctrl+K | Delete to end of line |
**Other:**
@@ -366,6 +366,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`
- Supports any environment variable, not just API keys
@@ -564,7 +565,7 @@ Global `~/.omp/agent/settings.json` stores persistent preferences:
| Setting | Description | Default |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| `theme` | Color theme name | auto-detected |
| `modelRoles` | Model assignments by role (e.g., `{"default": "...", "slow": "...", "smol": "...", "plan": "..."}`) | - |
| `modelRoles` | Model assignments by role (e.g., `{"default": "...", "slow": "...", "smol": "...", "plan": "..."}`) | - |
| `defaultThinkingLevel` | Thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` | - |
| `enabledModels` | Model patterns for cycling. Supports glob patterns (`github-copilot/*`, `*sonnet*`) and fuzzy matching. Same as `--models` CLI flag | - |
| `queueMode` | Message queue mode: `all` or `one-at-a-time` | `one-at-a-time` |
@@ -824,33 +825,33 @@ omp [options] [@files...] [messages...]
### Options
| Option | Description |
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Option | Description |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `--provider <name>` | Provider: `anthropic`, `openai`, `google`, `mistral`, `xai`, `groq`, `cerebras`, `openrouter`, `zai`, `cursor`, `github-copilot`, `openai-codex`, `google-gemini-cli`, `google-antigravity`, or custom |
| `--model <id>` | Model ID |
| `--api-key <key>` | API key (overrides environment) |
| `--system-prompt <text\|file>` | Custom system prompt (text or file path) |
| `--append-system-prompt <text\|file>` | Append to system prompt |
| `--mode <mode>` | Output mode: `text`, `json`, `rpc` (implies `--print`) |
| `--print`, `-p` | Non-interactive: process prompt and exit |
| `--no-session` | Don't save session |
| `--session <path>` | Use specific session file |
| `--session-dir <dir>` | Directory for session storage and lookup |
| `--continue`, `-c` | Continue most recent session |
| `--resume`, `-r` | Select session to resume |
| `--models <patterns>` | Comma-separated patterns for model role cycling. Supports glob patterns (e.g., `anthropic/*`, `*sonnet*:high`) and fuzzy matching (e.g., `sonnet,haiku:low`) |
| `--no-tools` | Disable all built-in tools |
| `--tools <tools>` | Restrict to comma-separated tool list (default: all tools enabled) |
| `--thinking <level>` | Thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` |
| `--extension <path>`, `-e` | Load an extension file (can be used multiple times) |
| `--no-extensions` | Disable extension discovery (explicit `-e` paths still work) |
| `--no-skills` | Disable skills discovery and loading |
| `--skills <patterns>` | Comma-separated glob patterns to filter skills (e.g., `git-*,docker`) |
| `--no-lsp` | Disable LSP integration |
| `--hook <path>` | Load a hook file (for debugging) |
| `--export <file> [output]` | Export session to HTML |
| `--help`, `-h` | Show help |
| `--version`, `-v` | Show version |
| `--model <id>` | Model ID |
| `--api-key <key>` | API key (overrides environment) |
| `--system-prompt <text\|file>` | Custom system prompt (text or file path) |
| `--append-system-prompt <text\|file>` | Append to system prompt |
| `--mode <mode>` | Output mode: `text`, `json`, `rpc` (implies `--print`) |
| `--print`, `-p` | Non-interactive: process prompt and exit |
| `--no-session` | Don't save session |
| `--session <path>` | Use specific session file |
| `--session-dir <dir>` | Directory for session storage and lookup |
| `--continue`, `-c` | Continue most recent session |
| `--resume`, `-r` | Select session to resume |
| `--models <patterns>` | Comma-separated patterns for model role cycling. Supports glob patterns (e.g., `anthropic/*`, `*sonnet*:high`) and fuzzy matching (e.g., `sonnet,haiku:low`) |
| `--no-tools` | Disable all built-in tools |
| `--tools <tools>` | Restrict to comma-separated tool list (default: all tools enabled) |
| `--thinking <level>` | Thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` |
| `--extension <path>`, `-e` | Load an extension file (can be used multiple times) |
| `--no-extensions` | Disable extension discovery (explicit `-e` paths still work) |
| `--no-skills` | Disable skills discovery and loading |
| `--skills <patterns>` | Comma-separated glob patterns to filter skills (e.g., `git-*,docker`) |
| `--no-lsp` | Disable LSP integration |
| `--hook <path>` | Load a hook file (for debugging) |
| `--export <file> [output]` | Export session to HTML |
| `--help`, `-h` | Show help |
| `--version`, `-v` | Show version |
### File Arguments
@@ -906,11 +907,11 @@ omp --export session.jsonl output.html
### Environment Variables
| Variable | Description |
|----------|-------------|
| Variable | Description |
| ------------------------------------------- | ----------------------------------------------------------------- |
| `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc. | API keys for providers (see [API Keys & OAuth](#api-keys--oauth)) |
| `OMP_CODING_AGENT_DIR` | Override the agent config directory (default: `~/.omp/agent`) |
| `VISUAL`, `EDITOR` | External editor for Ctrl+G (e.g., `vim`, `code --wait`) |
| `PI_CODING_AGENT_DIR` | Override the agent config directory (default: `~/.omp/agent`) |
| `VISUAL`, `EDITOR` | External editor for Ctrl+G (e.g., `vim`, `code --wait`) |
---
@@ -920,27 +921,27 @@ All tools are enabled by default. Use `--tools <list>` to restrict to a subset.
### Core Tools
| Tool | Description |
| ------- | --------------------------------------------------------------------------------------------------------------- |
| `read` | Read file contents. Images sent as attachments. Text: first 2000 lines. Use offset/limit for large files. |
| `write` | Write/overwrite file. Creates parent directories. |
| `edit` | Replace text in file with fuzzy whitespace matching. Fails if text appears multiple times or not found. |
| `bash` | Execute command. Returns stdout/stderr. Optional `timeout` parameter. |
| `grep` | Search file contents (regex or literal). Respects `.gitignore`. |
| `find` | Search for files by glob pattern. Respects `.gitignore`. |
| `ls` | List directory contents. Includes dotfiles. |
| Tool | Description |
| ------- | --------------------------------------------------------------------------------------------------------- |
| `read` | Read file contents. Images sent as attachments. Text: first 2000 lines. Use offset/limit for large files. |
| `write` | Write/overwrite file. Creates parent directories. |
| `edit` | Replace text in file with fuzzy whitespace matching. Fails if text appears multiple times or not found. |
| `bash` | Execute command. Returns stdout/stderr. Optional `timeout` parameter. |
| `grep` | Search file contents (regex or literal). Respects `.gitignore`. |
| `find` | Search for files by glob pattern. Respects `.gitignore`. |
| `ls` | List directory contents. Includes dotfiles. |
### Additional Built-in Tools
| Tool | Description |
| ------------- | ----------------------------------------------------- |
| `task` | Spawn sub-agents for complex multi-step tasks |
| `lsp` | Language Server Protocol queries (go-to-definition, references, hover) |
| `todo_write` | Track task progress during sessions |
| `web_search` | Search the web |
| `fetch` | Fetch and process URLs |
| `python` | Execute Python code in IPython kernel |
| `notebook` | Edit Jupyter notebook cells |
| Tool | Description |
| ------------ | ---------------------------------------------------------------------- |
| `task` | Spawn sub-agents for complex multi-step tasks |
| `lsp` | Language Server Protocol queries (go-to-definition, references, hover) |
| `todo_write` | Track task progress during sessions |
| `web_search` | Search the web |
| `fetch` | Fetch and process URLs |
| `python` | Execute Python code in IPython kernel |
| `notebook` | Edit Jupyter notebook cells |
Example: `--tools read,grep,find,ls` for read-only code review.
@@ -23,7 +23,7 @@ For each root:
Additional extension paths can be provided via settings and CLI:
- **Global settings**: `~/.omp/agent/config.yml` (or `$OMP_CODING_AGENT_DIR/config.yml`)
- **Global settings**: `~/.omp/agent/config.yml` (or `$PI_CODING_AGENT_DIR/config.yml`)
- **Project settings**: `<cwd>/.omp/settings.json`
- **CLI**: `--extension` or `-e`
@@ -39,7 +39,7 @@ extensions:
```json
// .omp/settings.json
{
"extensions": ["./project-extension.ts"]
"extensions": ["./project-extension.ts"]
}
```
@@ -66,10 +66,10 @@ Example `package.json` manifest:
```json
{
"name": "my-extension-pack",
"omp": {
"extensions": ["./src/safety-gates.ts", "./src/custom-tools.ts"]
}
"name": "my-extension-pack",
"omp": {
"extensions": ["./src/safety-gates.ts", "./src/custom-tools.ts"]
}
}
```
+18 -11
View File
@@ -6,6 +6,7 @@
- `jupyter-kernel-gateway` (`kernel_gateway` module) and `ipykernel` installed in the selected Python environment
Install:
```bash
python -m pip install jupyter_kernel_gateway ipykernel
```
@@ -16,6 +17,7 @@ The Python tool uses a Jupyter Kernel Gateway and talks to it over REST and WebS
By default it uses a shared local gateway so multiple pi instances reuse the same gateway process.
Shared-gateway startup flow:
1. Filter the environment and resolve the Python runtime (including venv detection)
2. Acquire the shared gateway (reuse a healthy gateway or spawn `python -m kernel_gateway` on 127.0.0.1:PORT)
3. Wait for gateway readiness (`GET /api/kernelspecs`)
@@ -29,19 +31,21 @@ Instead of spawning a local gateway, you can connect to an already-running Jupyt
```bash
# Connect to external gateway
export OMP_PYTHON_GATEWAY_URL="http://127.0.0.1:8888"
export PI_PYTHON_GATEWAY_URL="http://127.0.0.1:8888"
# Optional: auth token if gateway requires it (KG_AUTH_TOKEN)
export OMP_PYTHON_GATEWAY_TOKEN="your-token-here"
export PI_PYTHON_GATEWAY_TOKEN="your-token-here"
```
When `OMP_PYTHON_GATEWAY_URL` is set:
When `PI_PYTHON_GATEWAY_URL` is set:
- No local gateway process is spawned
- Kernels are created on the external gateway
- The gateway process is not killed on shutdown
- Availability check uses `/api/kernelspecs` endpoint instead of local module check
This is useful for:
- Remote kernel execution
- Shared kernel environments
- Pre-configured gateway setups
@@ -49,7 +53,7 @@ This is useful for:
## Environment Propagation
- The kernel inherits a filtered environment (explicit allowlist + denylist)
- Allowlisted prefixes include `LC_`, `XDG_`, and `OMP_`; known API-key vars are removed
- Allowlisted prefixes include `LC_`, `XDG_`, and `PI_`; known API-key vars are removed
- `PYTHONPATH` is passed through if present
- Virtual environments are detected via `VIRTUAL_ENV`, `.venv/`, or `venv/` and preferred when present
@@ -65,18 +69,21 @@ Project modules override user modules with the same filename.
## Kernel Modes
Settings under `python` control exposure and reuse:
- `toolMode`: `both` (default), `ipy-only`, `bash-only`
- `kernelMode`: `session` (default) or `per-call`
- `sharedGateway`: `true` (default). Setting to `false` throws an error because local (per-process) gateways are not supported; the shared gateway is required.
Mode behavior:
- `session`: reuse kernels per session id, serialize execution, evict after 5 minutes of idle time (max 4 sessions)
- `per-call`: create a fresh kernel per tool call and shut it down afterward
Environment override:
- `OMP_PY=0|bash` → `bash-only`
- `OMP_PY=1|py` → `ipy-only`
- `OMP_PY=mix|both` → `both`
- `PI_PY=0|bash` → `bash-only`
- `PI_PY=1|py` → `ipy-only`
- `PI_PY=mix|both` → `both`
## Shell Helper
@@ -95,9 +102,9 @@ and returns a `ShellResult` with `stdout`, `stderr`, and `code`.
## Troubleshooting
- **Kernel unavailable**: Ensure `python` + `jupyter-kernel-gateway` + `ipykernel` are installed; the session will fall back to bash-only.
- **Python mode override**: Check `python.toolMode` or `OMP_PY` if the Python tool is missing.
- **Python mode override**: Check `python.toolMode` or `PI_PY` if the Python tool is missing.
- **Shared gateway disabled**: `python.sharedGateway=false` causes the Python tool to error because local (per-process) gateways are not supported.
- **Skip preflight checks**: Set `OMP_PYTHON_SKIP_CHECK=1` to bypass kernel availability checks.
- **External gateway unreachable**: Check the URL is correct and the gateway is running. If auth is required, set `OMP_PYTHON_GATEWAY_TOKEN`.
- **IPC tracing**: Set `OMP_PYTHON_IPC_TRACE=1` to log kernel message flow.
- **Skip preflight checks**: Set `PI_PYTHON_SKIP_CHECK=1` to bypass kernel availability checks.
- **External gateway unreachable**: Check the URL is correct and the gateway is running. If auth is required, set `PI_PYTHON_GATEWAY_TOKEN`.
- **IPC tracing**: Set `PI_PYTHON_IPC_TRACE=1` to log kernel message flow.
- **Stdin requests**: Interactive input is not supported; refactor code to avoid `input()` or provide data programmatically.
+9 -3
View File
@@ -8,7 +8,7 @@ Sessions are stored as JSONL (JSON Lines) files. Each line is a JSON object with
~/.omp/agent/sessions/--<cwd>--/<timestamp>_<sessionId>.jsonl
```
Default base directory comes from `getAgentDir()` (overridable via `OMP_CODING_AGENT_DIR`).
Default base directory comes from `getAgentDir()` (overridable via `PI_CODING_AGENT_DIR`).
`<cwd>` is the working directory with the leading slash removed and `/`, `\`, `:` replaced by `-`.
`<timestamp>` is ISO-8601 with `:`/`.` replaced by `-`. `sessionId` is a nanoid.
@@ -49,7 +49,14 @@ interface SessionEntryBase {
First line of the file. Metadata only, not part of the tree (no `id`/`parentId`). `version` is absent in v1 sessions.
```json
{ "type": "session", "version": 3, "id": "nanoid", "timestamp": "2024-12-03T14:00:00.000Z", "cwd": "/path/to/project", "title": "Optional title" }
{
"type": "session",
"version": 3,
"id": "nanoid",
"timestamp": "2024-12-03T14:00:00.000Z",
"cwd": "/path/to/project",
"title": "Optional title"
}
```
For sessions with a parent (created via `/branch`, `newSession({ parentSession })`, or fork operations):
@@ -280,7 +287,6 @@ const text = await Bun.file("session.jsonl").text();
const entries = Bun.JSONL.parse(text);
for (const entry of entries) {
switch (entry.type) {
case "session":
console.log(`Session v${entry.version ?? 1}: ${entry.id}`);
+33 -35
View File
@@ -85,14 +85,14 @@ Used for native syntax highlighting in tool output and editors:
Editor border colors that indicate the current thinking/reasoning level:
| Token | Purpose |
| ----------------- | ----------------------------------------------------------------- |
| `thinkingOff` | Border when thinking is off (most subtle) |
| `thinkingMinimal` | Border for minimal thinking |
| `thinkingLow` | Border for low thinking |
| `thinkingMedium` | Border for medium thinking |
| `thinkingHigh` | Border for high thinking |
| `thinkingXhigh` | Border for xhigh thinking (most prominent) |
| Token | Purpose |
| ----------------- | ------------------------------------------ |
| `thinkingOff` | Border when thinking is off (most subtle) |
| `thinkingMinimal` | Border for minimal thinking |
| `thinkingLow` | Border for low thinking |
| `thinkingMedium` | Border for medium thinking |
| `thinkingHigh` | Border for high thinking |
| `thinkingXhigh` | Border for xhigh thinking (most prominent) |
These create a visual hierarchy: off → minimal → low → medium → high → xhigh
@@ -101,26 +101,26 @@ These create a visual hierarchy: off → minimal → low → medium → high →
| Token | Purpose |
| ------------ | ------------------------------------------------ |
| `bashMode` | Editor border color when in bash mode (! prefix) |
| `pythonMode` | Editor border color when in python mode (>>>) |
| `pythonMode` | Editor border color when in python mode (>>>) |
### Status Line (14 colors)
| Token | Purpose |
| ------------------- | --------------------------------------- |
| `statusLineBg` | Status line background |
| `statusLineSep` | Separators between status line segments |
| `statusLineModel` | Model segment text |
| `statusLinePath` | Working directory segment |
| `statusLineGitClean` | Git segment (clean) |
| `statusLineGitDirty` | Git segment (dirty) |
| `statusLineContext` | Context window usage segment |
| `statusLineSpend` | Token input/total segment |
| `statusLineStaged` | Git staged count |
| `statusLineDirty` | Git unstaged count |
| `statusLineUntracked` | Git untracked count |
| `statusLineOutput` | Token output/cache output segment |
| `statusLineCost` | Cost segment |
| `statusLineSubagents` | Subagent count segment |
| Token | Purpose |
| --------------------- | --------------------------------------- |
| `statusLineBg` | Status line background |
| `statusLineSep` | Separators between status line segments |
| `statusLineModel` | Model segment text |
| `statusLinePath` | Working directory segment |
| `statusLineGitClean` | Git segment (clean) |
| `statusLineGitDirty` | Git segment (dirty) |
| `statusLineContext` | Context window usage segment |
| `statusLineSpend` | Token input/total segment |
| `statusLineStaged` | Git staged count |
| `statusLineDirty` | Git unstaged count |
| `statusLineUntracked` | Git untracked count |
| `statusLineOutput` | Token output/cache output segment |
| `statusLineCost` | Cost segment |
| `statusLineSubagents` | Subagent count segment |
**Total: 66 color tokens** (all required)
@@ -280,20 +280,20 @@ On first run, OMP uses the terminal background reported by `COLORFGBG` and falls
### Theme Locations
Custom themes are loaded from `~/.omp/agent/themes/*.json` by default, or from `$OMP_CODING_AGENT_DIR/themes` if that environment variable is set.
Custom themes are loaded from `~/.omp/agent/themes/*.json` by default, or from `$PI_CODING_AGENT_DIR/themes` if that environment variable is set.
### Creating a Custom Theme
1. **Create theme directory:**
```bash
mkdir -p "${OMP_CODING_AGENT_DIR:-~/.omp/agent}/themes"
mkdir -p "${PI_CODING_AGENT_DIR:-~/.omp/agent}/themes"
```
2. **Create theme file:**
```bash
vim "${OMP_CODING_AGENT_DIR:-~/.omp/agent}/themes/my-theme.json"
vim "${PI_CODING_AGENT_DIR:-~/.omp/agent}/themes/my-theme.json"
```
3. **Define all colors (see the schema for the full list; snippet below shows structure):**
@@ -573,12 +573,9 @@ export async function initTheme(
themeName?: string,
enableWatcher?: boolean,
symbolPreset?: SymbolPreset,
colorBlindMode?: boolean,
colorBlindMode?: boolean
): Promise<void>;
export async function setTheme(
name: string,
enableWatcher?: boolean,
): Promise<{ success: boolean; error?: string }>;
export async function setTheme(name: string, enableWatcher?: boolean): Promise<{ success: boolean; error?: string }>;
// Usage throughout coding-agent
import { theme } from "./theme.js";
@@ -629,7 +626,9 @@ export function getMarkdownTheme(): MarkdownTheme {
strikethrough: (text) => chalk.strikethrough(text),
symbols: getSymbolTheme(),
getMermaidImage,
highlightCode: (code, lang) => { /* uses native syntax highlighter */ },
highlightCode: (code, lang) => {
/* uses native syntax highlighter */
},
};
}
```
@@ -688,7 +687,6 @@ const userMsg = theme.bg("userMessageBg", theme.fg("userMessageText", "Hello"));
```
4. **Convert colors to ANSI codes based on terminal capability:**
- Empty string (`""`) → terminal default (foreground/background reset)
- 256-color (`42`) → `\x1b[38;5;42m` / `\x1b[48;5;42m`
- Hex or resolved vars → `Bun.color(value, "ansi-16m" | "ansi-256")`
+1 -1
View File
@@ -74,7 +74,7 @@
"scripts": {
"check": "tsgo -p tsconfig.json",
"format-prompts": "bun scripts/format-prompts.ts",
"build:binary": "cd ../.. && bun --cwd=packages/natives run embed:native && bun build --compile --define OMP_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp && bun --cwd=packages/natives run embed:native --reset",
"build:binary": "cd ../.. && bun --cwd=packages/natives run embed:native && bun build --compile --define PI_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp && bun --cwd=packages/natives run embed:native --reset",
"generate-template": "bun scripts/generate-template.ts",
"test": "bun test"
},
@@ -8,9 +8,10 @@
*/
import * as os from "node:os";
import * as path from "node:path";
import { getEnv } from "@oh-my-pi/pi-utils";
/** Conditional startup debug prints (stderr) when OMP_DEBUG_STARTUP is set */
const debugStartup = process.env.OMP_DEBUG_STARTUP
/** Conditional startup debug prints (stderr) when PI_DEBUG_STARTUP is set */
const debugStartup = getEnv("PI_DEBUG_STARTUP")
? (stage: string) => process.stderr.write(`[startup] ${stage}\n`)
: () => {};
+4 -4
View File
@@ -195,9 +195,9 @@ ${chalk.bold("Subcommands:")}
${chalk.bold("Options:")}
--model <pattern> Model to use (fuzzy match: "opus", "gpt-5.2", or "p-openai/gpt-5.2")
--smol <id> Smol/fast model for lightweight tasks (or OMP_SMOL_MODEL env)
--slow <id> Slow/reasoning model for thorough analysis (or OMP_SLOW_MODEL env)
--plan <id> Plan model for architectural planning (or OMP_PLAN_MODEL env)
--smol <id> Smol/fast model for lightweight tasks (or PI_SMOL_MODEL env)
--slow <id> Slow/reasoning model for thorough analysis (or PI_SLOW_MODEL env)
--plan <id> Plan model for architectural planning (or PI_PLAN_MODEL env)
--api-key <key> API key (defaults to env vars)
--system-prompt <text> System prompt (default: coding assistant prompt)
--append-system-prompt <text> Append text or file contents to the system prompt
@@ -286,7 +286,7 @@ ${chalk.bold("Environment Variables:")}
PERPLEXITY_API_KEY - Perplexity search API key
${chalk.dim("# Configuration")}
OMP_CODING_AGENT_DIR - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
PI_CODING_AGENT_DIR - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
${chalk.bold("Available Tools (all enabled by default):")}
read - Read file contents
+1 -1
View File
@@ -142,7 +142,7 @@ ${chalk.bold("Options:")}
-h, --help Show this help
${chalk.bold("Environment:")}
OMP_GREP_WORKERS=0 Disable worker pool (use single-threaded mode)
PI_GREP_WORKERS=0 Disable worker pool (use single-threaded mode)
${chalk.bold("Examples:")}
${APP_NAME} grep "import" src/
@@ -1,5 +1,6 @@
import * as path from "node:path";
import { createInterface } from "node:readline/promises";
import { getEnv } from "@oh-my-pi/pi-utils";
import { applyChangelogProposals } from "../../commit/changelog";
import { detectChangelogBoundaries } from "../../commit/changelog/detect";
import { parseUnreleasedSection } from "../../commit/changelog/parse";
@@ -82,7 +83,7 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
} else {
writeStdout(" └─ (none found)");
}
const forceFallback = process.env.OMP_COMMIT_TEST_FALLBACK?.toLowerCase() === "true";
const forceFallback = getEnv("PI_COMMIT_TEST_FALLBACK")?.toLowerCase() === "true";
if (forceFallback) {
writeStdout("● Forcing fallback commit generation...");
const fallbackProposal = generateFallbackProposal(numstat);
@@ -137,7 +138,7 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
writeStderr(`Agent error: ${errorMessage}`);
if (error instanceof Error && error.stack && process.env.DEBUG) {
if (error instanceof Error && error.stack && getEnv("DEBUG")) {
writeStderr(error.stack);
}
writeStdout("● Using fallback commit generation...");
@@ -146,7 +147,7 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
}
if (!usedFallback && !commitState.proposal && !commitState.splitProposal) {
if (process.env.OMP_COMMIT_NO_FALLBACK?.toLowerCase() !== "true") {
if (getEnv("PI_COMMIT_NO_FALLBACK")?.toLowerCase() !== "true") {
writeStdout("● Agent did not provide proposal, using fallback...");
commitState.proposal = generateFallbackProposal(numstat);
usedFallback = true;
@@ -1,4 +1,5 @@
import type { Api, Model } from "@oh-my-pi/pi-ai";
import { getEnv } from "@oh-my-pi/pi-utils";
import { parseFileDiffs } from "../../commit/git/diff";
import type { ConventionalAnalysis } from "../../commit/types";
import { isExcludedFile } from "../../commit/utils/exclusions";
@@ -30,7 +31,7 @@ export interface MapReduceInput {
}
export function shouldUseMapReduce(diff: string, settings?: MapReduceSettings): boolean {
if (process.env.OMP_COMMIT_MAP_REDUCE?.toLowerCase() === "false") return false;
if (getEnv("PI_COMMIT_MAP_REDUCE")?.toLowerCase() === "false") return false;
if (settings?.enabled === false) return false;
const minFiles = settings?.minFiles ?? MIN_FILES_FOR_MAP_REDUCE;
const maxFileTokens = settings?.maxFileTokens ?? MAX_FILE_TOKENS;
+2 -2
View File
@@ -1,7 +1,7 @@
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
import { getEnv, isEnoent, logger } from "@oh-my-pi/pi-utils";
// Embed package.json at build time for config
import packageJson from "../package.json" with { type: "json" };
@@ -53,7 +53,7 @@ export function getChangelogPath(): string {
/** Get the agent config directory (e.g., ~/.omp/agent/) */
export function getAgentDir(): string {
return process.env.OMP_CODING_AGENT_DIR || path.join(os.homedir(), CONFIG_DIR_NAME, "agent");
return getEnv("PI_CODING_AGENT_DIR") || path.join(os.homedir(), CONFIG_DIR_NAME, "agent");
}
/** Get path to user's custom themes directory */
@@ -246,7 +246,6 @@ export const SETTINGS_SCHEMA = {
enabledModels: { type: "array", default: [] as string[] },
disabledProviders: { type: "array", default: [] as string[] },
disabledExtensions: { type: "array", default: [] as string[] },
env: { type: "record", default: {} as Record<string, string> },
modelRoles: { type: "record", default: {} as Record<string, string> },
// ─────────────────────────────────────────────────────────────────────────
@@ -449,10 +449,6 @@ export class Settings {
// Build merged view
this.rebuildMerged();
// Apply environment variables
this.applyEnvironmentVariables();
return this;
}
@@ -550,16 +546,6 @@ export class Settings {
return raw;
}
private applyEnvironmentVariables(): void {
const env = this.get("env") as Record<string, string>;
if (!env) return;
for (const [key, value] of Object.entries(env)) {
if (typeof key === "string" && typeof value === "string" && !(key in process.env)) {
process.env[key] = value;
}
}
}
// ─────────────────────────────────────────────────────────────────────────
// Saving
// ─────────────────────────────────────────────────────────────────────────
+3 -3
View File
@@ -4,7 +4,7 @@
* Client for interacting with Exa MCP servers.
*/
import * as os from "node:os";
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
import { getEnv, isEnoent, logger } from "@oh-my-pi/pi-utils";
import type { TSchema } from "@sinclair/typebox";
import type { CustomTool, CustomToolResult } from "../extensibility/custom-tools/types";
import { callMCP } from "../mcp/json-rpc";
@@ -21,8 +21,8 @@ import type {
/** Find EXA_API_KEY from process.env or .env files */
export async function findApiKey(): Promise<string | null> {
// Check process.env first
if (process.env.EXA_API_KEY) {
return process.env.EXA_API_KEY;
if (getEnv("EXA_API_KEY")) {
return getEnv("EXA_API_KEY");
}
// Try loading from .env files in cwd and home
+5 -5
View File
@@ -1,5 +1,5 @@
import * as path from "node:path";
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
import { getEnv, isEnoent, logger } from "@oh-my-pi/pi-utils";
import { getAgentDir } from "../config";
import { OutputSink } from "../session/streaming-output";
import { time } from "../utils/timings";
@@ -15,7 +15,7 @@ import {
import { discoverPythonModules } from "./modules";
import { PYTHON_PRELUDE } from "./prelude";
const debugStartup = process.env.OMP_DEBUG_STARTUP
const debugStartup = getEnv("PI_DEBUG_STARTUP")
? (stage: string) => process.stderr.write(`[startup] ${stage}\n`)
: () => {};
@@ -327,7 +327,7 @@ async function createKernelSession(
const env: Record<string, string> | undefined =
sessionFile || artifactsDir
? {
...(sessionFile ? { OMP_SESSION_FILE: sessionFile } : {}),
...(sessionFile ? { PI_SESSION_FILE: sessionFile } : {}),
...(artifactsDir ? { ARTIFACTS: artifactsDir } : {}),
}
: undefined;
@@ -384,7 +384,7 @@ async function restartKernelSession(
const env: Record<string, string> | undefined =
sessionFile || artifactsDir
? {
...(sessionFile ? { OMP_SESSION_FILE: sessionFile } : {}),
...(sessionFile ? { PI_SESSION_FILE: sessionFile } : {}),
...(artifactsDir ? { ARTIFACTS: artifactsDir } : {}),
}
: undefined;
@@ -537,7 +537,7 @@ export async function executePython(code: string, options?: PythonExecutorOption
const env: Record<string, string> | undefined =
sessionFile || artifactsDir
? {
...(sessionFile ? { OMP_SESSION_FILE: sessionFile } : {}),
...(sessionFile ? { PI_SESSION_FILE: sessionFile } : {}),
...(artifactsDir ? { ARTIFACTS: artifactsDir } : {}),
}
: undefined;
@@ -243,7 +243,7 @@ async function startGatewayProcess(
const kernelEnv: Record<string, string | undefined> = {
...runtime.env,
PYTHONUNBUFFERED: "1",
OMP_SHELL_SNAPSHOT: snapshotPath ?? undefined,
PI_SHELL_SNAPSHOT: snapshotPath ?? undefined,
};
const gatewayPort = await allocatePort();
+7 -7
View File
@@ -1,4 +1,4 @@
import { logger } from "@oh-my-pi/pi-utils";
import { getEnv, logger } from "@oh-my-pi/pi-utils";
import { $ } from "bun";
import { nanoid } from "nanoid";
import { Settings } from "../config/settings";
@@ -11,10 +11,10 @@ import { filterEnv, resolvePythonRuntime } from "./runtime";
const TEXT_ENCODER = new TextEncoder();
const TEXT_DECODER = new TextDecoder();
const TRACE_IPC = process.env.OMP_PYTHON_IPC_TRACE === "1";
const TRACE_IPC = getEnv("PI_PYTHON_IPC_TRACE") === "1";
const PRELUDE_INTROSPECTION_SNIPPET = "import json\nprint(json.dumps(__omp_prelude_docs__()))";
const debugStartup = process.env.OMP_DEBUG_STARTUP
const debugStartup = getEnv("PI_DEBUG_STARTUP")
? (stage: string) => process.stderr.write(`[startup] ${stage}\n`)
: () => {};
@@ -33,11 +33,11 @@ interface ExternalGatewayConfig {
}
function getExternalGatewayConfig(): ExternalGatewayConfig | null {
const url = process.env.OMP_PYTHON_GATEWAY_URL;
const url = getEnv("PI_PYTHON_GATEWAY_URL");
if (!url) return null;
return {
url: url.replace(/\/$/, ""),
token: process.env.OMP_PYTHON_GATEWAY_TOKEN,
token: getEnv("PI_PYTHON_GATEWAY_TOKEN"),
};
}
@@ -111,7 +111,7 @@ export interface PythonKernelAvailability {
}
export async function checkPythonKernelAvailability(cwd: string): Promise<PythonKernelAvailability> {
if (process.env.BUN_ENV === "test" || process.env.NODE_ENV === "test" || process.env.OMP_PYTHON_SKIP_CHECK === "1") {
if (process.env.BUN_ENV === "test" || process.env.NODE_ENV === "test" || getEnv("PI_PYTHON_SKIP_CHECK") === "1") {
return { ok: true };
}
@@ -163,7 +163,7 @@ async function checkExternalGatewayAvailability(config: ExternalGatewayConfig):
if (response.status === 401 || response.status === 403) {
return {
ok: false,
reason: `External gateway at ${config.url} requires authentication. Set OMP_PYTHON_GATEWAY_TOKEN.`,
reason: `External gateway at ${config.url} requires authentication. Set PI_PYTHON_GATEWAY_TOKEN.`,
};
}
+1 -1
View File
@@ -640,7 +640,7 @@ if "__omp_prelude_loaded__" not in globals():
output('explore_0', offset=10, limit=20) # Lines 10-29
output('explore_0', 'reviewer_1') # Read multiple outputs
"""
session_file = os.environ.get("OMP_SESSION_FILE")
session_file = os.environ.get("PI_SESSION_FILE")
if not session_file:
_emit_status("output", error="No session file available")
raise RuntimeError("No session - output artifacts unavailable")
+7 -6
View File
@@ -7,6 +7,8 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { getEnv } from "@oh-my-pi/pi-utils";
const DEFAULT_ENV_ALLOWLIST = new Set([
"PATH",
"HOME",
@@ -76,7 +78,7 @@ const DEFAULT_ENV_DENYLIST = new Set([
"MISTRAL_API_KEY",
]);
const DEFAULT_ENV_ALLOW_PREFIXES = ["LC_", "XDG_", "OMP_"];
const DEFAULT_ENV_ALLOW_PREFIXES = ["LC_", "XDG_", "PI_"];
const CASE_INSENSITIVE_ENV = process.platform === "win32";
const BASE_ENV_ALLOWLIST = new Set([...DEFAULT_ENV_ALLOWLIST, ...WINDOWS_ENV_ALLOWLIST]);
@@ -107,7 +109,7 @@ export interface PythonRuntime {
/** Filtered environment variables */
env: Record<string, string | undefined>;
/** Path to virtual environment, if detected */
venvPath: string | null;
venvPath?: string;
}
/**
@@ -135,15 +137,15 @@ export function filterEnv(env: Record<string, string | undefined>): Record<strin
/**
* Detect virtual environment path from VIRTUAL_ENV or common locations.
*/
export function resolveVenvPath(cwd: string): string | null {
if (process.env.VIRTUAL_ENV) return process.env.VIRTUAL_ENV;
export function resolveVenvPath(cwd: string): string | undefined {
if (getEnv("VIRTUAL_ENV")) return getEnv("VIRTUAL_ENV");
const candidates = [path.join(cwd, ".venv"), path.join(cwd, "venv")];
for (const candidate of candidates) {
if (fs.existsSync(candidate)) {
return candidate;
}
}
return null;
return undefined;
}
/**
@@ -189,6 +191,5 @@ export function resolvePythonRuntime(cwd: string, baseEnv: Record<string, string
return {
pythonPath: resolveWindowlessPython(pythonPath),
env,
venvPath: null,
};
}
+3 -3
View File
@@ -1,6 +1,6 @@
import * as os from "node:os";
import * as path from "node:path";
import { logger } from "@oh-my-pi/pi-utils";
import { getEnv, logger } from "@oh-my-pi/pi-utils";
import { TOML } from "bun";
/**
@@ -127,7 +127,7 @@ async function checkServerRunning(binaryPath: string): Promise<boolean> {
* Detect lspmux availability and state.
* Results are cached for STATE_CACHE_TTL_MS.
*
* Set OMP_DISABLE_LSPMUX=1 to disable.
* Set PI_DISABLE_LSPMUX=1 to disable.
*/
export async function detectLspmux(): Promise<LspmuxState> {
const now = Date.now();
@@ -135,7 +135,7 @@ export async function detectLspmux(): Promise<LspmuxState> {
return cachedState;
}
if (process.env.OMP_DISABLE_LSPMUX === "1") {
if (getEnv("PI_DISABLE_LSPMUX") === "1") {
cachedState = { available: false, running: false, binaryPath: null, config: null };
cacheTimestamp = now;
return cachedState;
+6 -6
View File
@@ -9,7 +9,7 @@ import * as os from "node:os";
import * as path from "node:path";
import { createInterface } from "node:readline/promises";
import { type ImageContent, supportsXhigh } from "@oh-my-pi/pi-ai";
import { postmortem } from "@oh-my-pi/pi-utils";
import { getEnv, postmortem } from "@oh-my-pi/pi-utils";
import chalk from "chalk";
import { type Args, parseArgs, printHelp } from "./cli/args";
import { parseConfigArgs, printConfigHelp, runConfigCommand } from "./cli/config-cli";
@@ -42,8 +42,8 @@ import { resolvePromptInput } from "./system-prompt";
import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog";
import { printTimings, time } from "./utils/timings";
/** Conditional startup debug prints (stderr) when OMP_DEBUG_STARTUP is set */
const debugStartup = process.env.OMP_DEBUG_STARTUP
/** Conditional startup debug prints (stderr) when PI_DEBUG_STARTUP is set */
const debugStartup = getEnv("PI_DEBUG_STARTUP")
? (stage: string) => process.stderr.write(`[startup] ${stage}\n`)
: () => {};
@@ -655,9 +655,9 @@ export async function main(args: string[]) {
time("initializeWithSettings");
// Apply model role overrides from CLI args or env vars (ephemeral, not persisted)
const smolModel = parsed.smol ?? process.env.OMP_SMOL_MODEL;
const slowModel = parsed.slow ?? process.env.OMP_SLOW_MODEL;
const planModel = parsed.plan ?? process.env.OMP_PLAN_MODEL;
const smolModel = parsed.smol ?? getEnv("PI_SMOL_MODEL");
const slowModel = parsed.slow ?? getEnv("PI_SLOW_MODEL");
const planModel = parsed.plan ?? getEnv("PI_PLAN_MODEL");
if (smolModel || slowModel || planModel) {
const currentRoles = settings.get("modelRoles") as Record<string, string>;
if (smolModel) currentRoles.smol = smolModel;
@@ -7,6 +7,7 @@ import * as os from "node:os";
import * as path from "node:path";
import { Container, Editor, matchesKey, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
import { nanoid } from "nanoid";
import { getEnv } from "@oh-my-pi/pi-utils";
import { getEditorTheme, theme } from "../../modes/theme/theme";
import { DynamicBorder } from "./dynamic-border";
@@ -47,7 +48,7 @@ export class HookEditorComponent extends Container {
this.addChild(new Spacer(1));
// Add hint
const hasExternalEditor = !!(process.env.VISUAL || process.env.EDITOR);
const hasExternalEditor = !!(getEnv("VISUAL") || getEnv("EDITOR"));
const hint = hasExternalEditor
? "ctrl+enter submit esc cancel ctrl+g external editor"
: "ctrl+enter submit esc cancel";
@@ -83,7 +84,7 @@ export class HookEditorComponent extends Container {
}
private async openExternalEditor(): Promise<void> {
const editorCmd = process.env.VISUAL || process.env.EDITOR;
const editorCmd = getEnv("VISUAL") || getEnv("EDITOR");
if (!editorCmd) {
return;
}
@@ -4,6 +4,7 @@ import * as os from "node:os";
import * as path from "node:path";
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import { readImageFromClipboard } from "@oh-my-pi/pi-natives";
import { getEnv } from "@oh-my-pi/pi-utils";
import { nanoid } from "nanoid";
import type { SettingPath, SettingValue } from "../../config/settings";
import { settings } from "../../config/settings";
@@ -460,7 +461,7 @@ export class InputController {
// Generate session title on first message
const hasUserMessages = this.ctx.agent.state.messages.some((m: AgentMessage) => m.role === "user");
if (!hasUserMessages && !this.ctx.sessionManager.getSessionName() && !process.env.OMP_NO_TITLE) {
if (!hasUserMessages && !this.ctx.sessionManager.getSessionName() && !getEnv("PI_NO_TITLE")) {
const registry = this.ctx.session.modelRegistry;
const smolModel = this.ctx.settings.getModelRole("smol");
generateSessionTitle(text, registry, smolModel, this.ctx.session.sessionId)
@@ -731,7 +732,7 @@ export class InputController {
async openExternalEditor(): Promise<void> {
// Determine editor (respect $VISUAL, then $EDITOR)
const editorCmd = process.env.VISUAL || process.env.EDITOR;
const editorCmd = getEnv("VISUAL") || getEnv("EDITOR");
if (!editorCmd) {
this.ctx.showWarning("No editor configured. Set $VISUAL or $EDITOR environment variable.");
return;
@@ -15,7 +15,7 @@ import {
Text,
TUI,
} from "@oh-my-pi/pi-tui";
import { isEnoent, logger, postmortem } from "@oh-my-pi/pi-utils";
import { getEnv, isEnoent, logger, postmortem } from "@oh-my-pi/pi-utils";
import chalk from "chalk";
import { KeybindingsManager } from "../config/keybindings";
import { renderPromptTemplate } from "../config/prompt-templates";
@@ -53,8 +53,8 @@ import { getEditorTheme, getMarkdownTheme, onThemeChange, theme } from "./theme/
import type { CompactionQueuedMessage, InteractiveModeContext, TodoItem } from "./types";
import { UiHelpers } from "./utils/ui-helpers";
/** Conditional startup debug prints (stderr) when OMP_DEBUG_STARTUP is set */
const debugStartup = process.env.OMP_DEBUG_STARTUP
/** Conditional startup debug prints (stderr) when PI_DEBUG_STARTUP is set */
const debugStartup = getEnv("PI_DEBUG_STARTUP")
? (stage: string) => process.stderr.write(`[startup] ${stage}\n`)
: () => {};
+6 -6
View File
@@ -215,14 +215,14 @@ export class EditTool implements AgentTool<TInput> {
this.session = session;
const {
OMP_EDIT_FUZZY: editFuzzy = "auto",
OMP_EDIT_FUZZY_THRESHOLD: editFuzzyThreshold = "auto",
OMP_EDIT_VARIANT: envEditVariant = "auto",
PI_EDIT_FUZZY: editFuzzy = "auto",
PI_EDIT_FUZZY_THRESHOLD: editFuzzyThreshold = "auto",
PI_EDIT_VARIANT: envEditVariant = "auto",
} = process.env;
this.envEditVariant = envEditVariant;
if (envEditVariant !== "replace" && envEditVariant !== "patch" && envEditVariant !== "auto") {
throw new Error(`Invalid OMP_EDIT_VARIANT: ${envEditVariant}`);
throw new Error(`Invalid PI_EDIT_VARIANT: ${envEditVariant}`);
}
switch (editFuzzy) {
@@ -238,7 +238,7 @@ export class EditTool implements AgentTool<TInput> {
this.allowFuzzy = session.settings.get("edit.fuzzyMatch");
break;
default:
throw new Error(`Invalid OMP_EDIT_FUZZY: ${editFuzzy}`);
throw new Error(`Invalid PI_EDIT_FUZZY: ${editFuzzy}`);
}
switch (editFuzzyThreshold) {
case "auto":
@@ -247,7 +247,7 @@ export class EditTool implements AgentTool<TInput> {
default:
this.fuzzyThreshold = parseFloat(editFuzzyThreshold);
if (Number.isNaN(this.fuzzyThreshold) || this.fuzzyThreshold < 0 || this.fuzzyThreshold > 1) {
throw new Error(`Invalid OMP_EDIT_FUZZY_THRESHOLD: ${editFuzzyThreshold}`);
throw new Error(`Invalid PI_EDIT_FUZZY_THRESHOLD: ${editFuzzyThreshold}`);
}
break;
}
+4 -4
View File
@@ -30,7 +30,7 @@ import * as path from "node:path";
import { Agent, type AgentEvent, type AgentMessage, type AgentTool, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
import { type Message, type Model, supportsXhigh } from "@oh-my-pi/pi-ai";
import type { Component } from "@oh-my-pi/pi-tui";
import { logger, postmortem } from "@oh-my-pi/pi-utils";
import { getEnv, logger, postmortem } from "@oh-my-pi/pi-utils";
import { YAML } from "bun";
import chalk from "chalk";
import { loadCapability } from "./capability";
@@ -111,8 +111,8 @@ import { wrapToolsWithMetaNotice } from "./tools/output-meta";
import { EventBus } from "./utils/event-bus";
import { time } from "./utils/timings";
/** Conditional startup debug prints (stderr) when OMP_DEBUG_STARTUP is set */
const debugStartup = process.env.OMP_DEBUG_STARTUP
/** Conditional startup debug prints (stderr) when PI_DEBUG_STARTUP is set */
const debugStartup = getEnv("PI_DEBUG_STARTUP")
? (stage: string) => process.stderr.write(`[startup] ${stage}\n`)
: () => {};
@@ -815,7 +815,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
toolSession.mcpManager = mcpManager;
// 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 && !process.env.EXA_API_KEY) {
if (mcpResult.exaApiKeys.length > 0 && !getEnv("EXA_API_KEY")) {
process.env.EXA_API_KEY = mcpResult.exaApiKeys[0];
}
@@ -306,12 +306,12 @@ async function probeHostInfo(host: SSHConnectionTarget): Promise<SSHHostInfo> {
const hasBash = !unexpandedPosixVars && (Boolean(bashVersion) || shell === "bash");
let compatShell: SSHHostInfo["compatShell"];
if (os === "windows" && host.compat !== false) {
const bashProbe = await runSshCaptureSync(await buildRemoteCommand(host, 'bash -lc "echo OMP_BASH_OK"'));
if (bashProbe.exitCode === 0 && bashProbe.stdout.includes("OMP_BASH_OK")) {
const bashProbe = await runSshCaptureSync(await buildRemoteCommand(host, 'bash -lc "echo PI_BASH_OK"'));
if (bashProbe.exitCode === 0 && bashProbe.stdout.includes("PI_BASH_OK")) {
compatShell = "bash";
} else {
const shProbe = await runSshCaptureSync(await buildRemoteCommand(host, 'sh -lc "echo OMP_SH_OK"'));
if (shProbe.exitCode === 0 && shProbe.stdout.includes("OMP_SH_OK")) {
const shProbe = await runSshCaptureSync(await buildRemoteCommand(host, 'sh -lc "echo PI_SH_OK"'));
if (shProbe.exitCode === 0 && shProbe.stdout.includes("PI_SH_OK")) {
compatShell = "sh";
}
}
+2 -1
View File
@@ -3,6 +3,7 @@
*/
import * as os from "node:os";
import * as path from "node:path";
import { getEnv } from "@oh-my-pi/pi-utils";
import { getSystemInfo as getNativeSystemInfo, type SystemInfo } from "@oh-my-pi/pi-natives";
import { $ } from "bun";
import chalk from "chalk";
@@ -448,7 +449,7 @@ export interface BuildSystemPromptOptions {
/** Build the system prompt with tools, guidelines, and context */
export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<string> {
if (process.env.NULL_PROMPT === "true") {
if (getEnv("NULL_PROMPT") === "true") {
return "";
}
+2 -1
View File
@@ -17,6 +17,7 @@ import * as os from "node:os";
import path from "node:path";
import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
import type { Usage } from "@oh-my-pi/pi-ai";
import { getEnv } from "@oh-my-pi/pi-utils";
import { $ } from "bun";
import { nanoid } from "nanoid";
import type { ToolSession } from "..";
@@ -135,7 +136,7 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
private constructor(session: ToolSession, description: string) {
this.session = session;
this.description = description;
this.blockedAgent = process.env.OMP_BLOCKED_AGENT;
this.blockedAgent = getEnv("PI_BLOCKED_AGENT");
}
/**
@@ -1,5 +1,7 @@
import process from "node:process";
import { getEnv } from "@oh-my-pi/pi-utils";
interface OmpCommand {
cmd: string;
args: string[];
@@ -10,7 +12,7 @@ const DEFAULT_CMD = process.platform === "win32" ? "omp.cmd" : "omp";
const DEFAULT_SHELL = process.platform === "win32";
export function resolveOmpCommand(): OmpCommand {
const envCmd = process.env.OMP_SUBPROCESS_CMD;
const envCmd = getEnv("PI_SUBPROCESS_CMD");
if (envCmd?.trim()) {
return { cmd: envCmd, args: [], shell: DEFAULT_SHELL };
}
+2 -2
View File
@@ -19,10 +19,10 @@ const parseNumber = (value: string | undefined, defaultValue: number): number =>
};
/** Maximum output bytes per agent */
export const MAX_OUTPUT_BYTES = parseNumber(getEnv("OMP_TASK_MAX_OUTPUT_BYTES"), 500_000);
export const MAX_OUTPUT_BYTES = parseNumber(getEnv("PI_TASK_MAX_OUTPUT_BYTES"), 500_000);
/** Maximum output lines per agent */
export const MAX_OUTPUT_LINES = parseNumber(getEnv("OMP_TASK_MAX_OUTPUT_LINES"), 5000);
export const MAX_OUTPUT_LINES = parseNumber(getEnv("PI_TASK_MAX_OUTPUT_LINES"), 5000);
/** EventBus channel for raw subagent events */
export const TASK_SUBAGENT_EVENT_CHANNEL = "task:subagent:event";
+5 -5
View File
@@ -1,5 +1,5 @@
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
import { logger } from "@oh-my-pi/pi-utils";
import { getEnv, logger } from "@oh-my-pi/pi-utils";
import type { PromptTemplate } from "../config/prompt-templates";
import type { Settings } from "../config/settings";
import type { Skill } from "../extensibility/skills";
@@ -194,7 +194,7 @@ export type ToolName = keyof typeof BUILTIN_TOOLS;
export type PythonToolMode = "ipy-only" | "bash-only" | "both";
/**
* Parse OMP_PY environment variable to determine Python tool mode.
* Parse PI_PY environment variable to determine Python tool mode.
* Returns null if not set or invalid.
*
* Values:
@@ -203,7 +203,7 @@ export type PythonToolMode = "ipy-only" | "bash-only" | "both";
* - "mix" or "both" → both
*/
function getPythonModeFromEnv(): PythonToolMode | null {
const value = process.env.OMP_PY?.toLowerCase();
const value = getEnv("PI_PY")?.toLowerCase();
if (!value) return null;
switch (value) {
@@ -240,7 +240,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
pythonMode !== "bash-only" &&
(requestedTools === undefined || requestedTools.includes("python"));
const isTestEnv = process.env.BUN_ENV === "test" || process.env.NODE_ENV === "test";
const skipPythonWarm = isTestEnv || process.env.OMP_PYTHON_SKIP_CHECK === "1";
const skipPythonWarm = isTestEnv || getEnv("PI_PYTHON_SKIP_CHECK") === "1";
if (shouldCheckPython) {
const availability = await checkPythonKernelAvailability(session.cwd);
time("createTools:pythonCheck");
@@ -324,7 +324,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
}),
);
time("createTools:afterFactories");
if (slowTools.length > 0 && process.env.OMP_TIMING === "1") {
if (slowTools.length > 0 && getEnv("PI_TIMING") === "1") {
logger.debug("Tool factory timings", { slowTools });
}
const tools = results.filter(r => r.tool !== null).map(r => r.tool as Tool);
+3 -2
View File
@@ -1,9 +1,10 @@
/**
* Central timing instrumentation for startup profiling.
* Enable with OMP_TIMING=1 or PI_TIMING=1 environment variable.
* Enable with PI_TIMING=1 or PI_TIMING=1 environment variable.
*/
import { getEnv } from "@oh-my-pi/pi-utils";
const ENABLED = process.env.OMP_TIMING === "1";
const ENABLED = getEnv("PI_TIMING") === "1";
const timings: Array<{ label: string; ms: number }> = [];
let lastTime = Date.now();
@@ -1,4 +1,4 @@
import { ptree } from "@oh-my-pi/pi-utils";
import { getEnv, ptree } from "@oh-my-pi/pi-utils";
import type { RenderResult, SpecialHandler } from "./types";
import { finalizeOutput, loadPage } from "./types";
@@ -85,7 +85,7 @@ export async function fetchGitHubApi(
};
// Use GITHUB_TOKEN if available
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
const token = getEnv("GITHUB_TOKEN") || getEnv("GH_TOKEN");
if (token) {
headers.Authorization = `Bearer ${token}`;
}
@@ -40,10 +40,10 @@ describe("executeBash", () => {
});
it("passes env vars", async () => {
const result = await executeBash("echo $OMP_TEST_ENV", {
const result = await executeBash("echo $PI_TEST_ENV", {
cwd: tempDir,
timeout: 5000,
env: { OMP_TEST_ENV: "hello" },
env: { PI_TEST_ENV: "hello" },
});
expect(result.output.trim()).toBe("hello");
});
@@ -166,7 +166,7 @@ describe("executeBash", () => {
return;
}
const snapshotPath = path.join(tempDir, "snapshot.sh");
fs.writeFileSync(snapshotPath, "export OMP_SNAPSHOT_TEST=from_snapshot\n");
fs.writeFileSync(snapshotPath, "export PI_SNAPSHOT_TEST=from_snapshot\n");
vi.spyOn(Settings.prototype, "getShellConfig").mockReturnValue({
shell: bashPath,
args: ["-l", "-c"],
@@ -179,7 +179,7 @@ describe("executeBash", () => {
vi.spyOn(shellSnapshot, "getOrCreateSnapshot").mockResolvedValue(snapshotPath);
const sessionKey = "snapshot-test";
await executeBash("true", { cwd: tempDir, timeout: 5000, sessionKey });
const result = await executeBash("echo $OMP_SNAPSHOT_TEST", { cwd: tempDir, timeout: 5000, sessionKey });
const result = await executeBash("echo $PI_SNAPSHOT_TEST", { cwd: tempDir, timeout: 5000, sessionKey });
expect(result.output.trim()).toBe("from_snapshot");
});
@@ -11,7 +11,7 @@ interface KernelStub {
describe("executePython (per-call)", () => {
it("shuts down kernel on timed-out cancellation", async () => {
process.env.OMP_PYTHON_SKIP_CHECK = "1";
process.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.OMP_PYTHON_SKIP_CHECK = "1";
process.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.OMP_PYTHON_SKIP_CHECK;
process.env.OMP_PYTHON_SKIP_CHECK = "1";
const previousSkip = process.env.PI_PYTHON_SKIP_CHECK;
process.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.OMP_PYTHON_SKIP_CHECK;
delete process.env.PI_PYTHON_SKIP_CHECK;
} else {
process.env.OMP_PYTHON_SKIP_CHECK = previousSkip;
process.env.PI_PYTHON_SKIP_CHECK = previousSkip;
}
});
});
@@ -9,7 +9,7 @@ describe("Python gateway environment filtering", () => {
OPENAI_API_KEY: "secret",
ANTHROPIC_API_KEY: "also-secret",
UNSAFE_TOKEN: "nope",
OMP_CUSTOM: "1",
PI_CUSTOM: "1",
LC_ALL: "en_US.UTF-8",
};
@@ -17,7 +17,7 @@ describe("Python gateway environment filtering", () => {
expect(filtered.PATH).toBe("/bin");
expect(filtered.HOME).toBe("/home/test");
expect(filtered.OMP_CUSTOM).toBe("1");
expect(filtered.PI_CUSTOM).toBe("1");
expect(filtered.LC_ALL).toBe("en_US.UTF-8");
expect(filtered.OPENAI_API_KEY).toBeUndefined();
expect(filtered.ANTHROPIC_API_KEY).toBeUndefined();
@@ -45,12 +45,12 @@ describe("Python gateway environment filtering", () => {
PATH: "/usr/bin",
HOME: "/home/test",
OPENAI_API_KEY: "secret",
OMP_DEBUG: "1",
PI_DEBUG: "1",
};
const filtered = filterEnv(env);
expect(filtered.OPENAI_API_KEY).toBeUndefined();
expect(filtered.PATH).toBe("/usr/bin");
expect(filtered.OMP_DEBUG).toBe("1");
expect(filtered.PI_DEBUG).toBe("1");
});
});
@@ -40,7 +40,7 @@ describe("executePython kernel reuse", () => {
let kernels: FakeKernel[] = [];
beforeEach(() => {
process.env.OMP_PYTHON_SKIP_CHECK = "1";
process.env.PI_PYTHON_SKIP_CHECK = "1";
startCalls = 0;
kernels = [];
PythonKernel.start = (async () => {
@@ -80,8 +80,8 @@ describe("PythonKernel gateway lifecycle", () => {
const originalSleep = Bun.sleep;
const originalWhich = Bun.which;
const originalExecute = PythonKernel.prototype.execute;
const originalGatewayUrl = process.env.OMP_PYTHON_GATEWAY_URL;
const originalGatewayToken = process.env.OMP_PYTHON_GATEWAY_TOKEN;
const originalGatewayUrl = process.env.PI_PYTHON_GATEWAY_URL;
const originalGatewayToken = process.env.PI_PYTHON_GATEWAY_TOKEN;
const originalBunEnv = process.env.BUN_ENV;
let tempDir: TempDir;
@@ -92,8 +92,8 @@ describe("PythonKernel gateway lifecycle", () => {
env = { fetchCalls: [], spawnCalls: [] };
process.env.BUN_ENV = "test";
delete process.env.OMP_PYTHON_GATEWAY_URL;
delete process.env.OMP_PYTHON_GATEWAY_TOKEN;
delete process.env.PI_PYTHON_GATEWAY_URL;
delete process.env.PI_PYTHON_GATEWAY_TOKEN;
FakeWebSocket.instances = [];
globalThis.WebSocket = FakeWebSocket as unknown as typeof WebSocket;
@@ -130,14 +130,14 @@ describe("PythonKernel gateway lifecycle", () => {
process.env.BUN_ENV = originalBunEnv;
}
if (originalGatewayUrl === undefined) {
delete process.env.OMP_PYTHON_GATEWAY_URL;
delete process.env.PI_PYTHON_GATEWAY_URL;
} else {
process.env.OMP_PYTHON_GATEWAY_URL = originalGatewayUrl;
process.env.PI_PYTHON_GATEWAY_URL = originalGatewayUrl;
}
if (originalGatewayToken === undefined) {
delete process.env.OMP_PYTHON_GATEWAY_TOKEN;
delete process.env.PI_PYTHON_GATEWAY_TOKEN;
} else {
process.env.OMP_PYTHON_GATEWAY_TOKEN = originalGatewayToken;
process.env.PI_PYTHON_GATEWAY_TOKEN = originalGatewayToken;
}
globalThis.fetch = originalFetch;
@@ -150,8 +150,8 @@ describe("PythonKernel (external gateway)", () => {
const originalWebSocket = globalThis.WebSocket;
beforeEach(() => {
process.env.OMP_PYTHON_GATEWAY_URL = "http://gateway.test";
process.env.OMP_PYTHON_SKIP_CHECK = "1";
process.env.PI_PYTHON_GATEWAY_URL = "http://gateway.test";
process.env.PI_PYTHON_SKIP_CHECK = "1";
globalThis.WebSocket = FakeWebSocket as unknown as typeof WebSocket;
});
+1 -1
View File
@@ -36,7 +36,7 @@ describe.skipIf(!process.env.ANTHROPIC_API_KEY && !process.env.ANTHROPIC_OAUTH_T
client = new RpcClient({
cliPath: path.join(import.meta.dir, "..", "dist", "cli.js"),
cwd: path.join(import.meta.dir, ".."),
env: { OMP_CODING_AGENT_DIR: sessionDir },
env: { PI_CODING_AGENT_DIR: sessionDir },
provider: "anthropic",
model: "claude-sonnet-4-5",
});
+11 -11
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.OMP_EDIT_VARIANT;
process.env.OMP_EDIT_VARIANT = "replace";
originalEditVariant = process.env.PI_EDIT_VARIANT;
process.env.PI_EDIT_VARIANT = "replace";
// Create a unique temporary directory for each test
testDir = path.join(os.tmpdir(), `coding-agent-test-${nanoid()}`);
@@ -69,9 +69,9 @@ describe("Coding Agent Tools", () => {
// Restore original edit variant
if (originalEditVariant === undefined) {
delete process.env.OMP_EDIT_VARIANT;
delete process.env.PI_EDIT_VARIANT;
} else {
process.env.OMP_EDIT_VARIANT = originalEditVariant;
process.env.PI_EDIT_VARIANT = originalEditVariant;
}
});
@@ -419,12 +419,12 @@ function b() {
});
it("should persist environment variables between commands", async () => {
if (process.platform === "win32" || process.env.OMP_SHELL_PERSIST !== "1") {
if (process.platform === "win32" || process.env.PI_SHELL_PERSIST !== "1") {
return;
}
await bashTool.execute("test-call-8-env-set", { command: "export OMP_TEST_VAR=hello" });
const result = await bashTool.execute("test-call-8-env-get", { command: "echo $OMP_TEST_VAR" });
await bashTool.execute("test-call-8-env-set", { command: "export PI_TEST_VAR=hello" });
const result = await bashTool.execute("test-call-8-env-get", { command: "echo $PI_TEST_VAR" });
expect(getTextOutput(result)).toContain("hello");
});
@@ -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.OMP_EDIT_VARIANT;
process.env.OMP_EDIT_VARIANT = "replace";
originalEditVariant = process.env.PI_EDIT_VARIANT;
process.env.PI_EDIT_VARIANT = "replace";
testDir = path.join(os.tmpdir(), `coding-agent-crlf-test-${nanoid()}`);
fs.mkdirSync(testDir, { recursive: true });
@@ -588,9 +588,9 @@ describe("edit tool CRLF handling", () => {
// Restore original edit variant
if (originalEditVariant === undefined) {
delete process.env.OMP_EDIT_VARIANT;
delete process.env.PI_EDIT_VARIANT;
} else {
process.env.OMP_EDIT_VARIANT = originalEditVariant;
process.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.OMP_PYTHON_SKIP_CHECK = "1";
process.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.OMP_PYTHON_SKIP_CHECK;
process.env.OMP_PYTHON_SKIP_CHECK = "1";
const previous = process.env.PI_PYTHON_SKIP_CHECK;
process.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.OMP_PYTHON_SKIP_CHECK;
delete process.env.PI_PYTHON_SKIP_CHECK;
} else {
process.env.OMP_PYTHON_SKIP_CHECK = previous;
process.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.OMP_PYTHON_SKIP_CHECK;
process.env.OMP_PYTHON_SKIP_CHECK = "1";
previousSkipCheck = process.env.PI_PYTHON_SKIP_CHECK;
process.env.PI_PYTHON_SKIP_CHECK = "1";
});
afterAll(() => {
if (previousSkipCheck === undefined) {
delete process.env.OMP_PYTHON_SKIP_CHECK;
delete process.env.PI_PYTHON_SKIP_CHECK;
return;
}
process.env.OMP_PYTHON_SKIP_CHECK = previousSkipCheck;
process.env.PI_PYTHON_SKIP_CHECK = previousSkipCheck;
tempDir.removeSync();
});
+6 -4
View File
@@ -8,7 +8,9 @@ import * as fs from "node:fs";
import { createRequire } from "node:module";
import * as os from "node:os";
import * as path from "node:path";
import packageJson from "../package.json" with { type: "json" };
import { getEnv } from "@oh-my-pi/pi-utils";
import packageJson from "../package.json";
import type { NativeBindings } from "./bindings";
import { embeddedAddon } from "./embedded-addon";
@@ -64,7 +66,7 @@ const compiledCandidates = [
];
const releaseCandidates = isCompiledBinary ? [...compiledCandidates, ...baseReleaseCandidates] : baseReleaseCandidates;
const candidates = process.env.OMP_DEV ? [...debugCandidates, ...releaseCandidates] : releaseCandidates;
const candidates = getEnv("PI_DEV") ? [...debugCandidates, ...releaseCandidates] : releaseCandidates;
function maybeExtractEmbeddedAddon(errors: string[]): string | null {
if (!isCompiledBinary || !embeddedAddon) return null;
@@ -109,12 +111,12 @@ function loadNative(): NativeBindings {
try {
const bindings = require(candidate) as NativeBindings;
validateNative(bindings, candidate);
if (process.env.OMP_DEV) {
if (getEnv("PI_DEV")) {
console.log(`Loaded native addon from ${candidate}`);
}
return bindings;
} catch (err) {
if (process.env.OMP_DEV) {
if (getEnv("PI_DEV")) {
console.error(`Error loading native addon from ${candidate}:`, err);
}
const message = err instanceof Error ? err.message : String(err);
+8 -8
View File
@@ -196,15 +196,15 @@ async function runSingleTask(
try {
await fs.appendFile(logFile, `{"type":"meta","task":"${task.id}","run":${runIndex},"workDir":"${cwd}"}\n`);
const env: Record<string, string> = { OMP_NO_TITLE: "1" };
const env: Record<string, string> = { PI_NO_TITLE: "1" };
if (config.editVariant !== undefined) {
env.OMP_EDIT_VARIANT = config.editVariant;
env.PI_EDIT_VARIANT = config.editVariant;
}
if (config.editFuzzy !== undefined) {
env.OMP_EDIT_FUZZY = config.editFuzzy === "auto" ? "auto" : config.editFuzzy ? "1" : "0";
env.PI_EDIT_FUZZY = config.editFuzzy === "auto" ? "auto" : config.editFuzzy ? "1" : "0";
}
if (config.editFuzzyThreshold !== undefined) {
env.OMP_EDIT_FUZZY_THRESHOLD =
env.PI_EDIT_FUZZY_THRESHOLD =
config.editFuzzyThreshold === "auto" ? "auto" : String(config.editFuzzyThreshold);
}
@@ -765,15 +765,15 @@ async function runBatch(
await Promise.all(orderedItems.map((item) => copyFixtures(item.task, workDir)));
const env: Record<string, string> = { OMP_NO_TITLE: "1" };
const env: Record<string, string> = { PI_NO_TITLE: "1" };
if (config.editVariant !== undefined) {
env.OMP_EDIT_VARIANT = config.editVariant;
env.PI_EDIT_VARIANT = config.editVariant;
}
if (config.editFuzzy !== undefined) {
env.OMP_EDIT_FUZZY = config.editFuzzy === "auto" ? "auto" : config.editFuzzy ? "1" : "0";
env.PI_EDIT_FUZZY = config.editFuzzy === "auto" ? "auto" : config.editFuzzy ? "1" : "0";
}
if (config.editFuzzyThreshold !== undefined) {
env.OMP_EDIT_FUZZY_THRESHOLD =
env.PI_EDIT_FUZZY_THRESHOLD =
config.editFuzzyThreshold === "auto" ? "auto" : String(config.editFuzzyThreshold);
}
+5
View File
@@ -1,6 +1,7 @@
# Changelog
## [Unreleased]
### Added
- Introduced `terminal-capabilities.ts` module consolidating terminal detection and image protocol support
@@ -11,6 +12,10 @@
### Changed
- Changed notification suppression environment variable from `OMP_NOTIFICATIONS` to `PI_NOTIFICATIONS`
- Changed TUI write log environment variable from `OMP_TUI_WRITE_LOG` to `PI_TUI_WRITE_LOG`
- Changed hardware cursor environment variable from `OMP_HARDWARE_CURSOR` to `PI_HARDWARE_CURSOR`
- Updated environment variable access to use `getEnv()` utility function from `@oh-my-pi/pi-utils` for consistent handling
- Renamed `TERMINAL_INFO` export to `TERMINAL` for clearer API semantics
- Reorganized terminal image exports from `terminal-image` to `terminal-capabilities` module
- Updated all internal references to use `TERMINAL` instead of `TERMINAL_INFO`
+1
View File
@@ -48,6 +48,7 @@
},
"dependencies": {
"@oh-my-pi/pi-natives": "workspace:*",
"@oh-my-pi/pi-utils": "workspace:*",
"@types/mime-types": "^3.0.1",
"chalk": "^5.6.2",
"marked": "^17.0.1",
+1 -1
View File
@@ -42,7 +42,7 @@ export class TerminalInfo {
}
export function isNotificationSuppressed(): boolean {
const value = getEnv("OMP_NOTIFICATIONS");
const value = getEnv("PI_NOTIFICATIONS");
if (!value) return false;
return value === "off" || value === "0" || value === "false";
}
+2 -2
View File
@@ -1,5 +1,5 @@
import * as fs from "node:fs";
import { logger } from "@oh-my-pi/pi-utils";
import { getEnv, logger } from "@oh-my-pi/pi-utils";
import { setKittyProtocolActive } from "./keys";
import { StdinBuffer } from "./stdin-buffer";
@@ -82,7 +82,7 @@ export class ProcessTerminal implements Terminal {
private stdinBuffer?: StdinBuffer;
private stdinDataHandler?: (data: string) => void;
private dead = false;
private writeLogPath = process.env.OMP_TUI_WRITE_LOG || "";
private writeLogPath = getEnv("PI_TUI_WRITE_LOG") || "";
get kittyProtocolActive(): boolean {
return this._kittyProtocolActive;
+2 -1
View File
@@ -4,6 +4,7 @@
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { getEnv } from "@oh-my-pi/pi-utils";
import { isKeyRelease, matchesKey } from "./keys";
import type { Terminal } from "./terminal";
import { setCellDimensions, TERMINAL } from "./terminal-capabilities";
@@ -227,7 +228,7 @@ export class TUI extends Container {
private hardwareCursorRow = 0; // Actual terminal cursor row (may differ due to IME positioning)
private inputBuffer = ""; // Buffer for parsing terminal responses
private cellSizeQueryPending = false;
private showHardwareCursor = process.env.OMP_HARDWARE_CURSOR === "1";
private showHardwareCursor = getEnv("PI_HARDWARE_CURSOR") === "1";
private maxLinesRendered = 0; // Track terminal's working area (max lines ever rendered)
private previousViewportTop = 0; // Track previous viewport top for resize-aware cursor moves
private fullRedrawCount = 0;
+8
View File
@@ -33,6 +33,14 @@ function parseEnvFile(filePath: string): Record<string, string> {
} catch {
// File doesn't exist or can't be read - return empty result
}
// OMP_ overrides PI_
for (const k in result) {
if (k.startsWith("OMP_")) {
result[`PI_${k.slice(4)}`] = result[k];
}
}
return result;
}
+6 -5
View File
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
import path from "node:path";
import * as timers from "node:timers";
import type { Subprocess } from "bun";
import { getEnv } from "./env";
export interface ShellConfig {
shell: string;
@@ -31,7 +32,7 @@ function isExecutable(path: string): boolean {
* Build the spawn environment (cached).
*/
function buildSpawnEnv(shell: string): Record<string, string> {
const noCI = process.env.OMP_BASH_NO_CI || process.env.CLAUDE_BASH_NO_CI;
const noCI = getEnv("PI_BASH_NO_CI") || getEnv("CLAUDE_BASH_NO_CI");
return {
...process.env,
SHELL: shell,
@@ -45,18 +46,18 @@ function buildSpawnEnv(shell: string): Record<string, string> {
/**
* Get shell args, optionally including login shell flag.
* Supports OMP_BASH_NO_LOGIN and CLAUDE_BASH_NO_LOGIN to skip -l.
* Supports PI_BASH_NO_LOGIN and CLAUDE_BASH_NO_LOGIN to skip -l.
*/
function getShellArgs(): string[] {
const noLogin = process.env.OMP_BASH_NO_LOGIN || process.env.CLAUDE_BASH_NO_LOGIN;
return noLogin ? ["-c"] : ["-l", "-c"];
const noLogin = getEnv("PI_BASH_NO_LOGIN") || getEnv("CLAUDE_BASH_NO_LOGIN");
return noLogin ? ["-l", "-c"] : ["-l", "-c"];
}
/**
* Get shell prefix for wrapping commands (profilers, strace, etc.).
*/
function getShellPrefix(): string | undefined {
return process.env.OMP_SHELL_PREFIX || process.env.CLAUDE_CODE_SHELL_PREFIX;
return getEnv("PI_SHELL_PREFIX") || getEnv("CLAUDE_CODE_SHELL_PREFIX");
}
/**
+1 -1
View File
@@ -18,7 +18,7 @@ $ErrorActionPreference = "Stop"
$Repo = "can1357/oh-my-pi"
$Package = "@oh-my-pi/pi-coding-agent"
$InstallDir = if ($env:OMP_INSTALL_DIR) { $env:OMP_INSTALL_DIR } else { "$env:LOCALAPPDATA\omp" }
$InstallDir = if ($env:PI_INSTALL_DIR) { $env:PI_INSTALL_DIR } else { "$env:LOCALAPPDATA\omp" }
$BinaryName = "omp-windows-x64.exe"
$NativeAddonName = "pi_natives.win32-x64.node"
$MinimumBunVersion = "1.3.7"
+1 -1
View File
@@ -12,7 +12,7 @@ set -e
REPO="can1357/oh-my-pi"
PACKAGE="@oh-my-pi/pi-coding-agent"
INSTALL_DIR="${OMP_INSTALL_DIR:-$HOME/.local/bin}"
INSTALL_DIR="${PI_INSTALL_DIR:-$HOME/.local/bin}"
MIN_BUN_VERSION="1.3.7"
# Parse arguments
+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", OMP_DEBUG_STARTUP: "1" },
env: { ...process.env, NO_COLOR: "1", PI_DEBUG_STARTUP: "1" },
});
instances.push({ proc, port, stdout: "", stderr: "", status: "pending" });
}