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:
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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,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,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";
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import "./utils/migrate-env";
|
||||
|
||||
export * from "./models";
|
||||
export * from "./providers/anthropic";
|
||||
export * from "./providers/azure-openai-responses";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -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 ?? {}) };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(/\/+$/, "");
|
||||
}
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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" } }),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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")`
|
||||
|
||||
@@ -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`)
|
||||
: () => {};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 +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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`)
|
||||
: () => {};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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" });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user