refactor: implemented $PWD > cwd to prevent macOS symlink resolution
- Replaced all direct `process.cwd()` calls with `getProjectDir()` utility function across 40+ files to centralize project directory resolution logic. - Added `getProjectDir()` and `setProjectDir()` functions to `@oh-my-pi/pi-utils/dirs` module to provide abstracted project directory management. - Made `SessionManager.list()` method asynchronous to support asynchronous session discovery operations. - Updated default working directory resolution throughout codebase to use `getProjectDir()` instead of `process.cwd()` for improved project directory detection.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added real-time authentication validation in OAuth provider selector with visual status indicators (checking, valid, invalid)
|
||||
@@ -8,6 +9,8 @@
|
||||
|
||||
### Changed
|
||||
|
||||
- Replaced `process.cwd()` with `getProjectDir()` throughout codebase for improved project directory detection and handling
|
||||
- Made `SessionManager.list()` async to support asynchronous session discovery operations
|
||||
- Preserved internal whitespace and indentation in bash command normalization to support heredocs and indentation-sensitive scripts
|
||||
- Improved git context loading performance with configurable timeouts and parallel status/commit queries
|
||||
- Enhanced git context reliability with better error handling for timeout and command failures
|
||||
|
||||
@@ -25,7 +25,7 @@ if (modelFallbackMessage) console.log("Note:", modelFallbackMessage);
|
||||
console.log("Continued session:", continued.sessionFile);
|
||||
|
||||
// List and open specific session
|
||||
const sessions = SessionManager.list(process.cwd());
|
||||
const sessions = await SessionManager.list(process.cwd());
|
||||
console.log(`\nFound ${sessions.length} sessions:`);
|
||||
for (const info of sessions.slice(0, 3)) {
|
||||
console.log(` ${info.id.slice(0, 8)}… - "${info.firstMessage.slice(0, 30)}…"`);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { $env } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
|
||||
/** Conditional startup debug prints (stderr) when PI_DEBUG_STARTUP is set */
|
||||
const debugStartup = $env.PI_DEBUG_STARTUP ? (stage: string) => process.stderr.write(`[startup] ${stage}\n`) : () => {};
|
||||
@@ -221,7 +222,7 @@ export async function loadCapability<T>(capabilityId: string, options: LoadOptio
|
||||
throw new Error(`Unknown capability: "${capabilityId}"`);
|
||||
}
|
||||
|
||||
const cwd = options.cwd ?? process.cwd();
|
||||
const cwd = options.cwd ?? getProjectDir();
|
||||
const home = os.homedir();
|
||||
const ctx: LoadContext = { cwd, home };
|
||||
const providers = filterProviders(capability, options);
|
||||
|
||||
@@ -62,7 +62,7 @@ export interface LoadOptions {
|
||||
providers?: string[];
|
||||
/** Exclude these providers (by ID). Default: none */
|
||||
excludeProviders?: string[];
|
||||
/** Custom cwd. Default: process.cwd() */
|
||||
/** Custom cwd. Default: getProjectDir() */
|
||||
cwd?: string;
|
||||
/** Include items even if they fail validation. Default: false */
|
||||
includeInvalid?: boolean;
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
||||
import { isEnoent } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import chalk from "chalk";
|
||||
import { resolveReadPath } from "../tools/path-utils";
|
||||
import { formatSize } from "../tools/truncate";
|
||||
@@ -34,7 +35,7 @@ export async function processFileArguments(fileArgs: string[], options?: Process
|
||||
|
||||
for (const fileArg of fileArgs) {
|
||||
// Expand and resolve path (handles ~ expansion and macOS screenshot Unicode spaces)
|
||||
const absolutePath = path.resolve(resolveReadPath(fileArg, process.cwd()));
|
||||
const absolutePath = path.resolve(resolveReadPath(fileArg, getProjectDir()));
|
||||
|
||||
const stat = fs.statSync(absolutePath, { throwIfNoEntry: false });
|
||||
if (!stat) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as path from "node:path";
|
||||
import { createInterface } from "node:readline/promises";
|
||||
import { Shell } from "@oh-my-pi/pi-natives";
|
||||
import { APP_NAME } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { APP_NAME, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import chalk from "chalk";
|
||||
import { Settings } from "../config/settings";
|
||||
import { getOrCreateSnapshot } from "../utils/shell-snapshot";
|
||||
@@ -47,7 +47,7 @@ export async function runShellCommand(cmd: ShellCommandArgs): Promise<void> {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cwd = cmd.cwd ? path.resolve(cmd.cwd) : process.cwd();
|
||||
const cwd = cmd.cwd ? path.resolve(cmd.cwd) : getProjectDir();
|
||||
const settings = await Settings.init({ cwd });
|
||||
const { shell, env: shellEnv } = settings.getShellConfig();
|
||||
const snapshotPath = cmd.noSnapshot || !shell.includes("bash") ? null : await getOrCreateSnapshot(shell, shellEnv);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as path from "node:path";
|
||||
import { createInterface } from "node:readline/promises";
|
||||
import { $env } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { applyChangelogProposals } from "../../commit/changelog";
|
||||
import { detectChangelogBoundaries } from "../../commit/changelog/detect";
|
||||
import { parseUnreleasedSection } from "../../commit/changelog/parse";
|
||||
@@ -26,7 +27,7 @@ interface CommitExecutionContext {
|
||||
}
|
||||
|
||||
export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
||||
const cwd = process.cwd();
|
||||
const cwd = getProjectDir();
|
||||
const git = new ControlledGit(cwd);
|
||||
const [settings, authStorage] = await Promise.all([Settings.init({ cwd }), discoverAuthStorage()]);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as path from "node:path";
|
||||
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
||||
import { logger } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { ModelRegistry } from "../config/model-registry";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { Settings } from "../config/settings";
|
||||
@@ -38,7 +39,7 @@ export async function runCommitCommand(args: CommitCommandArgs): Promise<void> {
|
||||
}
|
||||
|
||||
async function runLegacyCommitCommand(args: CommitCommandArgs): Promise<void> {
|
||||
const cwd = process.cwd();
|
||||
const cwd = getProjectDir();
|
||||
const settings = await Settings.init();
|
||||
const commitSettings = settings.getGroup("commit");
|
||||
const authStorage = await discoverAuthStorage();
|
||||
|
||||
@@ -2,7 +2,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 { CONFIG_DIR_NAME, getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { CONFIG_DIR_NAME, getAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import type { TSchema } from "@sinclair/typebox";
|
||||
import { Value } from "@sinclair/typebox/value";
|
||||
import { Ajv, type ErrorObject, type ValidateFunction } from "ajv";
|
||||
@@ -39,8 +39,8 @@ export function getPackageDir(): string {
|
||||
}
|
||||
dir = path.dirname(dir);
|
||||
}
|
||||
// Fallback to cwd (docs/examples won't be found, but that's fine)
|
||||
return process.cwd();
|
||||
// Fallback to project dir (docs/examples won't be found, but that's fine)
|
||||
return getProjectDir();
|
||||
}
|
||||
|
||||
/** Get path to CHANGELOG.md (optional, may not exist in binary) */
|
||||
@@ -273,7 +273,7 @@ export interface GetConfigDirsOptions {
|
||||
user?: boolean;
|
||||
/** Include project-level directories (.omp/...). Default: true */
|
||||
project?: boolean;
|
||||
/** Current working directory for project paths. Default: process.cwd() */
|
||||
/** Current working directory for project paths. Default: getProjectDir() */
|
||||
cwd?: string;
|
||||
/** Only return directories that exist. Default: false */
|
||||
existingOnly?: boolean;
|
||||
@@ -296,7 +296,7 @@ export interface GetConfigDirsOptions {
|
||||
* getConfigDirs("skills", { user: false, existingOnly: true })
|
||||
*/
|
||||
export function getConfigDirs(subpath: string, options: GetConfigDirsOptions = {}): ConfigDirEntry[] {
|
||||
const { user = true, project = true, cwd = process.cwd(), existingOnly = false } = options;
|
||||
const { user = true, project = true, cwd = getProjectDir(), existingOnly = false } = options;
|
||||
const results: ConfigDirEntry[] = [];
|
||||
|
||||
// User-level directories (highest priority)
|
||||
@@ -382,7 +382,7 @@ export function findConfigFileWithMeta(
|
||||
* Returns one entry per config base (.omp, .claude) - the nearest one found.
|
||||
* Results are in priority order (highest first).
|
||||
*/
|
||||
export function findAllNearestProjectConfigDirs(subpath: string, cwd: string = process.cwd()): ConfigDirEntry[] {
|
||||
export function findAllNearestProjectConfigDirs(subpath: string, cwd: string = getProjectDir()): ConfigDirEntry[] {
|
||||
const results: ConfigDirEntry[] = [];
|
||||
const foundBases = new Set<string>();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { logger } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectPromptsDir, getPromptsDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getProjectDir, getProjectPromptsDir, getPromptsDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import Handlebars from "handlebars";
|
||||
import { computeLineHash } from "../patch/hashline";
|
||||
import { jtdToTypeScript } from "../tools/jtd-to-typescript";
|
||||
@@ -459,7 +459,7 @@ async function loadTemplatesFromDir(
|
||||
}
|
||||
|
||||
export interface LoadPromptTemplatesOptions {
|
||||
/** Working directory for project-local templates. Default: process.cwd() */
|
||||
/** Working directory for project-local templates. Default: getProjectDir() */
|
||||
cwd?: string;
|
||||
/** Agent config directory for global templates. Default: from getPromptsDir() */
|
||||
agentDir?: string;
|
||||
@@ -471,7 +471,7 @@ export interface LoadPromptTemplatesOptions {
|
||||
* 2. Project: cwd/.omp/prompts/
|
||||
*/
|
||||
export async function loadPromptTemplates(options: LoadPromptTemplatesOptions = {}): Promise<PromptTemplate[]> {
|
||||
const resolvedCwd = options.cwd ?? process.cwd();
|
||||
const resolvedCwd = options.cwd ?? getProjectDir();
|
||||
const resolvedAgentDir = options.agentDir ?? getPromptsDir();
|
||||
|
||||
const templates: PromptTemplate[] = [];
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { isEnoent, logger, procmgr } from "@oh-my-pi/pi-utils";
|
||||
import { getAgentDbPath, getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getAgentDbPath, getAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { YAML } from "bun";
|
||||
import { type Settings as SettingsCapabilityItem, settingsCapability } from "../capability/settings";
|
||||
import type { ModelRole } from "../config/model-registry";
|
||||
@@ -146,7 +146,7 @@ export class Settings {
|
||||
#persist: boolean;
|
||||
|
||||
private constructor(options: SettingsOptions = {}) {
|
||||
this.#cwd = path.normalize(options.cwd ?? process.cwd());
|
||||
this.#cwd = path.normalize(options.cwd ?? getProjectDir());
|
||||
this.#agentDir = path.normalize(options.agentDir ?? getAgentDir());
|
||||
this.#configPath = options.inMemory ? null : path.join(this.#agentDir, "config.yml");
|
||||
this.#persist = !options.inMemory;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* System information collection for debug reports.
|
||||
*/
|
||||
import * as os from "node:os";
|
||||
import { VERSION } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getProjectDir, VERSION } from "@oh-my-pi/pi-utils/dirs";
|
||||
|
||||
export interface SystemInfo {
|
||||
os: string;
|
||||
@@ -65,7 +65,7 @@ export async function collectSystemInfo(): Promise<SystemInfo> {
|
||||
bun: Bun.version,
|
||||
node: process.version,
|
||||
},
|
||||
cwd: process.cwd(),
|
||||
cwd: getProjectDir(),
|
||||
shell,
|
||||
terminal,
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
||||
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
|
||||
import { getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import * as typebox from "@sinclair/typebox";
|
||||
import { getConfigDirs } from "../../config";
|
||||
import { execCommand } from "../../exec/exec";
|
||||
@@ -62,7 +62,7 @@ async function loadCommandModule(
|
||||
}
|
||||
|
||||
export interface DiscoverCustomCommandsOptions {
|
||||
/** Current working directory. Default: process.cwd() */
|
||||
/** Current working directory. Default: getProjectDir() */
|
||||
cwd?: string;
|
||||
/** Agent config directory. Default: from getAgentDir() */
|
||||
agentDir?: string;
|
||||
@@ -80,7 +80,7 @@ export interface DiscoverCustomCommandsResult {
|
||||
export async function discoverCustomCommands(
|
||||
options: DiscoverCustomCommandsOptions = {},
|
||||
): Promise<DiscoverCustomCommandsResult> {
|
||||
const cwd = options.cwd ?? process.cwd();
|
||||
const cwd = options.cwd ?? getProjectDir();
|
||||
const agentDir = options.agentDir ?? getAgentDir();
|
||||
const paths: Array<{ path: string; source: CustomCommandSource }> = [];
|
||||
const seen = new Set<string>();
|
||||
@@ -136,7 +136,7 @@ export async function discoverCustomCommands(
|
||||
}
|
||||
|
||||
export interface LoadCustomCommandsOptions {
|
||||
/** Current working directory. Default: process.cwd() */
|
||||
/** Current working directory. Default: getProjectDir() */
|
||||
cwd?: string;
|
||||
/** Agent config directory. Default: from getAgentDir() */
|
||||
agentDir?: string;
|
||||
@@ -163,7 +163,7 @@ function loadBundledCommands(sharedApi: CustomCommandAPI): LoadedCustomCommand[]
|
||||
* Discover and load custom commands from standard locations.
|
||||
*/
|
||||
export async function loadCustomCommands(options: LoadCustomCommandsOptions = {}): Promise<CustomCommandsLoadResult> {
|
||||
const cwd = options.cwd ?? process.cwd();
|
||||
const cwd = options.cwd ?? getProjectDir();
|
||||
const agentDir = options.agentDir ?? getAgentDir();
|
||||
|
||||
const { paths } = await discoverCustomCommands({ cwd, agentDir });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import { isEnoent } from "@oh-my-pi/pi-utils";
|
||||
import { getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import type { InstalledPlugin } from "./types";
|
||||
|
||||
const PLUGINS_DIR = path.join(getAgentDir(), "plugins");
|
||||
@@ -131,7 +131,7 @@ export async function listPlugins(): Promise<InstalledPlugin[]> {
|
||||
}
|
||||
|
||||
export async function linkPlugin(localPath: string): Promise<void> {
|
||||
const cwd = process.cwd();
|
||||
const cwd = getProjectDir();
|
||||
const absolutePath = path.resolve(cwd, localPath);
|
||||
|
||||
// Validate that resolved path is within cwd to prevent path traversal
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
getPluginsLockfile,
|
||||
getPluginsNodeModules,
|
||||
getPluginsPackageJson,
|
||||
getProjectDir,
|
||||
getProjectPluginOverridesPath,
|
||||
} from "@oh-my-pi/pi-utils/dirs";
|
||||
import { extractPackageName, parsePluginSpec } from "./parser";
|
||||
@@ -50,7 +51,7 @@ export class PluginManager {
|
||||
#runtimeConfig: PluginRuntimeConfig | null = null;
|
||||
#cwd: string;
|
||||
|
||||
constructor(cwd: string = process.cwd()) {
|
||||
constructor(cwd: string = getProjectDir()) {
|
||||
this.#cwd = cwd;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { logger } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { skillCapability } from "../capability/skill";
|
||||
import type { SourceMeta } from "../capability/types";
|
||||
import type { SkillsSettings } from "../config/settings";
|
||||
@@ -207,7 +208,7 @@ async function scanDirectoryForSkills(dir: string): Promise<LoadSkillsResult> {
|
||||
}
|
||||
|
||||
export interface LoadSkillsOptions extends SkillsSettings {
|
||||
/** Working directory for project-local skills. Default: process.cwd() */
|
||||
/** Working directory for project-local skills. Default: getProjectDir() */
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
@@ -217,7 +218,7 @@ export interface LoadSkillsOptions extends SkillsSettings {
|
||||
*/
|
||||
export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadSkillsResult> {
|
||||
const {
|
||||
cwd = process.cwd(),
|
||||
cwd = getProjectDir(),
|
||||
enabled = true,
|
||||
enableCodexUser = true,
|
||||
enableClaudeUser = true,
|
||||
|
||||
@@ -281,7 +281,7 @@ export function substituteArgs(content: string, args: string[]): string {
|
||||
}
|
||||
|
||||
export interface LoadSlashCommandsOptions {
|
||||
/** Working directory for project-local commands. Default: process.cwd() */
|
||||
/** Working directory for project-local commands. Default: getProjectDir() */
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as path from "node:path";
|
||||
import { $env, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
||||
import { getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { OutputSink } from "../session/streaming-output";
|
||||
import { time } from "../utils/timings";
|
||||
import { shutdownSharedGateway } from "./gateway-coordinator";
|
||||
@@ -523,7 +523,7 @@ export async function executePythonWithKernel(
|
||||
}
|
||||
|
||||
export async function executePython(code: string, options?: PythonExecutorOptions): Promise<PythonResult> {
|
||||
const cwd = options?.cwd ?? process.cwd();
|
||||
const cwd = options?.cwd ?? getProjectDir();
|
||||
await ensureKernelAvailable(cwd);
|
||||
|
||||
const kernelMode = options?.kernelMode ?? "session";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import { getAgentModulesDir, getProjectModulesDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getAgentModulesDir, getProjectDir, getProjectModulesDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
|
||||
export type PythonModuleSource = "user" | "project";
|
||||
|
||||
@@ -24,7 +24,7 @@ export interface PythonModuleExecutor {
|
||||
}
|
||||
|
||||
export interface DiscoverPythonModulesOptions {
|
||||
/** Working directory for project-level modules. Default: process.cwd() */
|
||||
/** Working directory for project-level modules. Default: getProjectDir() */
|
||||
cwd?: string;
|
||||
/** Agent directory for user-level modules. Default: from getAgentDir() */
|
||||
agentDir?: string;
|
||||
@@ -65,7 +65,7 @@ async function readModuleContent(candidate: ModuleCandidate): Promise<PythonModu
|
||||
* Discover Python prelude extension modules from user and project directories.
|
||||
*/
|
||||
export async function discoverPythonModules(options: DiscoverPythonModulesOptions = {}): Promise<PythonModuleEntry[]> {
|
||||
const cwd = options.cwd ?? process.cwd();
|
||||
const cwd = options.cwd ?? getProjectDir();
|
||||
|
||||
const userDir = getAgentModulesDir(options.agentDir);
|
||||
const projectDir = getProjectModulesDir(cwd);
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
* This file handles CLI argument parsing and translates them into
|
||||
* createAgentSession() options. The SDK does the heavy lifting.
|
||||
*/
|
||||
|
||||
import { realpathSync } from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
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 { $env, postmortem } from "@oh-my-pi/pi-utils";
|
||||
import { VERSION } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getProjectDir, setProjectDir, VERSION } from "@oh-my-pi/pi-utils/dirs";
|
||||
import chalk from "chalk";
|
||||
import type { Args } from "./cli/args";
|
||||
import { processFileArguments } from "./cli/file-processor";
|
||||
@@ -278,11 +280,11 @@ async function maybeAutoChdir(parsed: Args): Promise<void> {
|
||||
}
|
||||
|
||||
const normalizePath = (value: string) => {
|
||||
const resolved = path.resolve(value);
|
||||
const resolved = realpathSync(path.resolve(value));
|
||||
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
||||
};
|
||||
|
||||
const cwd = normalizePath(process.cwd());
|
||||
const cwd = normalizePath(getProjectDir());
|
||||
const normalizedHome = normalizePath(home);
|
||||
if (cwd !== normalizedHome) {
|
||||
return;
|
||||
@@ -303,7 +305,7 @@ async function maybeAutoChdir(parsed: Args): Promise<void> {
|
||||
if (!(await isDirectory(candidate))) {
|
||||
continue;
|
||||
}
|
||||
process.chdir(candidate);
|
||||
setProjectDir(candidate);
|
||||
return;
|
||||
} catch {
|
||||
// Try next candidate.
|
||||
@@ -313,7 +315,7 @@ async function maybeAutoChdir(parsed: Args): Promise<void> {
|
||||
try {
|
||||
const fallback = os.tmpdir();
|
||||
if (fallback && normalizePath(fallback) !== cwd && (await isDirectory(fallback))) {
|
||||
process.chdir(fallback);
|
||||
setProjectDir(fallback);
|
||||
}
|
||||
} catch {
|
||||
// Ignore fallback errors.
|
||||
@@ -355,7 +357,7 @@ async function buildSessionOptions(
|
||||
modelRegistry: ModelRegistry,
|
||||
): Promise<CreateAgentSessionOptions> {
|
||||
const options: CreateAgentSessionOptions = {
|
||||
cwd: parsed.cwd ?? process.cwd(),
|
||||
cwd: parsed.cwd ?? getProjectDir(),
|
||||
};
|
||||
|
||||
// Auto-discover SYSTEM.md if no CLI system prompt provided
|
||||
@@ -524,7 +526,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cwd = process.cwd();
|
||||
const cwd = getProjectDir();
|
||||
await Settings.init({ cwd });
|
||||
debugStartup("main:Settings.init");
|
||||
time("Settings.init");
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import { readJsonl } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { type Subprocess, spawn } from "bun";
|
||||
import type { JsonRpcResponse, MCPRequestOptions, MCPStdioServerConfig, MCPTransport } from "../../mcp/types";
|
||||
|
||||
@@ -54,7 +55,7 @@ export class StdioTransport implements MCPTransport {
|
||||
|
||||
this.#process = spawn({
|
||||
cmd: [this.config.command, ...args],
|
||||
cwd: this.config.cwd ?? process.cwd(),
|
||||
cwd: this.config.cwd ?? getProjectDir(),
|
||||
env,
|
||||
stdin: "pipe",
|
||||
stdout: "pipe",
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as path from "node:path";
|
||||
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
||||
import { type Component, padding, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
||||
import { isEnoent } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { theme } from "../../modes/theme/theme";
|
||||
import type { AgentSession } from "../../session/agent-session";
|
||||
import { shortenPath } from "../../tools/render-utils";
|
||||
@@ -21,7 +22,7 @@ function sanitizeStatusText(text: string): string {
|
||||
|
||||
/** Find the git root by walking up from cwd. Returns path and content of .git/HEAD if found. */
|
||||
async function findGitHeadPath(): Promise<{ path: string; content: string } | null> {
|
||||
let dir = process.cwd();
|
||||
let dir = getProjectDir();
|
||||
while (true) {
|
||||
const gitHeadPath = path.join(dir, ".git", "HEAD");
|
||||
try {
|
||||
@@ -201,7 +202,7 @@ export class FooterComponent implements Component {
|
||||
};
|
||||
|
||||
// Replace home directory with ~
|
||||
let pwd = shortenPath(process.cwd());
|
||||
let pwd = shortenPath(getProjectDir());
|
||||
|
||||
// Add git branch if available
|
||||
const branch = this.#getCurrentBranch();
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
||||
import { type Component, padding, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { $ } from "bun";
|
||||
import { settings } from "../../config/settings";
|
||||
import type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle } from "../../config/settings-schema";
|
||||
@@ -41,7 +42,7 @@ function sanitizeStatusText(text: string): string {
|
||||
|
||||
/** Find the git root directory by walking up from cwd */
|
||||
function findGitHeadPath(): string | null {
|
||||
let dir = process.cwd();
|
||||
let dir = getProjectDir();
|
||||
while (true) {
|
||||
const gitHeadPath = path.join(dir, ".git", "HEAD");
|
||||
if (fs.existsSync(gitHeadPath)) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as os from "node:os";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { theme } from "../../../modes/theme/theme";
|
||||
import { shortenPath } from "../../../tools/render-utils";
|
||||
import type { RenderedSegment, SegmentContext, StatusLineSegment, StatusLineSegmentId } from "./types";
|
||||
@@ -91,7 +92,7 @@ const pathSegment: StatusLineSegment = {
|
||||
render(ctx) {
|
||||
const opts = ctx.options.path ?? {};
|
||||
|
||||
let pwd = process.cwd();
|
||||
let pwd = getProjectDir();
|
||||
|
||||
if (opts.abbreviate !== false) {
|
||||
pwd = shortenPath(pwd);
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
type TUI,
|
||||
} from "@oh-my-pi/pi-tui";
|
||||
import { logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import type { Theme } from "../../modes/theme/theme";
|
||||
import { theme } from "../../modes/theme/theme";
|
||||
import {
|
||||
@@ -129,7 +130,7 @@ export class ToolExecutionComponent extends Container {
|
||||
options: ToolExecutionOptions = {},
|
||||
tool: AgentTool | undefined,
|
||||
ui: TUI,
|
||||
cwd: string = process.cwd(),
|
||||
cwd: string = getProjectDir(),
|
||||
) {
|
||||
super();
|
||||
this.#toolName = toolName;
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { UsageLimit, UsageReport } from "@oh-my-pi/pi-ai";
|
||||
import { copyToClipboard } from "@oh-my-pi/pi-natives";
|
||||
import { Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@oh-my-pi/pi-tui";
|
||||
import { Snowflake } from "@oh-my-pi/pi-utils";
|
||||
import { setProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { $ } from "bun";
|
||||
import { reset as resetCapabilities } from "../../capability";
|
||||
import { loadCustomShare } from "../../export/custom-share";
|
||||
@@ -466,7 +467,7 @@ export class CommandController {
|
||||
try {
|
||||
await this.ctx.sessionManager.flush();
|
||||
await this.ctx.sessionManager.moveTo(resolvedPath);
|
||||
process.chdir(resolvedPath);
|
||||
setProjectDir(resolvedPath);
|
||||
resetCapabilities();
|
||||
await this.ctx.refreshSlashCommandState(resolvedPath);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Handles /mcp subcommands for managing MCP servers.
|
||||
*/
|
||||
import { Spacer, Text } from "@oh-my-pi/pi-tui";
|
||||
import { getMCPConfigPath } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getMCPConfigPath, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import type { SourceMeta } from "../../capability/types";
|
||||
import { analyzeAuthError, discoverOAuthEndpoints, MCPManager } from "../../mcp";
|
||||
import { connectToServer, disconnectServer, listTools } from "../../mcp/client";
|
||||
@@ -528,7 +528,7 @@ export class MCPCommandController {
|
||||
if (this.ctx.mcpManager) {
|
||||
resolvedConfig = await this.ctx.mcpManager.prepareConfig(config);
|
||||
} else {
|
||||
const tempManager = new MCPManager(process.cwd());
|
||||
const tempManager = new MCPManager(getProjectDir());
|
||||
tempManager.setAuthStorage(this.ctx.session.modelRegistry.authStorage);
|
||||
resolvedConfig = await tempManager.prepareConfig(config);
|
||||
}
|
||||
@@ -540,7 +540,7 @@ export class MCPCommandController {
|
||||
async #findConfiguredServer(
|
||||
name: string,
|
||||
): Promise<{ filePath: string; scope: "user" | "project"; config: MCPServerConfig } | null> {
|
||||
const cwd = process.cwd();
|
||||
const cwd = getProjectDir();
|
||||
const userPath = getMCPConfigPath("user", cwd);
|
||||
const projectPath = getMCPConfigPath("project", cwd);
|
||||
|
||||
@@ -665,7 +665,7 @@ export class MCPCommandController {
|
||||
async #handleWizardComplete(name: string, config: MCPServerConfig, scope: "user" | "project"): Promise<void> {
|
||||
try {
|
||||
// Determine file path
|
||||
const cwd = process.cwd();
|
||||
const cwd = getProjectDir();
|
||||
const filePath = getMCPConfigPath(scope, cwd);
|
||||
|
||||
// Add server to config
|
||||
@@ -747,7 +747,7 @@ export class MCPCommandController {
|
||||
*/
|
||||
async #handleList(): Promise<void> {
|
||||
try {
|
||||
const cwd = process.cwd();
|
||||
const cwd = getProjectDir();
|
||||
|
||||
// Load from both user and project configs
|
||||
const userPath = getMCPConfigPath("user", cwd);
|
||||
@@ -913,7 +913,7 @@ export class MCPCommandController {
|
||||
}
|
||||
|
||||
try {
|
||||
const cwd = process.cwd();
|
||||
const cwd = getProjectDir();
|
||||
const userPath = getMCPConfigPath("user", cwd);
|
||||
const projectPath = getMCPConfigPath("project", cwd);
|
||||
const filePath = scope === "user" ? userPath : projectPath;
|
||||
@@ -957,7 +957,7 @@ export class MCPCommandController {
|
||||
|
||||
let connection: MCPServerConnection | undefined;
|
||||
try {
|
||||
const cwd = process.cwd();
|
||||
const cwd = getProjectDir();
|
||||
const userPath = getMCPConfigPath("user", cwd);
|
||||
const projectPath = getMCPConfigPath("project", cwd);
|
||||
|
||||
@@ -989,7 +989,7 @@ export class MCPCommandController {
|
||||
if (this.ctx.mcpManager) {
|
||||
resolvedConfig = await this.ctx.mcpManager.prepareConfig(config);
|
||||
} else {
|
||||
const tempManager = new MCPManager(process.cwd());
|
||||
const tempManager = new MCPManager(getProjectDir());
|
||||
tempManager.setAuthStorage(this.ctx.session.modelRegistry.authStorage);
|
||||
resolvedConfig = await tempManager.prepareConfig(config);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import { getOAuthProviders, type OAuthProvider } from "@oh-my-pi/pi-ai";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
||||
import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getAgentDbPath, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { MODEL_ROLES } from "../../config/model-registry";
|
||||
import { settings } from "../../config/settings";
|
||||
import { DebugSelectorComponent } from "../../debug";
|
||||
@@ -67,7 +67,7 @@ export class SelectorController {
|
||||
availableThinkingLevels: this.ctx.session.getAvailableThinkingLevels(),
|
||||
thinkingLevel: this.ctx.session.thinkingLevel,
|
||||
availableThemes,
|
||||
cwd: process.cwd(),
|
||||
cwd: getProjectDir(),
|
||||
},
|
||||
{
|
||||
onChange: (id, value) => this.handleSettingChange(id, value),
|
||||
@@ -146,7 +146,7 @@ export class SelectorController {
|
||||
* Replaces /status with a unified view of all providers and extensions.
|
||||
*/
|
||||
async showExtensionsDashboard(): Promise<void> {
|
||||
const dashboard = await ExtensionDashboard.create(process.cwd(), this.ctx.settings, this.ctx.ui.terminal.rows);
|
||||
const dashboard = await ExtensionDashboard.create(getProjectDir(), this.ctx.settings, this.ctx.ui.terminal.rows);
|
||||
this.showSelector(done => {
|
||||
dashboard.onClose = () => {
|
||||
done();
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
TUI,
|
||||
} from "@oh-my-pi/pi-tui";
|
||||
import { $env, isEnoent, logger, postmortem } from "@oh-my-pi/pi-utils";
|
||||
import { APP_NAME } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { APP_NAME, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import chalk from "chalk";
|
||||
import { KeybindingsManager } from "../config/keybindings";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
@@ -249,7 +249,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
this.#cleanupUnsubscribe = postmortem.register("session-manager-flush", () => this.sessionManager.flush());
|
||||
debugStartup("InteractiveMode.init:cleanupRegistered");
|
||||
|
||||
await this.refreshSlashCommandState(process.cwd());
|
||||
await this.refreshSlashCommandState(getProjectDir());
|
||||
debugStartup("InteractiveMode.init:slashCommands");
|
||||
|
||||
// Get current model info for welcome screen
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Agent, type AgentEvent, type AgentMessage, type AgentTool, type Thinkin
|
||||
import { type Message, type Model, supportsXhigh } from "@oh-my-pi/pi-ai";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { $env, logger, postmortem } from "@oh-my-pi/pi-utils";
|
||||
import { getAgentDbPath, getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getAgentDbPath, getAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import chalk from "chalk";
|
||||
import { loadCapability } from "./capability";
|
||||
import { type Rule, ruleCapability } from "./capability/rule";
|
||||
@@ -85,7 +85,7 @@ const debugStartup = $env.PI_DEBUG_STARTUP ? (stage: string) => process.stderr.w
|
||||
|
||||
// Types
|
||||
export interface CreateAgentSessionOptions {
|
||||
/** Working directory for project-local discovery. Default: process.cwd() */
|
||||
/** Working directory for project-local discovery. Default: getProjectDir() */
|
||||
cwd?: string;
|
||||
/** Global config directory. Default: ~/.omp/agent */
|
||||
agentDir?: string;
|
||||
@@ -240,7 +240,7 @@ export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir(
|
||||
* Discover extensions from cwd.
|
||||
*/
|
||||
export async function discoverExtensions(cwd?: string): Promise<LoadExtensionsResult> {
|
||||
const resolvedCwd = cwd ?? process.cwd();
|
||||
const resolvedCwd = cwd ?? getProjectDir();
|
||||
|
||||
return discoverAndLoadExtensions([], resolvedCwd);
|
||||
}
|
||||
@@ -255,7 +255,7 @@ export async function discoverSkills(
|
||||
): Promise<{ skills: Skill[]; warnings: SkillWarning[] }> {
|
||||
return await loadSkillsInternal({
|
||||
...settings,
|
||||
cwd: cwd ?? process.cwd(),
|
||||
cwd: cwd ?? getProjectDir(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ export async function discoverContextFiles(
|
||||
_agentDir?: string,
|
||||
): Promise<Array<{ path: string; content: string; depth?: number }>> {
|
||||
return await loadContextFilesInternal({
|
||||
cwd: cwd ?? process.cwd(),
|
||||
cwd: cwd ?? getProjectDir(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ export async function discoverContextFiles(
|
||||
*/
|
||||
export async function discoverPromptTemplates(cwd?: string, agentDir?: string): Promise<PromptTemplate[]> {
|
||||
return await loadPromptTemplatesInternal({
|
||||
cwd: cwd ?? process.cwd(),
|
||||
cwd: cwd ?? getProjectDir(),
|
||||
agentDir: agentDir ?? getDefaultAgentDir(),
|
||||
});
|
||||
}
|
||||
@@ -286,14 +286,14 @@ export async function discoverPromptTemplates(cwd?: string, agentDir?: string):
|
||||
* Discover file-based slash commands from commands/ directories.
|
||||
*/
|
||||
export async function discoverSlashCommands(cwd?: string): Promise<FileSlashCommand[]> {
|
||||
return loadSlashCommandsInternal({ cwd: cwd ?? process.cwd() });
|
||||
return loadSlashCommandsInternal({ cwd: cwd ?? getProjectDir() });
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover custom commands (TypeScript slash commands) from cwd and agentDir.
|
||||
*/
|
||||
export async function discoverCustomTSCommands(cwd?: string, agentDir?: string): Promise<CustomCommandsLoadResult> {
|
||||
const resolvedCwd = cwd ?? process.cwd();
|
||||
const resolvedCwd = cwd ?? getProjectDir();
|
||||
const resolvedAgentDir = agentDir ?? getDefaultAgentDir();
|
||||
|
||||
return loadCustomCommandsInternal({
|
||||
@@ -307,7 +307,7 @@ export async function discoverCustomTSCommands(cwd?: string, agentDir?: string):
|
||||
* Returns the manager and loaded tools.
|
||||
*/
|
||||
export async function discoverMCPServers(cwd?: string): Promise<MCPToolsLoadResult> {
|
||||
const resolvedCwd = cwd ?? process.cwd();
|
||||
const resolvedCwd = cwd ?? getProjectDir();
|
||||
return discoverAndLoadMCPTools(resolvedCwd);
|
||||
}
|
||||
|
||||
@@ -469,7 +469,7 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
|
||||
* model: myModel,
|
||||
* getApiKey: async () => Bun.env.MY_KEY,
|
||||
* systemPrompt: 'You are helpful.',
|
||||
* tools: codingTools({ cwd: process.cwd() }),
|
||||
* tools: codingTools({ cwd: getProjectDir() }),
|
||||
* skills: [],
|
||||
* sessionManager: SessionManager.inMemory(),
|
||||
* });
|
||||
@@ -477,7 +477,7 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
|
||||
*/
|
||||
export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
|
||||
debugStartup("sdk:createAgentSession:entry");
|
||||
const cwd = options.cwd ?? process.cwd();
|
||||
const cwd = options.cwd ?? getProjectDir();
|
||||
const agentDir = options.agentDir ?? getDefaultAgentDir();
|
||||
const eventBus = options.eventBus ?? new EventBus();
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, Message, TextContent, Usage } from "@oh-my-pi/pi-ai";
|
||||
import { isEnoent, logger, parseJsonlLenient, Snowflake } from "@oh-my-pi/pi-utils";
|
||||
import { getBlobsDir, getAgentDir as getDefaultAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getBlobsDir, getAgentDir as getDefaultAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { type BlobPutResult, BlobStore, externalizeImageData, isBlobRef, resolveImageData } from "./blob-store";
|
||||
import {
|
||||
type BashExecutionMessage,
|
||||
@@ -2153,10 +2153,10 @@ export class SessionManager {
|
||||
sessionDir?: string,
|
||||
storage: SessionStorage = new FileSessionStorage(),
|
||||
): Promise<SessionManager> {
|
||||
// Extract cwd from session header if possible, otherwise use process.cwd()
|
||||
// Extract cwd from session header if possible, otherwise use getProjectDir()
|
||||
const entries = await loadEntriesFromFile(filePath, storage);
|
||||
const header = entries.find(e => e.type === "session") as SessionHeader | undefined;
|
||||
const cwd = header?.cwd ?? process.cwd();
|
||||
const cwd = header?.cwd ?? getProjectDir();
|
||||
// If no sessionDir provided, derive from file's parent directory
|
||||
const dir = sessionDir ?? path.resolve(filePath, "..");
|
||||
const manager = new SessionManager(cwd, dir, true, storage);
|
||||
@@ -2188,7 +2188,10 @@ export class SessionManager {
|
||||
}
|
||||
|
||||
/** Create an in-memory session (no file persistence) */
|
||||
static inMemory(cwd: string = process.cwd(), storage: SessionStorage = new MemorySessionStorage()): SessionManager {
|
||||
static inMemory(
|
||||
cwd: string = getProjectDir(),
|
||||
storage: SessionStorage = new MemorySessionStorage(),
|
||||
): SessionManager {
|
||||
const manager = new SessionManager(cwd, "", false, storage);
|
||||
manager.#initNewSession();
|
||||
return manager;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import * as os from "node:os";
|
||||
import { getSystemInfo as getNativeSystemInfo, type SystemInfo } from "@oh-my-pi/pi-natives";
|
||||
import { $env, logger } from "@oh-my-pi/pi-utils";
|
||||
import { getGpuCachePath } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { getGpuCachePath, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { $ } from "bun";
|
||||
import { contextFileCapability } from "./capability/context-file";
|
||||
import { systemPromptCapability } from "./capability/system-prompt";
|
||||
@@ -362,7 +362,7 @@ export async function resolvePromptInput(input: string | undefined, description:
|
||||
}
|
||||
|
||||
export interface LoadContextFilesOptions {
|
||||
/** Working directory to start walking up from. Default: process.cwd() */
|
||||
/** Working directory to start walking up from. Default: getProjectDir() */
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ export interface LoadContextFilesOptions {
|
||||
export async function loadProjectContextFiles(
|
||||
options: LoadContextFilesOptions = {},
|
||||
): Promise<Array<{ path: string; content: string; depth?: number }>> {
|
||||
const resolvedCwd = options.cwd ?? process.cwd();
|
||||
const resolvedCwd = options.cwd ?? getProjectDir();
|
||||
|
||||
const result = await loadCapability(contextFileCapability.id, { cwd: resolvedCwd });
|
||||
|
||||
@@ -404,7 +404,7 @@ export async function loadProjectContextFiles(
|
||||
* Returns combined content from all discovered SYSTEM.md files.
|
||||
*/
|
||||
export async function loadSystemPromptFiles(options: LoadContextFilesOptions = {}): Promise<string | null> {
|
||||
const resolvedCwd = options.cwd ?? process.cwd();
|
||||
const resolvedCwd = options.cwd ?? getProjectDir();
|
||||
|
||||
const result = await loadCapability<SystemPromptFile>(systemPromptCapability.id, { cwd: resolvedCwd });
|
||||
|
||||
@@ -433,7 +433,7 @@ export interface BuildSystemPromptOptions {
|
||||
appendSystemPrompt?: string;
|
||||
/** Skills settings for discovery. */
|
||||
skillsSettings?: SkillsSettings;
|
||||
/** Working directory. Default: process.cwd() */
|
||||
/** Working directory. Default: getProjectDir() */
|
||||
cwd?: string;
|
||||
/** Pre-loaded context files (skips discovery if provided). */
|
||||
contextFiles?: Array<{ path: string; content: string; depth?: number }>;
|
||||
@@ -463,7 +463,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
||||
preloadedSkills: providedPreloadedSkills,
|
||||
rules,
|
||||
} = options;
|
||||
const resolvedCwd = cwd ?? process.cwd();
|
||||
const resolvedCwd = cwd ?? getProjectDir();
|
||||
const resolvedCustomPrompt = await resolvePromptInput(customPrompt, "system prompt");
|
||||
const resolvedAppendPrompt = await resolvePromptInput(appendSystemPrompt, "append system prompt");
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { $env, isEnoent } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { type BashResult, executeBash } from "../exec/bash-executor";
|
||||
@@ -205,7 +206,7 @@ interface BashRenderContext {
|
||||
function formatBashCommand(args: BashRenderArgs, _uiTheme: Theme): string {
|
||||
const command = args.command || "…";
|
||||
const prompt = "$";
|
||||
const cwd = process.cwd();
|
||||
const cwd = getProjectDir();
|
||||
let displayWorkdir = args.cwd;
|
||||
|
||||
if (displayWorkdir) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
||||
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
@@ -836,7 +837,7 @@ export const pythonToolRenderer = {
|
||||
renderCall(args: PythonRenderArgs, uiTheme: Theme): Component {
|
||||
const ui = new ToolUIKit(uiTheme);
|
||||
const cells = args.cells ?? [];
|
||||
const cwd = process.cwd();
|
||||
const cwd = getProjectDir();
|
||||
let displayWorkdir = args.cwd;
|
||||
|
||||
if (displayWorkdir) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from "bun:test";
|
||||
import { disposeAllKernelSessions, executePython } from "@oh-my-pi/pi-coding-agent/ipy/executor";
|
||||
import type { KernelExecuteResult } from "@oh-my-pi/pi-coding-agent/ipy/kernel";
|
||||
import * as pythonKernel from "@oh-my-pi/pi-coding-agent/ipy/kernel";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
|
||||
class FakeKernel {
|
||||
execute = vi.fn(async () => this.result);
|
||||
@@ -36,7 +37,7 @@ describe("executePython lifecycle", () => {
|
||||
.spyOn(pythonKernel.PythonKernel, "start")
|
||||
.mockResolvedValue(kernel as unknown as pythonKernel.PythonKernel);
|
||||
|
||||
await executePython("print('hi')", { kernelMode: "per-call", cwd: process.cwd() });
|
||||
await executePython("print('hi')", { kernelMode: "per-call", cwd: getProjectDir() });
|
||||
|
||||
expect(startSpy).toHaveBeenCalledTimes(1);
|
||||
expect(kernel.execute).toHaveBeenCalledTimes(1);
|
||||
@@ -50,8 +51,8 @@ describe("executePython lifecycle", () => {
|
||||
.spyOn(pythonKernel.PythonKernel, "start")
|
||||
.mockResolvedValue(kernel as unknown as pythonKernel.PythonKernel);
|
||||
|
||||
await executePython("1 + 1", { kernelMode: "session", sessionId: "test-session", cwd: process.cwd() });
|
||||
await executePython("2 + 2", { kernelMode: "session", sessionId: "test-session", cwd: process.cwd() });
|
||||
await executePython("1 + 1", { kernelMode: "session", sessionId: "test-session", cwd: getProjectDir() });
|
||||
await executePython("2 + 2", { kernelMode: "session", sessionId: "test-session", cwd: getProjectDir() });
|
||||
|
||||
expect(startSpy).toHaveBeenCalledTimes(1);
|
||||
expect(kernel.execute).toHaveBeenCalledTimes(2);
|
||||
@@ -66,12 +67,12 @@ describe("executePython lifecycle", () => {
|
||||
.mockResolvedValueOnce(kernel as unknown as pythonKernel.PythonKernel)
|
||||
.mockResolvedValueOnce(kernelNext as unknown as pythonKernel.PythonKernel);
|
||||
|
||||
await executePython("1 + 1", { kernelMode: "session", sessionId: "reset-session", cwd: process.cwd() });
|
||||
await executePython("1 + 1", { kernelMode: "session", sessionId: "reset-session", cwd: getProjectDir() });
|
||||
await executePython("2 + 2", {
|
||||
kernelMode: "session",
|
||||
sessionId: "reset-session",
|
||||
reset: true,
|
||||
cwd: process.cwd(),
|
||||
cwd: getProjectDir(),
|
||||
});
|
||||
|
||||
expect(startSpy).toHaveBeenCalledTimes(2);
|
||||
@@ -89,7 +90,7 @@ describe("executePython lifecycle", () => {
|
||||
.mockResolvedValueOnce(kernel as unknown as pythonKernel.PythonKernel)
|
||||
.mockResolvedValueOnce(kernelNext as unknown as pythonKernel.PythonKernel);
|
||||
|
||||
await executePython("1 + 1", { kernelMode: "session", sessionId: "dead-session", cwd: process.cwd() });
|
||||
await executePython("1 + 1", { kernelMode: "session", sessionId: "dead-session", cwd: getProjectDir() });
|
||||
|
||||
expect(startSpy).toHaveBeenCalledTimes(2);
|
||||
expect(kernel.shutdown).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -4,10 +4,11 @@ import * as path from "node:path";
|
||||
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
||||
import { resetPreludeDocsCache, warmPythonEnvironment } from "@oh-my-pi/pi-coding-agent/ipy/executor";
|
||||
import { getPythonToolDescription, PythonTool } from "@oh-my-pi/pi-coding-agent/tools/python";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
|
||||
const resolvePythonPath = (): string | null => {
|
||||
const venvPath = Bun.env.VIRTUAL_ENV;
|
||||
const candidates = [venvPath, path.join(process.cwd(), ".venv"), path.join(process.cwd(), "venv")].filter(
|
||||
const candidates = [venvPath, path.join(getProjectDir(), ".venv"), path.join(getProjectDir(), "venv")].filter(
|
||||
Boolean,
|
||||
) as string[];
|
||||
for (const candidate of candidates) {
|
||||
@@ -42,7 +43,7 @@ describe.skipIf(!shouldRun)("PYTHON_PRELUDE integration", () => {
|
||||
const helpers = ["env", "read", "write", "append", "rm", "mv", "cp", "find", "grep"];
|
||||
|
||||
const session = {
|
||||
cwd: process.cwd(),
|
||||
cwd: getProjectDir(),
|
||||
hasUI: false,
|
||||
getSessionFile: () => null,
|
||||
getSessionSpawns: () => null,
|
||||
@@ -75,7 +76,7 @@ describe.skipIf(!shouldRun)("PYTHON_PRELUDE integration", () => {
|
||||
|
||||
it("exposes prelude docs via warmup", async () => {
|
||||
resetPreludeDocsCache();
|
||||
const result = await warmPythonEnvironment(process.cwd());
|
||||
const result = await warmPythonEnvironment(getProjectDir());
|
||||
expect(result.ok).toBe(true);
|
||||
const names = result.docs.map(doc => doc.name);
|
||||
expect(names).toContain("read");
|
||||
@@ -83,7 +84,7 @@ describe.skipIf(!shouldRun)("PYTHON_PRELUDE integration", () => {
|
||||
|
||||
it("renders prelude docs in python tool description", async () => {
|
||||
resetPreludeDocsCache();
|
||||
const result = await warmPythonEnvironment(process.cwd());
|
||||
const result = await warmPythonEnvironment(getProjectDir());
|
||||
expect(result.ok).toBe(true);
|
||||
const description = getPythonToolDescription();
|
||||
expect(description).toContain("read");
|
||||
|
||||
@@ -2,6 +2,7 @@ import { beforeAll, describe, expect, it } from "bun:test";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import { FileType, fuzzyFind, type GlobMatch, glob, grep, htmlToMarkdown, invalidateFsScanCache } from "../src/index";
|
||||
|
||||
let testDir: string;
|
||||
@@ -135,7 +136,7 @@ describe("pi-natives", () => {
|
||||
const newFile = path.join(testDir, "newly-added.ts");
|
||||
await fs.writeFile(newFile, "export const newer = true;\n");
|
||||
|
||||
const relativePath = path.relative(process.cwd(), newFile);
|
||||
const relativePath = path.relative(getProjectDir(), newFile);
|
||||
invalidateFsScanCache(relativePath);
|
||||
|
||||
const result = await glob({ pattern: "newly-added.ts", path: testDir, cache: true });
|
||||
|
||||
@@ -443,6 +443,7 @@ Supports both slash commands and file paths.
|
||||
|
||||
```typescript
|
||||
import { CombinedAutocompleteProvider } from "@oh-my-pi/pi-tui";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
|
||||
const provider = new CombinedAutocompleteProvider(
|
||||
[
|
||||
@@ -450,7 +451,7 @@ const provider = new CombinedAutocompleteProvider(
|
||||
{ name: "clear", description: "Clear screen" },
|
||||
{ name: "delete", description: "Delete last message" },
|
||||
],
|
||||
process.cwd() // base path for file completion
|
||||
getProjectDir() // base path for file completion
|
||||
);
|
||||
|
||||
editor.setAutocompleteProvider(provider);
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { fuzzyFind } from "@oh-my-pi/pi-natives";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
|
||||
const PATH_DELIMITERS = new Set([" ", "\t", '"', "'", "="]);
|
||||
|
||||
@@ -203,7 +204,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||
#dirCache: Map<string, { entries: fs.Dirent[]; timestamp: number }> = new Map();
|
||||
readonly #DIR_CACHE_TTL = 2000; // 2 seconds
|
||||
|
||||
constructor(commands: (SlashCommand | AutocompleteItem)[] = [], basePath: string = process.cwd()) {
|
||||
constructor(commands: (SlashCommand | AutocompleteItem)[] = [], basePath: string = getProjectDir()) {
|
||||
this.#commands = commands;
|
||||
this.#basePath = basePath;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import type { AutocompleteProvider, CombinedAutocompleteProvider } from "../autocomplete";
|
||||
import { type EditorKeybindingsManager, getEditorKeybindings } from "../keybindings";
|
||||
import { matchesKey } from "../keys";
|
||||
@@ -419,7 +420,7 @@ export class Editor implements Component, Focusable {
|
||||
this.#history.pop();
|
||||
}
|
||||
|
||||
this.#historyStorage?.add(trimmed, process.cwd());
|
||||
this.#historyStorage?.add(trimmed, getProjectDir());
|
||||
}
|
||||
|
||||
#isEditorEmpty(): boolean {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Markdown } from "@oh-my-pi/pi-tui/components/markdown";
|
||||
import { Text } from "@oh-my-pi/pi-tui/components/text";
|
||||
import { ProcessTerminal } from "@oh-my-pi/pi-tui/terminal";
|
||||
import { TUI } from "@oh-my-pi/pi-tui/tui";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
||||
import chalk from "chalk";
|
||||
import { defaultEditorTheme, defaultMarkdownTheme } from "./test-themes";
|
||||
|
||||
@@ -31,7 +32,7 @@ const autocompleteProvider = new CombinedAutocompleteProvider(
|
||||
{ name: "delete", description: "Delete the last message" },
|
||||
{ name: "clear", description: "Clear all messages" },
|
||||
],
|
||||
process.cwd(),
|
||||
getProjectDir(),
|
||||
);
|
||||
editor.setAutocompleteProvider(autocompleteProvider);
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* Uses PI_CONFIG_DIR (default ".omp") for the config root and
|
||||
* PI_CODING_AGENT_DIR to override the agent directory.
|
||||
*/
|
||||
|
||||
import { realpathSync } from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { version } from "../package.json" with { type: "json" };
|
||||
@@ -22,6 +24,24 @@ export const VERSION: string = version;
|
||||
// Root directories
|
||||
// =============================================================================
|
||||
|
||||
let projectDir = process.cwd();
|
||||
if ($env.PWD) {
|
||||
if (realpathSync($env.PWD) === projectDir) {
|
||||
projectDir = $env.PWD;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the project directory. */
|
||||
export function getProjectDir(): string {
|
||||
return projectDir;
|
||||
}
|
||||
|
||||
/** Set the project directory. */
|
||||
export function setProjectDir(dir: string): void {
|
||||
projectDir = path.resolve(dir);
|
||||
process.chdir(projectDir);
|
||||
}
|
||||
|
||||
/** Get the config root directory (~/.omp). */
|
||||
export function getConfigRootDir(): string {
|
||||
return path.join(os.homedir(), $env.PI_CONFIG_DIR || CONFIG_DIR_NAME);
|
||||
@@ -42,7 +62,7 @@ export function getAgentDir(): string {
|
||||
}
|
||||
|
||||
/** Get the project-local config directory (.omp). */
|
||||
export function getProjectAgentDir(cwd: string = process.cwd()): string {
|
||||
export function getProjectAgentDir(cwd: string = getProjectDir()): string {
|
||||
return path.join(cwd, CONFIG_DIR_NAME);
|
||||
}
|
||||
|
||||
@@ -225,17 +245,17 @@ export function getDebugLogPath(agentDir?: string): string {
|
||||
// =============================================================================
|
||||
|
||||
/** Get the project-level Python modules directory (.omp/modules). */
|
||||
export function getProjectModulesDir(cwd: string = process.cwd()): string {
|
||||
export function getProjectModulesDir(cwd: string = getProjectDir()): string {
|
||||
return path.join(getProjectAgentDir(cwd), "modules");
|
||||
}
|
||||
|
||||
/** Get the project-level prompts directory (.omp/prompts). */
|
||||
export function getProjectPromptsDir(cwd: string = process.cwd()): string {
|
||||
export function getProjectPromptsDir(cwd: string = getProjectDir()): string {
|
||||
return path.join(getProjectAgentDir(cwd), "prompts");
|
||||
}
|
||||
|
||||
/** Get the project-level plugin overrides path (.omp/plugin-overrides.json). */
|
||||
export function getProjectPluginOverridesPath(cwd: string = process.cwd()): string {
|
||||
export function getProjectPluginOverridesPath(cwd: string = getProjectDir()): string {
|
||||
return path.join(getProjectAgentDir(cwd), "plugin-overrides.json");
|
||||
}
|
||||
|
||||
@@ -244,7 +264,7 @@ export function getProjectPluginOverridesPath(cwd: string = process.cwd()): stri
|
||||
// =============================================================================
|
||||
|
||||
/** Get the primary MCP config file path (first candidate). */
|
||||
export function getMCPConfigPath(scope: "user" | "project", cwd: string = process.cwd()): string {
|
||||
export function getMCPConfigPath(scope: "user" | "project", cwd: string = getProjectDir()): string {
|
||||
if (scope === "user") {
|
||||
return path.join(getAgentDir(), "mcp.json");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as path from "node:path";
|
||||
import { Glob } from "bun";
|
||||
import { getProjectDir } from "./dirs";
|
||||
|
||||
export interface GlobPathsOptions {
|
||||
/** Base directory for glob patterns. Defaults to process.cwd(). */
|
||||
/** Base directory for glob patterns. Defaults to getProjectDir(). */
|
||||
cwd?: string;
|
||||
/** Glob exclusion patterns. */
|
||||
exclude?: string[];
|
||||
@@ -126,7 +127,7 @@ export async function loadGitignorePatterns(baseDir: string): Promise<string[]>
|
||||
|
||||
/**
|
||||
* Resolve filesystem paths matching glob patterns with optional exclude filters.
|
||||
* Returns paths relative to the provided cwd (or process.cwd()).
|
||||
* Returns paths relative to the provided cwd (or getProjectDir()).
|
||||
* Errors and abort/timeouts are surfaced to the caller.
|
||||
*/
|
||||
export async function globPaths(patterns: string | string[], options: GlobPathsOptions = {}): Promise<string[]> {
|
||||
@@ -140,11 +141,11 @@ export async function globPaths(patterns: string | string[], options: GlobPathsO
|
||||
let effectiveExclude = exclude ? [...baseExclude, ...exclude] : baseExclude;
|
||||
|
||||
if (gitignore) {
|
||||
const gitignorePatterns = await loadGitignorePatterns(cwd ?? process.cwd());
|
||||
const gitignorePatterns = await loadGitignorePatterns(cwd ?? getProjectDir());
|
||||
effectiveExclude = [...effectiveExclude, ...gitignorePatterns];
|
||||
}
|
||||
|
||||
const base = cwd ?? process.cwd();
|
||||
const base = cwd ?? getProjectDir();
|
||||
const allResults: string[] = [];
|
||||
|
||||
// Combine timeout and abort signals
|
||||
|
||||
Reference in New Issue
Block a user