From fcd7ff4f8482be999c56340b03c72760d7ab312d Mon Sep 17 00:00:00 2001 From: can1357 Date: Fri, 13 Feb 2026 15:06:05 +0100 Subject: [PATCH] refactor(dirs): centralized directory path utilities into @oh-my-pi/pi-utils/dirs module - Extracted directory path utilities from multiple packages into a centralized '@oh-my-pi/pi-utils/dirs' module. - Moved 30+ path helper functions (getAgentDir, getConfigRootDir, getPluginsDir, getMCPConfigPath, etc.) from scattered locations into a single shared utility module. - Consolidated APP_NAME, CONFIG_DIR_NAME, and VERSION constants into the centralized dirs module for reuse across packages. - Updated 70+ import statements across packages/ai, packages/coding-agent, packages/stats, and packages/tui to use the new centralized module. - Removed local path construction logic and replaced with utility function calls for improved maintainability and consistency. - Deleted packages/coding-agent/src/extensibility/plugins/paths.ts as its functions were moved to the centralized dirs module. --- packages/ai/src/storage.ts | 11 +- packages/ai/src/utils/oauth/kimi.ts | 6 +- packages/ai/test/oauth.ts | 5 +- packages/coding-agent/CHANGELOG.md | 3 +- packages/coding-agent/src/cli.ts | 2 +- packages/coding-agent/src/cli/args.ts | 2 +- packages/coding-agent/src/cli/config-cli.ts | 3 +- packages/coding-agent/src/cli/grep-cli.ts | 2 +- packages/coding-agent/src/cli/jupyter-cli.ts | 3 +- packages/coding-agent/src/cli/plugin-cli.ts | 3 +- packages/coding-agent/src/cli/setup-cli.ts | 5 +- packages/coding-agent/src/cli/shell-cli.ts | 2 +- packages/coding-agent/src/cli/stats-cli.ts | 3 +- packages/coding-agent/src/cli/update-cli.ts | 2 +- .../coding-agent/src/cli/web-search-cli.ts | 3 +- packages/coding-agent/src/commands/launch.ts | 2 +- packages/coding-agent/src/config.ts | 65 +---- .../coding-agent/src/config/keybindings.ts | 2 +- .../src/config/prompt-templates.ts | 8 +- packages/coding-agent/src/config/settings.ts | 2 +- packages/coding-agent/src/debug/index.ts | 2 +- .../coding-agent/src/debug/report-bundle.ts | 13 +- .../coding-agent/src/debug/system-info.ts | 2 +- .../coding-agent/src/discovery/helpers.ts | 7 +- .../coding-agent/src/export/custom-share.ts | 2 +- .../coding-agent/src/export/html/index.ts | 2 +- .../extensibility/custom-commands/loader.ts | 3 +- .../src/extensibility/plugins/index.ts | 7 - .../src/extensibility/plugins/installer.ts | 2 +- .../src/extensibility/plugins/loader.ts | 10 +- .../src/extensibility/plugins/manager.ts | 8 +- .../src/extensibility/plugins/paths.ts | 37 --- packages/coding-agent/src/index.ts | 2 +- packages/coding-agent/src/ipy/executor.ts | 2 +- .../src/ipy/gateway-coordinator.ts | 2 +- packages/coding-agent/src/ipy/modules.ts | 11 +- packages/coding-agent/src/ipy/runtime.ts | 5 +- packages/coding-agent/src/main.ts | 3 +- .../coding-agent/src/mcp/config-writer.ts | 14 +- .../src/modes/components/welcome.ts | 2 +- .../controllers/mcp-command-controller.ts | 9 +- .../modes/controllers/selector-controller.ts | 2 +- .../src/modes/interactive-mode.ts | 2 +- .../coding-agent/src/modes/theme/theme.ts | 2 +- packages/coding-agent/src/patch/index.ts | 19 +- packages/coding-agent/src/sdk.ts | 2 +- .../coding-agent/src/session/agent-session.ts | 2 +- .../coding-agent/src/session/agent-storage.ts | 2 +- .../src/session/history-storage.ts | 2 +- .../src/session/session-manager.ts | 2 +- .../src/ssh/connection-manager.ts | 7 +- packages/coding-agent/src/ssh/sshfs-mount.ts | 5 +- packages/coding-agent/src/system-prompt.ts | 4 +- packages/coding-agent/src/task/worktree.ts | 3 +- packages/coding-agent/src/tools/read.ts | 5 +- .../coding-agent/src/utils/tools-manager.ts | 2 +- packages/coding-agent/src/web/search/auth.ts | 2 +- .../src/web/search/providers/codex.ts | 2 +- .../src/web/search/providers/gemini.ts | 2 +- .../src/web/search/providers/perplexity.ts | 2 +- ...gent-session-auto-compaction-queue.test.ts | 4 +- .../test/core/python-modules.test.ts | 17 +- .../test/extensions-discovery.test.ts | 3 +- .../test/extensions-runner.test.ts | 3 +- packages/coding-agent/test/sdk-skills.test.ts | 3 +- .../test/settings-manager.test.ts | 3 +- packages/stats/package.json | 131 ++++----- packages/stats/src/db.ts | 7 +- packages/stats/src/parser.ts | 16 +- packages/tui/src/tui.ts | 6 +- packages/utils/package.json | 4 + packages/utils/src/dirs.ts | 250 ++++++++++++++++++ packages/utils/src/logger.ts | 8 +- 73 files changed, 456 insertions(+), 347 deletions(-) delete mode 100644 packages/coding-agent/src/extensibility/plugins/paths.ts create mode 100644 packages/utils/src/dirs.ts diff --git a/packages/ai/src/storage.ts b/packages/ai/src/storage.ts index cf674e22d..b32b9dfb1 100644 --- a/packages/ai/src/storage.ts +++ b/packages/ai/src/storage.ts @@ -5,9 +5,8 @@ import { Database, type Statement } from "bun:sqlite"; import * as fs from "node:fs/promises"; -import * as os from "node:os"; import * as path from "node:path"; -import { $env } from "@oh-my-pi/pi-utils"; +import { getAgentDir } from "@oh-my-pi/pi-utils/dirs"; import type { OAuthCredentials } from "./utils/oauth/types"; type AuthCredential = { type: "api_key"; key: string } | ({ type: "oauth" } & OAuthCredentials); @@ -21,14 +20,6 @@ type AuthRow = { updated_at: number; }; -/** - * Get the agent config directory (e.g., ~/.omp/agent/) - */ -function getAgentDir(): string { - const configDir = $env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".omp", "agent"); - return configDir; -} - /** * Get path to agent.db */ diff --git a/packages/ai/src/utils/oauth/kimi.ts b/packages/ai/src/utils/oauth/kimi.ts index a83d499d3..1b3730974 100644 --- a/packages/ai/src/utils/oauth/kimi.ts +++ b/packages/ai/src/utils/oauth/kimi.ts @@ -7,6 +7,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { $env, abortableSleep, isEnoent } from "@oh-my-pi/pi-utils"; +import { getAgentDir } from "@oh-my-pi/pi-utils/dirs"; import packageJson from "../../../package.json" with { type: "json" }; import type { OAuthController, OAuthCredentials } from "./types"; @@ -36,11 +37,6 @@ interface TokenResponse { interval?: number; } -function getAgentDir(): string { - const configDir = $env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".omp", "agent"); - return configDir; -} - function resolveOAuthHost(): string { return $env.KIMI_CODE_OAUTH_HOST || $env.KIMI_OAUTH_HOST || DEFAULT_OAUTH_HOST; } diff --git a/packages/ai/test/oauth.ts b/packages/ai/test/oauth.ts index 22f63f128..93df3d7c9 100644 --- a/packages/ai/test/oauth.ts +++ b/packages/ai/test/oauth.ts @@ -7,11 +7,10 @@ * E2E tests are disabled by default. Set E2E=1 environment variable to enable. */ import * as fs from "node:fs/promises"; -import * as os from "node:os"; -import * as path from "node:path"; import { getOAuthApiKey } from "@oh-my-pi/pi-ai/utils/oauth"; import type { OAuthCredentials, OAuthProvider } from "@oh-my-pi/pi-ai/utils/oauth/types"; import { isEnoent } from "@oh-my-pi/pi-utils"; +import { getTestAuthPath } from "@oh-my-pi/pi-utils/dirs"; /** * E2E tests require explicit opt-in via E2E=1 environment variable. @@ -28,7 +27,7 @@ export function e2eApiKey(envVar: string): string | undefined { return Bun.env[envVar]; } -const AUTH_PATH = path.join(os.homedir(), ".omp", "agent", "testauth.db"); +const AUTH_PATH = getTestAuthPath(); type ApiKeyCredential = { type: "api_key"; diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index f8d8450ef..690c9a19b 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,7 +1,6 @@ # Changelog ## [Unreleased] - ### Added - Comprehensive `models.yml` provider integration guide documenting custom model registration, provider overrides, API adapters, merge behavior, and practical integration patterns for Ollama, vLLM, LM Studio, and proxy endpoints @@ -9,6 +8,8 @@ ### Changed +- Moved directory path utilities from `src/config.ts` to `@oh-my-pi/pi-utils/dirs` for shared use across packages +- Updated imports throughout codebase to use centralized directory path functions from `@oh-my-pi/pi-utils/dirs` - Updated interactive bash terminal UI label from 'InteractiveTerm' to 'Console' for clarity - Enhanced bash execution environment with comprehensive non-interactive defaults for pagers, editors, and package managers to prevent command blocking and interactive prompts - Updated custom models configuration to use `~/.omp/agent/models.yml` (YAML format) while maintaining backward compatibility with legacy `models.json` diff --git a/packages/coding-agent/src/cli.ts b/packages/coding-agent/src/cli.ts index 71a0845c8..bfad7a085 100755 --- a/packages/coding-agent/src/cli.ts +++ b/packages/coding-agent/src/cli.ts @@ -4,7 +4,7 @@ * lightweight CLI runner from pi-utils. */ import { type CommandEntry, run } from "@oh-my-pi/pi-utils/cli"; -import { APP_NAME, VERSION } from "./config"; +import { APP_NAME, VERSION } from "@oh-my-pi/pi-utils/dirs"; // Detect known Bun errata that cause TUI crashes (e.g. Bun.stringWidth mishandling OSC sequences). if (Bun.stringWidth("\x1b[0m\x1b]8;;\x07") !== 0) { diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 46082568d..dc5b786c7 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -3,8 +3,8 @@ */ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import { logger } from "@oh-my-pi/pi-utils"; +import { APP_NAME, CONFIG_DIR_NAME } from "@oh-my-pi/pi-utils/dirs"; import chalk from "chalk"; -import { APP_NAME, CONFIG_DIR_NAME } from "../config"; import { BUILTIN_TOOLS } from "../tools"; export type Mode = "text" | "json" | "rpc"; diff --git a/packages/coding-agent/src/cli/config-cli.ts b/packages/coding-agent/src/cli/config-cli.ts index c24260551..2b979ae8e 100644 --- a/packages/coding-agent/src/cli/config-cli.ts +++ b/packages/coding-agent/src/cli/config-cli.ts @@ -4,8 +4,9 @@ * Handles `omp config ` subcommands for managing settings. * Uses settings-defs as the source of truth for available settings. */ + +import { APP_NAME, getAgentDir } from "@oh-my-pi/pi-utils/dirs"; import chalk from "chalk"; -import { APP_NAME, getAgentDir } from "../config"; import { getDefault, getEnumValues, diff --git a/packages/coding-agent/src/cli/grep-cli.ts b/packages/coding-agent/src/cli/grep-cli.ts index af9d83a8d..72f3b719d 100644 --- a/packages/coding-agent/src/cli/grep-cli.ts +++ b/packages/coding-agent/src/cli/grep-cli.ts @@ -5,8 +5,8 @@ */ import * as path from "node:path"; import { grep } from "@oh-my-pi/pi-natives"; +import { APP_NAME } from "@oh-my-pi/pi-utils/dirs"; import chalk from "chalk"; -import { APP_NAME } from "../config"; export interface GrepCommandArgs { pattern: string; diff --git a/packages/coding-agent/src/cli/jupyter-cli.ts b/packages/coding-agent/src/cli/jupyter-cli.ts index d2454c838..ba5a0861a 100644 --- a/packages/coding-agent/src/cli/jupyter-cli.ts +++ b/packages/coding-agent/src/cli/jupyter-cli.ts @@ -3,8 +3,9 @@ * * Handles `omp jupyter` subcommand for managing the shared Python gateway. */ + +import { APP_NAME } from "@oh-my-pi/pi-utils/dirs"; import chalk from "chalk"; -import { APP_NAME } from "../config"; import { getGatewayStatus, shutdownSharedGateway } from "../ipy/gateway-coordinator"; export type JupyterAction = "kill" | "status"; diff --git a/packages/coding-agent/src/cli/plugin-cli.ts b/packages/coding-agent/src/cli/plugin-cli.ts index 458e233ca..5f2897d66 100644 --- a/packages/coding-agent/src/cli/plugin-cli.ts +++ b/packages/coding-agent/src/cli/plugin-cli.ts @@ -3,8 +3,9 @@ * * Handles `omp plugin ` subcommands for plugin lifecycle management. */ + +import { APP_NAME } from "@oh-my-pi/pi-utils/dirs"; import chalk from "chalk"; -import { APP_NAME } from "../config"; import { PluginManager, parseSettingValue, validateSetting } from "../extensibility/plugins"; import { theme } from "../modes/theme/theme"; diff --git a/packages/coding-agent/src/cli/setup-cli.ts b/packages/coding-agent/src/cli/setup-cli.ts index a6b12f9c6..5088b52da 100644 --- a/packages/coding-agent/src/cli/setup-cli.ts +++ b/packages/coding-agent/src/cli/setup-cli.ts @@ -3,11 +3,10 @@ * * Handles `omp setup ` to install dependencies for optional features. */ -import * as os from "node:os"; import * as path from "node:path"; +import { APP_NAME, getPythonEnvDir } from "@oh-my-pi/pi-utils/dirs"; import { $ } from "bun"; import chalk from "chalk"; -import { APP_NAME, CONFIG_DIR_NAME } from "../config"; import { theme } from "../modes/theme/theme"; export type SetupComponent = "python"; @@ -23,7 +22,7 @@ export interface SetupCommandArgs { const VALID_COMPONENTS: SetupComponent[] = ["python"]; const PYTHON_PACKAGES = ["jupyter_kernel_gateway", "ipykernel"]; -const MANAGED_PYTHON_ENV = path.join(os.homedir(), CONFIG_DIR_NAME, "python-env"); +const MANAGED_PYTHON_ENV = getPythonEnvDir(); /** * Parse setup subcommand arguments. diff --git a/packages/coding-agent/src/cli/shell-cli.ts b/packages/coding-agent/src/cli/shell-cli.ts index b0c3492da..c6254deb0 100644 --- a/packages/coding-agent/src/cli/shell-cli.ts +++ b/packages/coding-agent/src/cli/shell-cli.ts @@ -6,8 +6,8 @@ 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 chalk from "chalk"; -import { APP_NAME } from "../config"; import { Settings } from "../config/settings"; import { getOrCreateSnapshot } from "../utils/shell-snapshot"; diff --git a/packages/coding-agent/src/cli/stats-cli.ts b/packages/coding-agent/src/cli/stats-cli.ts index f79b1f144..c90bf16d0 100644 --- a/packages/coding-agent/src/cli/stats-cli.ts +++ b/packages/coding-agent/src/cli/stats-cli.ts @@ -3,8 +3,9 @@ * * Handles `omp stats` subcommand for viewing AI usage statistics. */ + +import { APP_NAME } from "@oh-my-pi/pi-utils/dirs"; import chalk from "chalk"; -import { APP_NAME } from "../config"; import { openPath } from "../utils/open"; // ============================================================================= diff --git a/packages/coding-agent/src/cli/update-cli.ts b/packages/coding-agent/src/cli/update-cli.ts index 288f29186..bf8c96438 100644 --- a/packages/coding-agent/src/cli/update-cli.ts +++ b/packages/coding-agent/src/cli/update-cli.ts @@ -9,8 +9,8 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { pipeline } from "node:stream/promises"; import { isEnoent } from "@oh-my-pi/pi-utils"; +import { APP_NAME, VERSION } from "@oh-my-pi/pi-utils/dirs"; import chalk from "chalk"; -import { APP_NAME, VERSION } from "../config"; import { theme } from "../modes/theme/theme"; /** diff --git a/packages/coding-agent/src/cli/web-search-cli.ts b/packages/coding-agent/src/cli/web-search-cli.ts index 1b791e0b4..e575082f5 100644 --- a/packages/coding-agent/src/cli/web-search-cli.ts +++ b/packages/coding-agent/src/cli/web-search-cli.ts @@ -3,8 +3,9 @@ * * Handles `omp q`/`omp web-search` subcommands for testing web search providers. */ + +import { APP_NAME } from "@oh-my-pi/pi-utils/dirs"; import chalk from "chalk"; -import { APP_NAME } from "../config"; import { initTheme, theme } from "../modes/theme/theme"; import { runSearchQuery, type SearchParams } from "../web/search/index"; import { renderSearchResult } from "../web/search/render"; diff --git a/packages/coding-agent/src/commands/launch.ts b/packages/coding-agent/src/commands/launch.ts index 211c0c15b..d0e14a34b 100644 --- a/packages/coding-agent/src/commands/launch.ts +++ b/packages/coding-agent/src/commands/launch.ts @@ -2,8 +2,8 @@ * Root command for the coding agent CLI. */ import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli"; +import { APP_NAME } from "@oh-my-pi/pi-utils/dirs"; import { parseArgs } from "../cli/args"; -import { APP_NAME } from "../config"; import { runRootCommand } from "../main"; export default class Index extends Command { diff --git a/packages/coding-agent/src/config.ts b/packages/coding-agent/src/config.ts index 57ca65f19..67223e77b 100644 --- a/packages/coding-agent/src/config.ts +++ b/packages/coding-agent/src/config.ts @@ -1,25 +1,15 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; -import { $env, isEnoent, logger } from "@oh-my-pi/pi-utils"; +import { isEnoent, logger } from "@oh-my-pi/pi-utils"; +import { CONFIG_DIR_NAME, getAgentDir } 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"; import { JSONC, YAML } from "bun"; -// Embed package.json at build time for config -import packageJson from "../package.json" with { type: "json" }; - -// ============================================================================= -// App Config (from embedded package.json) -// ============================================================================= - -export const APP_NAME: string = (packageJson as { ompConfig?: { name?: string } }).ompConfig?.name || "omp"; -export const CONFIG_DIR_NAME: string = - (packageJson as { ompConfig?: { configDir?: string } }).ompConfig?.configDir || ".omp"; -export const VERSION: string = (packageJson as { version: string }).version; const priorityList = [ - { dir: ".omp", globalAgentDir: ".omp/agent" }, + { dir: CONFIG_DIR_NAME, globalAgentDir: `${CONFIG_DIR_NAME}/agent` }, { dir: ".claude" }, { dir: ".codex" }, { dir: ".gemini" }, @@ -253,55 +243,6 @@ export class ConfigFile implements IConfigFile { } } -/** Get the agent config directory (e.g., ~/.omp/agent/) */ -export function getAgentDir(): string { - return $env.PI_CODING_AGENT_DIR || path.join(os.homedir(), CONFIG_DIR_NAME, "agent"); -} - -/** Get path to user's custom themes directory */ -export function getCustomThemesDir(): string { - return path.join(getAgentDir(), "themes"); -} - -/** - * Gets the path to agent.db (SQLite database for settings and auth storage). - * @param agentDir - Base agent directory, defaults to ~/.omp/agent - * @returns Absolute path to the agent.db file - */ -export function getAgentDbPath(agentDir: string = getAgentDir()): string { - return path.join(agentDir, "agent.db"); -} - -/** Get path to tools directory */ -export function getToolsDir(): string { - return path.join(getAgentDir(), "tools"); -} - -/** Get path to slash commands directory */ -export function getCommandsDir(): string { - return path.join(getAgentDir(), "commands"); -} - -/** Get path to prompts directory */ -export function getPromptsDir(): string { - return path.join(getAgentDir(), "prompts"); -} - -/** Get path to content-addressed blob store directory */ -export function getBlobsDir(): string { - return path.join(getAgentDir(), "blobs"); -} - -/** Get path to sessions directory */ -export function getSessionsDir(): string { - return path.join(getAgentDir(), "sessions"); -} - -/** Get path to debug log file */ -export function getDebugLogPath(): string { - return path.join(getAgentDir(), `${APP_NAME}-debug.log`); -} - // ============================================================================= // Multi-Config Directory Helpers // ============================================================================= diff --git a/packages/coding-agent/src/config/keybindings.ts b/packages/coding-agent/src/config/keybindings.ts index 43f091cf0..0661e4395 100644 --- a/packages/coding-agent/src/config/keybindings.ts +++ b/packages/coding-agent/src/config/keybindings.ts @@ -9,7 +9,7 @@ import { setEditorKeybindings, } from "@oh-my-pi/pi-tui"; import { logger } from "@oh-my-pi/pi-utils"; -import { getAgentDir } from "../config"; +import { getAgentDir } from "@oh-my-pi/pi-utils/dirs"; /** * Application-level actions (coding agent specific). diff --git a/packages/coding-agent/src/config/prompt-templates.ts b/packages/coding-agent/src/config/prompt-templates.ts index d2f543fb9..05482485c 100644 --- a/packages/coding-agent/src/config/prompt-templates.ts +++ b/packages/coding-agent/src/config/prompt-templates.ts @@ -1,8 +1,8 @@ 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 Handlebars from "handlebars"; -import { CONFIG_DIR_NAME, getPromptsDir } from "../config"; import { computeLineHash } from "../patch/hashline"; import { jtdToTypeScript } from "../tools/jtd-to-typescript"; import { parseFrontmatter } from "../utils/frontmatter"; @@ -468,7 +468,7 @@ export interface LoadPromptTemplatesOptions { /** * Load all prompt templates from: * 1. Global: agentDir/prompts/ - * 2. Project: cwd/{CONFIG_DIR_NAME}/prompts/ + * 2. Project: cwd/.omp/prompts/ */ export async function loadPromptTemplates(options: LoadPromptTemplatesOptions = {}): Promise { const resolvedCwd = options.cwd ?? process.cwd(); @@ -481,8 +481,8 @@ export async function loadPromptTemplates(options: LoadPromptTemplatesOptions = const globalPromptsDir = options.agentDir ? path.join(options.agentDir, "prompts") : resolvedAgentDir; templates.push(...(await loadTemplatesFromDir(globalPromptsDir, "user"))); - // 2. Load project templates from cwd/{CONFIG_DIR_NAME}/prompts/ - const projectPromptsDir = path.resolve(resolvedCwd, CONFIG_DIR_NAME, "prompts"); + // 2. Load project templates from cwd/.omp/prompts/ + const projectPromptsDir = getProjectPromptsDir(resolvedCwd); templates.push(...(await loadTemplatesFromDir(projectPromptsDir, "project"))); return templates; diff --git a/packages/coding-agent/src/config/settings.ts b/packages/coding-agent/src/config/settings.ts index 4c8e3d5e5..d68705c14 100644 --- a/packages/coding-agent/src/config/settings.ts +++ b/packages/coding-agent/src/config/settings.ts @@ -14,9 +14,9 @@ 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 { YAML } from "bun"; import { type Settings as SettingsCapabilityItem, settingsCapability } from "../capability/settings"; -import { getAgentDbPath, getAgentDir } from "../config"; import type { ModelRole } from "../config/model-registry"; import { loadCapability } from "../discovery"; import { setColorBlindMode, setSymbolPreset, setTheme } from "../modes/theme/theme"; diff --git a/packages/coding-agent/src/debug/index.ts b/packages/coding-agent/src/debug/index.ts index a4b3b8133..0ed83c130 100644 --- a/packages/coding-agent/src/debug/index.ts +++ b/packages/coding-agent/src/debug/index.ts @@ -6,7 +6,7 @@ import * as fs from "node:fs/promises"; import { getWorkProfile } from "@oh-my-pi/pi-natives"; import { Container, Loader, type SelectItem, SelectList, Spacer, Text } from "@oh-my-pi/pi-tui"; -import { getSessionsDir } from "../config"; +import { getSessionsDir } from "@oh-my-pi/pi-utils/dirs"; import { DynamicBorder } from "../modes/components/dynamic-border"; import { getSelectListTheme, getSymbolTheme, theme } from "../modes/theme/theme"; import type { InteractiveModeContext } from "../modes/types"; diff --git a/packages/coding-agent/src/debug/report-bundle.ts b/packages/coding-agent/src/debug/report-bundle.ts index 7b1ff3658..cfa75f49d 100644 --- a/packages/coding-agent/src/debug/report-bundle.ts +++ b/packages/coding-agent/src/debug/report-bundle.ts @@ -4,24 +4,13 @@ * Creates a .tar.gz archive with session data, logs, system info, and optional profiling data. */ import * as fs from "node:fs/promises"; -import * as os from "node:os"; import * as path from "node:path"; import type { WorkProfile } from "@oh-my-pi/pi-natives"; import { isEnoent } from "@oh-my-pi/pi-utils"; +import { getLogPath, getReportsDir } from "@oh-my-pi/pi-utils/dirs"; import type { CpuProfile, HeapSnapshot } from "./profiler"; import { collectSystemInfo, sanitizeEnv } from "./system-info"; -/** Reports directory path */ -export function getReportsDir(): string { - return path.join(os.homedir(), ".omp", "reports"); -} - -/** Get today's log file path */ -function getLogPath(): string { - const today = new Date().toISOString().slice(0, 10); - return path.join(os.homedir(), ".omp", "logs", `omp.${today}.log`); -} - /** Read last N lines from a file */ async function readLastLines(filePath: string, n: number): Promise { try { diff --git a/packages/coding-agent/src/debug/system-info.ts b/packages/coding-agent/src/debug/system-info.ts index 8f92321a5..e594a1861 100644 --- a/packages/coding-agent/src/debug/system-info.ts +++ b/packages/coding-agent/src/debug/system-info.ts @@ -2,7 +2,7 @@ * System information collection for debug reports. */ import * as os from "node:os"; -import { VERSION } from "../config"; +import { VERSION } from "@oh-my-pi/pi-utils/dirs"; export interface SystemInfo { os: string; diff --git a/packages/coding-agent/src/discovery/helpers.ts b/packages/coding-agent/src/discovery/helpers.ts index a4b627a95..97f33bd1d 100644 --- a/packages/coding-agent/src/discovery/helpers.ts +++ b/packages/coding-agent/src/discovery/helpers.ts @@ -4,6 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; +import { CONFIG_DIR_NAME } from "@oh-my-pi/pi-utils/dirs"; import { readDirEntries, readFile } from "../capability/fs"; import type { Skill, SkillFrontmatter } from "../capability/skill"; import type { LoadContext, LoadResult, SourceMeta } from "../capability/types"; @@ -39,9 +40,9 @@ export function expandPath(p: string): string { */ export const SOURCE_PATHS = { native: { - userBase: ".omp", - userAgent: ".omp/agent", - projectDir: ".omp", + userBase: CONFIG_DIR_NAME, + userAgent: `${CONFIG_DIR_NAME}/agent`, + projectDir: CONFIG_DIR_NAME, }, claude: { userBase: ".claude", diff --git a/packages/coding-agent/src/export/custom-share.ts b/packages/coding-agent/src/export/custom-share.ts index 4601751c2..1e9ec5254 100644 --- a/packages/coding-agent/src/export/custom-share.ts +++ b/packages/coding-agent/src/export/custom-share.ts @@ -6,7 +6,7 @@ */ import * as fs from "node:fs"; import * as path from "node:path"; -import { getAgentDir } from "../config"; +import { getAgentDir } from "@oh-my-pi/pi-utils/dirs"; export interface CustomShareResult { /** URL to display/open (optional - script may handle everything itself) */ diff --git a/packages/coding-agent/src/export/html/index.ts b/packages/coding-agent/src/export/html/index.ts index 7dd3ea1b8..697aab72f 100644 --- a/packages/coding-agent/src/export/html/index.ts +++ b/packages/coding-agent/src/export/html/index.ts @@ -1,7 +1,7 @@ import * as path from "node:path"; import type { AgentState } from "@oh-my-pi/pi-agent-core"; import { isEnoent } from "@oh-my-pi/pi-utils"; -import { APP_NAME } from "../../config"; +import { APP_NAME } from "@oh-my-pi/pi-utils/dirs"; import { getResolvedThemeColors, getThemeExportColors } from "../../modes/theme/theme"; import { type SessionEntry, type SessionHeader, SessionManager } from "../../session/session-manager"; // Pre-generated template (created by scripts/generate-template.ts at publish time) diff --git a/packages/coding-agent/src/extensibility/custom-commands/loader.ts b/packages/coding-agent/src/extensibility/custom-commands/loader.ts index f563982e0..3d541dda1 100644 --- a/packages/coding-agent/src/extensibility/custom-commands/loader.ts +++ b/packages/coding-agent/src/extensibility/custom-commands/loader.ts @@ -8,8 +8,9 @@ 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 * as typebox from "@sinclair/typebox"; -import { getAgentDir, getConfigDirs } from "../../config"; +import { getConfigDirs } from "../../config"; import { execCommand } from "../../exec/exec"; import { ReviewCommand } from "./bundled/review"; import type { diff --git a/packages/coding-agent/src/extensibility/plugins/index.ts b/packages/coding-agent/src/extensibility/plugins/index.ts index c3b3c0e56..c53cda777 100644 --- a/packages/coding-agent/src/extensibility/plugins/index.ts +++ b/packages/coding-agent/src/extensibility/plugins/index.ts @@ -14,13 +14,6 @@ export { } from "./loader"; export { PluginManager, parseSettingValue, validateSetting } from "./manager"; export { extractPackageName, formatPluginSpec, parsePluginSpec } from "./parser"; -export { - getPluginsDir, - getPluginsLockfile, - getPluginsNodeModules, - getPluginsPackageJson, - getProjectPluginOverrides, -} from "./paths"; export type { BooleanSetting, DoctorCheck, diff --git a/packages/coding-agent/src/extensibility/plugins/installer.ts b/packages/coding-agent/src/extensibility/plugins/installer.ts index 76b161ed3..586cde57e 100644 --- a/packages/coding-agent/src/extensibility/plugins/installer.ts +++ b/packages/coding-agent/src/extensibility/plugins/installer.ts @@ -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 "../../config"; +import { getAgentDir } from "@oh-my-pi/pi-utils/dirs"; import type { InstalledPlugin } from "./types"; const PLUGINS_DIR = path.join(getAgentDir(), "plugins"); diff --git a/packages/coding-agent/src/extensibility/plugins/loader.ts b/packages/coding-agent/src/extensibility/plugins/loader.ts index 08f9780ba..631e384d2 100644 --- a/packages/coding-agent/src/extensibility/plugins/loader.ts +++ b/packages/coding-agent/src/extensibility/plugins/loader.ts @@ -7,12 +7,8 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { isEnoent } from "@oh-my-pi/pi-utils"; -import { - getAllProjectPluginOverridePaths, - getPluginsLockfile, - getPluginsNodeModules, - getPluginsPackageJson, -} from "./paths"; +import { getPluginsLockfile, getPluginsNodeModules, getPluginsPackageJson } from "@oh-my-pi/pi-utils/dirs"; +import { getConfigDirPaths } from "../../config"; import type { InstalledPlugin, PluginManifest, PluginRuntimeConfig, ProjectPluginOverrides } from "./types"; // ============================================================================= @@ -36,7 +32,7 @@ async function loadRuntimeConfig(): Promise { * Load project-local plugin overrides (checks .omp and .pi directories). */ async function loadProjectOverrides(cwd: string): Promise { - for (const overridesPath of getAllProjectPluginOverridePaths(cwd)) { + for (const overridesPath of getConfigDirPaths("plugin-overrides.json", { user: false, cwd })) { try { return await Bun.file(overridesPath).json(); } catch (err) { diff --git a/packages/coding-agent/src/extensibility/plugins/manager.ts b/packages/coding-agent/src/extensibility/plugins/manager.ts index 0ac00052a..eb639364a 100644 --- a/packages/coding-agent/src/extensibility/plugins/manager.ts +++ b/packages/coding-agent/src/extensibility/plugins/manager.ts @@ -1,14 +1,14 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { isEnoent, logger } from "@oh-my-pi/pi-utils"; -import { extractPackageName, parsePluginSpec } from "./parser"; import { getPluginsDir, getPluginsLockfile, getPluginsNodeModules, getPluginsPackageJson, - getProjectPluginOverrides, -} from "./paths"; + getProjectPluginOverridesPath, +} from "@oh-my-pi/pi-utils/dirs"; +import { extractPackageName, parsePluginSpec } from "./parser"; import type { DoctorCheck, DoctorOptions, @@ -82,7 +82,7 @@ export class PluginManager { } async #loadProjectOverrides(): Promise { - const overridesPath = getProjectPluginOverrides(this.#cwd); + const overridesPath = getProjectPluginOverridesPath(this.#cwd); try { return await Bun.file(overridesPath).json(); } catch (err) { diff --git a/packages/coding-agent/src/extensibility/plugins/paths.ts b/packages/coding-agent/src/extensibility/plugins/paths.ts deleted file mode 100644 index 952ca7c07..000000000 --- a/packages/coding-agent/src/extensibility/plugins/paths.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as os from "node:os"; -import * as path from "node:path"; -import { CONFIG_DIR_NAME, getConfigDirPaths } from "../../config"; - -// ============================================================================= -// Plugin Directory Paths -// ============================================================================= - -/** Root plugin directory: ~/.omp/plugins (not under agent/) */ -export function getPluginsDir(): string { - return path.join(os.homedir(), CONFIG_DIR_NAME, "plugins"); -} - -/** Where npm installs packages: ~/.omp/plugins/node_modules */ -export function getPluginsNodeModules(): string { - return path.join(getPluginsDir(), "node_modules"); -} - -/** Plugin manifest: ~/.omp/plugins/package.json */ -export function getPluginsPackageJson(): string { - return path.join(getPluginsDir(), "package.json"); -} - -/** Plugin lock file: ~/.omp/plugins/omp-plugins.lock.json */ -export function getPluginsLockfile(): string { - return path.join(getPluginsDir(), "omp-plugins.lock.json"); -} - -/** Project-local plugin overrides: .omp/plugin-overrides.json (primary) */ -export function getProjectPluginOverrides(cwd: string): string { - return path.join(cwd, CONFIG_DIR_NAME, "plugin-overrides.json"); -} - -/** All possible project plugin override paths (primary + legacy) */ -export function getAllProjectPluginOverridePaths(cwd: string): string[] { - return getConfigDirPaths("plugin-overrides.json", { user: false, cwd }); -} diff --git a/packages/coding-agent/src/index.ts b/packages/coding-agent/src/index.ts index e06160a5f..c3be2e286 100644 --- a/packages/coding-agent/src/index.ts +++ b/packages/coding-agent/src/index.ts @@ -7,7 +7,7 @@ export { StringEnum } from "@oh-my-pi/pi-ai"; export { Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui"; // Logging export { logger } from "@oh-my-pi/pi-utils"; -export { getAgentDir, VERSION } from "./config"; +export { getAgentDir, VERSION } from "@oh-my-pi/pi-utils/dirs"; export { formatKeyHint, formatKeyHints } from "./config/keybindings"; export { ModelRegistry } from "./config/model-registry"; // Prompt templates diff --git a/packages/coding-agent/src/ipy/executor.ts b/packages/coding-agent/src/ipy/executor.ts index eba0c3c22..d0a73c99d 100644 --- a/packages/coding-agent/src/ipy/executor.ts +++ b/packages/coding-agent/src/ipy/executor.ts @@ -1,6 +1,6 @@ import * as path from "node:path"; import { $env, isEnoent, logger } from "@oh-my-pi/pi-utils"; -import { getAgentDir } from "../config"; +import { getAgentDir } from "@oh-my-pi/pi-utils/dirs"; import { OutputSink } from "../session/streaming-output"; import { time } from "../utils/timings"; import { shutdownSharedGateway } from "./gateway-coordinator"; diff --git a/packages/coding-agent/src/ipy/gateway-coordinator.ts b/packages/coding-agent/src/ipy/gateway-coordinator.ts index 4b07807f8..292e8f566 100644 --- a/packages/coding-agent/src/ipy/gateway-coordinator.ts +++ b/packages/coding-agent/src/ipy/gateway-coordinator.ts @@ -2,8 +2,8 @@ import * as fs from "node:fs"; import { createServer } from "node:net"; import * as path from "node:path"; import { isEnoent, logger, procmgr } from "@oh-my-pi/pi-utils"; +import { getAgentDir } from "@oh-my-pi/pi-utils/dirs"; import type { Subprocess } from "bun"; -import { getAgentDir } from "../config"; import { Settings } from "../config/settings"; import { getOrCreateSnapshot } from "../utils/shell-snapshot"; import { time } from "../utils/timings"; diff --git a/packages/coding-agent/src/ipy/modules.ts b/packages/coding-agent/src/ipy/modules.ts index 871f6473a..7bc605bc7 100644 --- a/packages/coding-agent/src/ipy/modules.ts +++ b/packages/coding-agent/src/ipy/modules.ts @@ -1,6 +1,6 @@ import * as fs from "node:fs/promises"; -import * as os from "node:os"; import * as path from "node:path"; +import { getAgentModulesDir, getProjectModulesDir } from "@oh-my-pi/pi-utils/dirs"; export type PythonModuleSource = "user" | "project"; @@ -26,8 +26,8 @@ export interface PythonModuleExecutor { export interface DiscoverPythonModulesOptions { /** Working directory for project-level modules. Default: process.cwd() */ cwd?: string; - /** Home directory for user-level modules. Default: os.homedir() */ - homeDir?: string; + /** Agent directory for user-level modules. Default: from getAgentDir() */ + agentDir?: string; } interface ModuleCandidate { @@ -66,10 +66,9 @@ async function readModuleContent(candidate: ModuleCandidate): Promise { const cwd = options.cwd ?? process.cwd(); - const homeDir = options.homeDir ?? os.homedir(); - const userDir = path.join(homeDir, ".omp", "agent", "modules"); - const projectDir = path.resolve(cwd, ".omp", "modules"); + const userDir = getAgentModulesDir(options.agentDir); + const projectDir = getProjectModulesDir(cwd); const userCandidates = await listModuleCandidates(userDir, "user"); const projectCandidates = await listModuleCandidates(projectDir, "project"); diff --git a/packages/coding-agent/src/ipy/runtime.ts b/packages/coding-agent/src/ipy/runtime.ts index ef9ccd25d..4c4fd18c4 100644 --- a/packages/coding-agent/src/ipy/runtime.ts +++ b/packages/coding-agent/src/ipy/runtime.ts @@ -5,11 +5,10 @@ * for both the shared gateway and local kernel spawning. */ import * as fs from "node:fs"; -import * as os from "node:os"; import * as path from "node:path"; import { $env } from "@oh-my-pi/pi-utils"; -import { CONFIG_DIR_NAME } from "../config"; +import { getPythonEnvDir } from "@oh-my-pi/pi-utils/dirs"; const DEFAULT_ENV_ALLOWLIST = new Set([ "PATH", @@ -106,7 +105,7 @@ function resolvePathKey(env: Record): string { } function resolveManagedPythonEnv(): string { - return path.join(os.homedir(), CONFIG_DIR_NAME, "python-env"); + return getPythonEnvDir(); } function resolveManagedPythonCandidate(): { venvPath: string; pythonPath: string } { diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 4bf94a43b..e32fbc3fd 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -10,12 +10,13 @@ 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 chalk from "chalk"; import type { Args } from "./cli/args"; import { processFileArguments } from "./cli/file-processor"; import { listModels } from "./cli/list-models"; import { selectSession } from "./cli/session-picker"; -import { findConfigFile, VERSION } from "./config"; +import { findConfigFile } from "./config"; import { ModelRegistry, ModelsConfigFile } from "./config/model-registry"; import { parseModelPattern, parseModelString, resolveModelScope, type ScopedModel } from "./config/model-resolver"; import { Settings, settings } from "./config/settings"; diff --git a/packages/coding-agent/src/mcp/config-writer.ts b/packages/coding-agent/src/mcp/config-writer.ts index fb43e7889..87fe459a0 100644 --- a/packages/coding-agent/src/mcp/config-writer.ts +++ b/packages/coding-agent/src/mcp/config-writer.ts @@ -4,24 +4,12 @@ * Utilities for reading/writing .omp/mcp.json files at user or project level. */ import * as fs from "node:fs"; -import * as os from "node:os"; import * as path from "node:path"; import { isEnoent } from "@oh-my-pi/pi-utils"; + import { validateServerConfig } from "./config"; import type { MCPConfigFile, MCPServerConfig } from "./types"; -/** - * Get the path to the MCP config file. - * @param scope - "user" for ~/.omp/mcp.json or "project" for .omp/mcp.json - * @param cwd - Current working directory (used for project scope) - */ -export function getMCPConfigPath(scope: "user" | "project", cwd: string): string { - if (scope === "user") { - return path.join(os.homedir(), ".omp", "mcp.json"); - } - return path.join(cwd, ".omp", "mcp.json"); -} - /** * Read an MCP config file. * Returns empty config if file doesn't exist. diff --git a/packages/coding-agent/src/modes/components/welcome.ts b/packages/coding-agent/src/modes/components/welcome.ts index 44d9a8f34..cc806b461 100644 --- a/packages/coding-agent/src/modes/components/welcome.ts +++ b/packages/coding-agent/src/modes/components/welcome.ts @@ -1,5 +1,5 @@ import { type Component, padding, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui"; -import { APP_NAME } from "../../config"; +import { APP_NAME } from "@oh-my-pi/pi-utils/dirs"; import { theme } from "../../modes/theme/theme"; export interface RecentSession { diff --git a/packages/coding-agent/src/modes/controllers/mcp-command-controller.ts b/packages/coding-agent/src/modes/controllers/mcp-command-controller.ts index f20e959bc..303bbe34c 100644 --- a/packages/coding-agent/src/modes/controllers/mcp-command-controller.ts +++ b/packages/coding-agent/src/modes/controllers/mcp-command-controller.ts @@ -4,16 +4,11 @@ * 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 type { SourceMeta } from "../../capability/types"; import { analyzeAuthError, discoverOAuthEndpoints, MCPManager } from "../../mcp"; import { connectToServer, disconnectServer, listTools } from "../../mcp/client"; -import { - addMCPServer, - getMCPConfigPath, - readMCPConfigFile, - removeMCPServer, - updateMCPServer, -} from "../../mcp/config-writer"; +import { addMCPServer, readMCPConfigFile, removeMCPServer, updateMCPServer } from "../../mcp/config-writer"; import { MCPOAuthFlow } from "../../mcp/oauth-flow"; import type { MCPServerConfig, MCPServerConnection } from "../../mcp/types"; import type { OAuthCredential } from "../../session/auth-storage"; diff --git a/packages/coding-agent/src/modes/controllers/selector-controller.ts b/packages/coding-agent/src/modes/controllers/selector-controller.ts index fe06503fa..52a011741 100644 --- a/packages/coding-agent/src/modes/controllers/selector-controller.ts +++ b/packages/coding-agent/src/modes/controllers/selector-controller.ts @@ -2,7 +2,7 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core"; import 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 "../../config"; +import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs"; import { MODEL_ROLES } from "../../config/model-registry"; import { settings } from "../../config/settings"; import { DebugSelectorComponent } from "../../debug"; diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index 2137399db..99ab2570a 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -16,8 +16,8 @@ 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 chalk from "chalk"; -import { APP_NAME } from "../config"; import { KeybindingsManager } from "../config/keybindings"; import { renderPromptTemplate } from "../config/prompt-templates"; import { type Settings, settings } from "../config/settings"; diff --git a/packages/coding-agent/src/modes/theme/theme.ts b/packages/coding-agent/src/modes/theme/theme.ts index 1607860c7..cd9875fa6 100644 --- a/packages/coding-agent/src/modes/theme/theme.ts +++ b/packages/coding-agent/src/modes/theme/theme.ts @@ -7,10 +7,10 @@ import { } from "@oh-my-pi/pi-natives"; import type { EditorTheme, MarkdownTheme, SelectListTheme, SymbolTheme } from "@oh-my-pi/pi-tui"; import { adjustHsv, isEnoent, logger } from "@oh-my-pi/pi-utils"; +import { getCustomThemesDir } from "@oh-my-pi/pi-utils/dirs"; import { type Static, Type } from "@sinclair/typebox"; import { TypeCompiler } from "@sinclair/typebox/compiler"; import chalk from "chalk"; -import { getCustomThemesDir } from "../../config"; // Embed theme JSON files at build time import darkThemeJson from "./dark.json" with { type: "json" }; import { defaultThemes } from "./defaults"; diff --git a/packages/coding-agent/src/patch/index.ts b/packages/coding-agent/src/patch/index.ts index 45979b0d4..8c0f798c9 100644 --- a/packages/coding-agent/src/patch/index.ts +++ b/packages/coding-agent/src/patch/index.ts @@ -11,7 +11,6 @@ import * as fs from "node:fs/promises"; import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core"; import { StringEnum } from "@oh-my-pi/pi-ai"; -import { invalidateFsScanCache } from "@oh-my-pi/pi-natives"; import { type Static, Type } from "@sinclair/typebox"; import { renderPromptTemplate } from "../config/prompt-templates"; import { @@ -25,6 +24,11 @@ import hashlineDescription from "../prompts/tools/hashline.md" with { type: "tex import patchDescription from "../prompts/tools/patch.md" with { type: "text" }; import replaceDescription from "../prompts/tools/replace.md" with { type: "text" }; import type { ToolSession } from "../tools"; +import { + invalidateFsScanAfterDelete, + invalidateFsScanAfterRename, + invalidateFsScanAfterWrite, +} from "../tools/fs-cache-invalidation"; import { outputMeta } from "../tools/output-meta"; import { enforcePlanModeWrite, resolvePlanPath } from "../tools/plan-mode-guard"; import { applyPatch } from "./applicator"; @@ -531,7 +535,7 @@ export class EditTool implements AgentTool { const finalContent = bom + restoreLineEndings(result.content, originalEnding); const diagnostics = await this.#writethrough(absolutePath, finalContent, signal, file, batchRequest); - invalidateFsScanCache(absolutePath); + invalidateFsScanAfterWrite(absolutePath); const diffResult = generateDiffString(originalNormalized, result.content); const normative = buildNormativeUpdateInput({ @@ -589,8 +593,13 @@ export class EditTool implements AgentTool { fuzzyThreshold: this.#fuzzyThreshold, allowFuzzy: this.#allowFuzzy, }); - invalidateFsScanCache(resolvedPath); - if (resolvedRename) invalidateFsScanCache(resolvedRename); + if (resolvedRename) { + invalidateFsScanAfterRename(resolvedPath, resolvedRename); + } else if (result.change.type === "delete") { + invalidateFsScanAfterDelete(resolvedPath); + } else { + invalidateFsScanAfterWrite(resolvedPath); + } const effRename = result.change.newPath ? rename : undefined; // Generate diff for display @@ -713,7 +722,7 @@ export class EditTool implements AgentTool { const finalContent = bom + restoreLineEndings(result.content, originalEnding); const diagnostics = await this.#writethrough(absolutePath, finalContent, signal, file, batchRequest); - invalidateFsScanCache(absolutePath); + invalidateFsScanAfterWrite(absolutePath); const diffResult = generateDiffString(normalizedContent, result.content); const resultText = diff --git a/packages/coding-agent/src/sdk.ts b/packages/coding-agent/src/sdk.ts index 46a857f86..700d44b1c 100644 --- a/packages/coding-agent/src/sdk.ts +++ b/packages/coding-agent/src/sdk.ts @@ -2,10 +2,10 @@ 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 chalk from "chalk"; import { loadCapability } from "./capability"; import { type Rule, ruleCapability } from "./capability/rule"; -import { getAgentDbPath, getAgentDir } from "./config"; import { ModelRegistry } from "./config/model-registry"; import { formatModelString, parseModelString } from "./config/model-resolver"; import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./config/prompt-templates"; diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index 3f574a5bd..f93385a09 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -29,8 +29,8 @@ import type { } from "@oh-my-pi/pi-ai"; import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-ai"; import { abortableSleep, isEnoent, logger } from "@oh-my-pi/pi-utils"; +import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs"; import type { Rule } from "../capability/rule"; -import { getAgentDbPath } from "../config"; import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "../config/model-registry"; import { expandRoleAlias, parseModelString } from "../config/model-resolver"; import { diff --git a/packages/coding-agent/src/session/agent-storage.ts b/packages/coding-agent/src/session/agent-storage.ts index b473c9273..5ca834480 100644 --- a/packages/coding-agent/src/session/agent-storage.ts +++ b/packages/coding-agent/src/session/agent-storage.ts @@ -2,7 +2,7 @@ import { Database, type Statement } from "bun:sqlite"; import * as fs from "node:fs"; import * as path from "node:path"; import { logger } from "@oh-my-pi/pi-utils"; -import { getAgentDbPath } from "../config"; +import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs"; import type { RawSettings as Settings } from "../config/settings"; import type { AuthCredential } from "./auth-storage"; diff --git a/packages/coding-agent/src/session/history-storage.ts b/packages/coding-agent/src/session/history-storage.ts index 161d4fd87..dda9cd57b 100644 --- a/packages/coding-agent/src/session/history-storage.ts +++ b/packages/coding-agent/src/session/history-storage.ts @@ -2,7 +2,7 @@ import { Database, type Statement } from "bun:sqlite"; import * as fs from "node:fs"; import * as path from "node:path"; import { logger } from "@oh-my-pi/pi-utils"; -import { getAgentDir } from "../config"; +import { getAgentDir } from "@oh-my-pi/pi-utils/dirs"; export interface HistoryEntry { id: number; diff --git a/packages/coding-agent/src/session/session-manager.ts b/packages/coding-agent/src/session/session-manager.ts index f55887ea0..d3b2cf25d 100644 --- a/packages/coding-agent/src/session/session-manager.ts +++ b/packages/coding-agent/src/session/session-manager.ts @@ -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 "../config"; +import { getBlobsDir, getAgentDir as getDefaultAgentDir } from "@oh-my-pi/pi-utils/dirs"; import { type BlobPutResult, BlobStore, externalizeImageData, isBlobRef, resolveImageData } from "./blob-store"; import { type BashExecutionMessage, diff --git a/packages/coding-agent/src/ssh/connection-manager.ts b/packages/coding-agent/src/ssh/connection-manager.ts index 8ea41e5ec..6ecb3ca62 100644 --- a/packages/coding-agent/src/ssh/connection-manager.ts +++ b/packages/coding-agent/src/ssh/connection-manager.ts @@ -1,9 +1,8 @@ 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 { getRemoteHostDir, getSshControlDir } from "@oh-my-pi/pi-utils/dirs"; import { $ } from "bun"; -import { CONFIG_DIR_NAME } from "../config"; export interface SSHConnectionTarget { name: string; @@ -25,9 +24,9 @@ export interface SSHHostInfo { compatEnabled: boolean; } -const CONTROL_DIR = path.join(os.homedir(), CONFIG_DIR_NAME, "ssh-control"); +const CONTROL_DIR = getSshControlDir(); const CONTROL_PATH = path.join(CONTROL_DIR, "%h.sock"); -const HOST_INFO_DIR = path.join(os.homedir(), CONFIG_DIR_NAME, "remote-host"); +const HOST_INFO_DIR = getRemoteHostDir(); const HOST_INFO_VERSION = 2; const activeHosts = new Map(); diff --git a/packages/coding-agent/src/ssh/sshfs-mount.ts b/packages/coding-agent/src/ssh/sshfs-mount.ts index bade95fe4..dac51b60c 100644 --- a/packages/coding-agent/src/ssh/sshfs-mount.ts +++ b/packages/coding-agent/src/ssh/sshfs-mount.ts @@ -1,11 +1,10 @@ import * as fs from "node:fs"; -import * as os from "node:os"; import * as path from "node:path"; +import { getRemoteDir } from "@oh-my-pi/pi-utils/dirs"; import { $ } from "bun"; -import { CONFIG_DIR_NAME } from "../config"; import { getControlDir, getControlPathTemplate, type SSHConnectionTarget } from "./connection-manager"; -const REMOTE_DIR = path.join(os.homedir(), CONFIG_DIR_NAME, "remote"); +const REMOTE_DIR = getRemoteDir(); const CONTROL_DIR = getControlDir(); const CONTROL_PATH = getControlPathTemplate(); diff --git a/packages/coding-agent/src/system-prompt.ts b/packages/coding-agent/src/system-prompt.ts index 6a13f89ec..7b540ecbc 100644 --- a/packages/coding-agent/src/system-prompt.ts +++ b/packages/coding-agent/src/system-prompt.ts @@ -2,9 +2,9 @@ * System prompt construction and project context loading */ import * as os from "node:os"; -import * as path from "node:path"; 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 { $ } from "bun"; import { contextFileCapability } from "./capability/context-file"; import { systemPromptCapability } from "./capability/system-prompt"; @@ -272,7 +272,7 @@ interface GpuCache { } function getSystemInfoCachePath(): string { - return path.join(os.homedir(), ".omp", "gpu_cache.json"); + return getGpuCachePath(); } async function loadGpuCache(): Promise { diff --git a/packages/coding-agent/src/task/worktree.ts b/packages/coding-agent/src/task/worktree.ts index 0c7f48956..33570ffc8 100644 --- a/packages/coding-agent/src/task/worktree.ts +++ b/packages/coding-agent/src/task/worktree.ts @@ -2,6 +2,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import path from "node:path"; import { isEnoent, Snowflake } from "@oh-my-pi/pi-utils"; +import { getWorktreeDir } from "@oh-my-pi/pi-utils/dirs"; import { $ } from "bun"; export interface WorktreeBaseline { @@ -30,7 +31,7 @@ export async function getRepoRoot(cwd: string): Promise { export async function ensureWorktree(baseCwd: string, id: string): Promise { const repoRoot = await getRepoRoot(baseCwd); const encodedProject = getEncodedProjectName(repoRoot); - const worktreeDir = path.join(os.homedir(), ".omp", "wt", encodedProject, id); + const worktreeDir = getWorktreeDir(encodedProject, id); await fs.mkdir(path.dirname(worktreeDir), { recursive: true }); await $`git worktree remove -f ${worktreeDir}`.cwd(repoRoot).quiet().nothrow(); await fs.rm(worktreeDir, { recursive: true, force: true }); diff --git a/packages/coding-agent/src/tools/read.ts b/packages/coding-agent/src/tools/read.ts index fb5c3488f..f4ee21eef 100644 --- a/packages/coding-agent/src/tools/read.ts +++ b/packages/coding-agent/src/tools/read.ts @@ -1,5 +1,4 @@ import * as fs from "node:fs/promises"; -import * as os from "node:os"; import path from "node:path"; import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core"; import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai"; @@ -7,8 +6,8 @@ import { FileType, glob } from "@oh-my-pi/pi-natives"; import type { Component } from "@oh-my-pi/pi-tui"; import { Text } from "@oh-my-pi/pi-tui"; import { ptree, untilAborted } from "@oh-my-pi/pi-utils"; +import { getRemoteDir } from "@oh-my-pi/pi-utils/dirs"; import { type Static, Type } from "@sinclair/typebox"; -import { CONFIG_DIR_NAME } from "../config"; import { renderPromptTemplate } from "../config/prompt-templates"; import type { RenderResultOptions } from "../extensibility/custom-tools/types"; import { getLanguageFromPath, type Theme } from "../modes/theme/theme"; @@ -40,7 +39,7 @@ import { const CONVERTIBLE_EXTENSIONS = new Set([".pdf", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".rtf", ".epub"]); // Remote mount path prefix (sshfs mounts) - skip fuzzy matching to avoid hangs -const REMOTE_MOUNT_PREFIX = path.join(os.homedir(), CONFIG_DIR_NAME, "remote") + path.sep; +const REMOTE_MOUNT_PREFIX = getRemoteDir() + path.sep; function isRemoteMountPath(absolutePath: string): boolean { return absolutePath.startsWith(REMOTE_MOUNT_PREFIX); diff --git a/packages/coding-agent/src/utils/tools-manager.ts b/packages/coding-agent/src/utils/tools-manager.ts index 237c8cb84..581cbb27b 100644 --- a/packages/coding-agent/src/utils/tools-manager.ts +++ b/packages/coding-agent/src/utils/tools-manager.ts @@ -2,7 +2,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { logger, ptree, TempDir } from "@oh-my-pi/pi-utils"; -import { APP_NAME, getToolsDir } from "../config"; +import { APP_NAME, getToolsDir } from "@oh-my-pi/pi-utils/dirs"; const TOOLS_DIR = getToolsDir(); const TOOL_DOWNLOAD_TIMEOUT_MS = 15000; diff --git a/packages/coding-agent/src/web/search/auth.ts b/packages/coding-agent/src/web/search/auth.ts index 9674a813a..2ed08e115 100644 --- a/packages/coding-agent/src/web/search/auth.ts +++ b/packages/coding-agent/src/web/search/auth.ts @@ -9,7 +9,7 @@ */ import { buildAnthropicHeaders as buildProviderAnthropicHeaders, getEnvApiKey } from "@oh-my-pi/pi-ai"; import { $env, logger } from "@oh-my-pi/pi-utils"; -import { getAgentDbPath, getAgentDir } from "../../config"; +import { getAgentDbPath, getAgentDir } from "@oh-my-pi/pi-utils/dirs"; import { AgentStorage } from "../../session/agent-storage"; import type { AuthCredential } from "../../session/auth-storage"; import type { AnthropicAuthConfig, AnthropicOAuthCredential, ModelsJson } from "./types"; diff --git a/packages/coding-agent/src/web/search/providers/codex.ts b/packages/coding-agent/src/web/search/providers/codex.ts index c17375a1d..72b5faee0 100644 --- a/packages/coding-agent/src/web/search/providers/codex.ts +++ b/packages/coding-agent/src/web/search/providers/codex.ts @@ -7,8 +7,8 @@ */ import * as os from "node:os"; import { readSseJson } from "@oh-my-pi/pi-utils"; +import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs"; import packageJson from "../../../../package.json" with { type: "json" }; -import { getAgentDbPath } from "../../../config"; import { AgentStorage } from "../../../session/agent-storage"; import type { SearchResponse, SearchSource } from "../../../web/search/types"; import { SearchProviderError } from "../../../web/search/types"; diff --git a/packages/coding-agent/src/web/search/providers/gemini.ts b/packages/coding-agent/src/web/search/providers/gemini.ts index b1d7297ef..a00a10000 100644 --- a/packages/coding-agent/src/web/search/providers/gemini.ts +++ b/packages/coding-agent/src/web/search/providers/gemini.ts @@ -6,7 +6,7 @@ * Returns synthesized answers with citations and source metadata from grounding chunks. */ import { refreshGoogleCloudToken } from "@oh-my-pi/pi-ai"; -import { getAgentDbPath } from "../../../config"; +import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs"; import { AgentStorage } from "../../../session/agent-storage"; import type { SearchCitation, SearchResponse, SearchSource } from "../../../web/search/types"; import { SearchProviderError } from "../../../web/search/types"; diff --git a/packages/coding-agent/src/web/search/providers/perplexity.ts b/packages/coding-agent/src/web/search/providers/perplexity.ts index c478c4942..5610e3de5 100644 --- a/packages/coding-agent/src/web/search/providers/perplexity.ts +++ b/packages/coding-agent/src/web/search/providers/perplexity.ts @@ -8,7 +8,7 @@ import { getEnvApiKey } from "@oh-my-pi/pi-ai"; import { readSseJson } from "@oh-my-pi/pi-utils"; -import { getAgentDbPath } from "../../../config"; +import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs"; import { AgentStorage } from "../../../session/agent-storage"; import type { PerplexityMessageOutput, diff --git a/packages/coding-agent/test/agent-session-auto-compaction-queue.test.ts b/packages/coding-agent/test/agent-session-auto-compaction-queue.test.ts index de17dbbd1..5693e26ac 100644 --- a/packages/coding-agent/test/agent-session-auto-compaction-queue.test.ts +++ b/packages/coding-agent/test/agent-session-auto-compaction-queue.test.ts @@ -1,7 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; import * as fs from "node:fs"; import * as path from "node:path"; - import { Agent } from "@oh-my-pi/pi-agent-core"; import { getModel } from "@oh-my-pi/pi-ai/models"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; @@ -12,6 +11,7 @@ import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { TempDir } from "@oh-my-pi/pi-utils"; +import { getProjectAgentDir } from "@oh-my-pi/pi-utils/dirs"; /** * Regression test: auto-compaction completion should resume the agent loop when @@ -30,7 +30,7 @@ describe("AgentSession auto-compaction queue resume", () => { // Provide an extension that short-circuits compaction so the test doesn't // make any LLM calls. - const extensionsDir = path.join(tempDir.path(), ".omp", "extensions"); + const extensionsDir = path.join(getProjectAgentDir(tempDir.path()), "extensions"); fs.mkdirSync(extensionsDir, { recursive: true }); fs.writeFileSync( path.join(extensionsDir, "compaction-short-circuit.ts"), diff --git a/packages/coding-agent/test/core/python-modules.test.ts b/packages/coding-agent/test/core/python-modules.test.ts index feba823a6..ba0c5e0ef 100644 --- a/packages/coding-agent/test/core/python-modules.test.ts +++ b/packages/coding-agent/test/core/python-modules.test.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs/promises"; import * as path from "node:path"; import { loadPythonModules, type PythonModuleExecutor } from "@oh-my-pi/pi-coding-agent/ipy/modules"; import { TempDir } from "@oh-my-pi/pi-utils"; +import { getAgentModulesDir, getProjectModulesDir } from "@oh-my-pi/pi-utils/dirs"; const fixturesDir = path.resolve(import.meta.dir, "../../test/fixtures/python-modules"); @@ -26,11 +27,11 @@ describe("python modules", () => { it("loads modules in sorted order with silent execution", async () => { tempRoot = TempDir.createSync("@omp-python-modules-"); - const homeDir = path.join(tempRoot.path(), "home"); + const agentDir = path.join(tempRoot.path(), "agent"); const cwd = path.join(tempRoot.path(), "project"); - await writeModule(path.join(homeDir, ".omp", "agent", "modules"), "beta.py", "user-omp"); - await writeModule(path.join(homeDir, ".omp", "agent", "modules"), "alpha.py", "user-omp"); + await writeModule(getAgentModulesDir(agentDir), "beta.py", "user-omp"); + await writeModule(getAgentModulesDir(agentDir), "alpha.py", "user-omp"); const calls: Array<{ name: string; options?: { silent?: boolean; storeHistory?: boolean } }> = []; const executor: PythonModuleExecutor = { @@ -41,7 +42,7 @@ describe("python modules", () => { }, }; - await loadPythonModules(executor, { cwd, homeDir }); + await loadPythonModules(executor, { cwd, agentDir }); expect(calls.map(call => call.name)).toEqual(["alpha", "beta"]); for (const call of calls) { expect(call.options).toEqual({ silent: true, storeHistory: false }); @@ -50,11 +51,11 @@ describe("python modules", () => { it("fails fast when a module fails to execute", async () => { tempRoot = TempDir.createSync("@omp-python-modules-"); - const homeDir = path.join(tempRoot.path(), "home"); + const agentDir = path.join(tempRoot.path(), "agent"); const cwd = path.join(tempRoot.path(), "project"); - await writeModule(path.join(homeDir, ".omp", "agent", "modules"), "alpha.py", "user-omp"); - await writeModule(path.join(cwd, ".omp", "modules"), "beta.py", "project-omp"); + await writeModule(getAgentModulesDir(agentDir), "alpha.py", "user-omp"); + await writeModule(getProjectModulesDir(cwd), "beta.py", "project-omp"); const executor: PythonModuleExecutor = { execute: async (code: string) => { @@ -69,6 +70,6 @@ describe("python modules", () => { }, }; - await expect(loadPythonModules(executor, { cwd, homeDir })).rejects.toThrow("Failed to load Python module"); + await expect(loadPythonModules(executor, { cwd, agentDir })).rejects.toThrow("Failed to load Python module"); }); }); diff --git a/packages/coding-agent/test/extensions-discovery.test.ts b/packages/coding-agent/test/extensions-discovery.test.ts index 1f3448efa..19be05743 100644 --- a/packages/coding-agent/test/extensions-discovery.test.ts +++ b/packages/coding-agent/test/extensions-discovery.test.ts @@ -4,6 +4,7 @@ import * as path from "node:path"; import { fileURLToPath } from "node:url"; import { discoverAndLoadExtensions, loadExtensions } from "@oh-my-pi/pi-coding-agent/extensibility/extensions/loader"; import { TempDir } from "@oh-my-pi/pi-utils"; +import { getProjectAgentDir } from "@oh-my-pi/pi-utils/dirs"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -13,7 +14,7 @@ describe("extensions discovery", () => { beforeEach(() => { tempDir = TempDir.createSync("@pi-ext-test-"); - extensionsDir = path.join(tempDir.path(), ".omp", "extensions"); + extensionsDir = path.join(getProjectAgentDir(tempDir.path()), "extensions"); fs.mkdirSync(extensionsDir, { recursive: true }); }); diff --git a/packages/coding-agent/test/extensions-runner.test.ts b/packages/coding-agent/test/extensions-runner.test.ts index 6be332d6e..64223fcfd 100644 --- a/packages/coding-agent/test/extensions-runner.test.ts +++ b/packages/coding-agent/test/extensions-runner.test.ts @@ -11,6 +11,7 @@ import { ExtensionRunner } from "@oh-my-pi/pi-coding-agent/extensibility/extensi import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { logger, TempDir } from "@oh-my-pi/pi-utils"; +import { getProjectAgentDir } from "@oh-my-pi/pi-utils/dirs"; describe("ExtensionRunner", () => { let tempDir: TempDir; @@ -20,7 +21,7 @@ describe("ExtensionRunner", () => { beforeEach(async () => { tempDir = TempDir.createSync("@pi-runner-test-"); - extensionsDir = path.join(tempDir.path(), ".omp", "extensions"); + extensionsDir = path.join(getProjectAgentDir(tempDir.path()), "extensions"); fs.mkdirSync(extensionsDir, { recursive: true }); sessionManager = SessionManager.inMemory(); const authStorage = await AuthStorage.create(path.join(tempDir.path(), "testauth.db")); diff --git a/packages/coding-agent/test/sdk-skills.test.ts b/packages/coding-agent/test/sdk-skills.test.ts index cada098b3..4d765194c 100644 --- a/packages/coding-agent/test/sdk-skills.test.ts +++ b/packages/coding-agent/test/sdk-skills.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import type { Skill } from "@oh-my-pi/pi-coding-agent/sdk"; import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; +import { getProjectAgentDir } from "@oh-my-pi/pi-utils/dirs"; describe("createAgentSession skills option", () => { let tempDir: string; @@ -13,7 +14,7 @@ describe("createAgentSession skills option", () => { beforeEach(() => { tempDir = path.join(os.tmpdir(), `pi-sdk-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); // Create skill in .omp/skills/ for native project-level discovery - skillsDir = path.join(tempDir, ".omp", "skills", "test-skill"); + skillsDir = path.join(getProjectAgentDir(tempDir), "skills", "test-skill"); fs.mkdirSync(skillsDir, { recursive: true }); // Create a test skill in the pi skills directory diff --git a/packages/coding-agent/test/settings-manager.test.ts b/packages/coding-agent/test/settings-manager.test.ts index acb33389f..c41c1e79d 100644 --- a/packages/coding-agent/test/settings-manager.test.ts +++ b/packages/coding-agent/test/settings-manager.test.ts @@ -4,6 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { _resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { Snowflake } from "@oh-my-pi/pi-utils"; +import { getProjectAgentDir } from "@oh-my-pi/pi-utils/dirs"; import { YAML } from "bun"; describe("Settings", () => { @@ -24,7 +25,7 @@ describe("Settings", () => { fs.rmSync(testDir, { recursive: true }); } fs.mkdirSync(agentDir, { recursive: true }); - fs.mkdirSync(path.join(projectDir, ".omp"), { recursive: true }); + fs.mkdirSync(getProjectAgentDir(projectDir), { recursive: true }); }); const getConfigPath = () => path.join(agentDir, "config.yml"); diff --git a/packages/stats/package.json b/packages/stats/package.json index d1076fee5..638a00757 100644 --- a/packages/stats/package.json +++ b/packages/stats/package.json @@ -1,66 +1,67 @@ { - "name": "@oh-my-pi/omp-stats", - "version": "12.0.0", - "description": "Local observability dashboard for pi AI usage statistics", - "type": "module", - "main": "./src/index.ts", - "types": "./src/index.ts", - "bin": { - "omp-stats": "./src/index.ts" - }, - "exports": { - ".": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "./src/server": { - "types": "./src/server.ts", - "import": "./src/server.ts" - }, - "./src/db": { - "types": "./src/db.ts", - "import": "./src/db.ts" - } - }, - "files": [ - "src", - "public", - "README.md" - ], - "keywords": [ - "ai", - "observability", - "metrics", - "dashboard", - "llm", - "statistics" - ], - "scripts": { - "dev": "bun run src/index.ts", - "check": "tsgo -p tsconfig.json && tsgo -p tsconfig.client.json", - "build": "bun run build.ts" - }, - "author": "Can Bölük", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/can1357/oh-my-pi.git", - "directory": "packages/stats" - }, - "dependencies": { - "@oh-my-pi/pi-ai": "workspace:*", - "date-fns": "^4.1.0", - "lucide-react": "^0.563.0", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "recharts": "^3.7.0" - }, - "devDependencies": { - "@types/bun": "^1.3.9", - "@types/react": "^19.2.10", - "@types/react-dom": "^19.2.3" - }, - "engines": { - "bun": ">=1.3.7" - } -} + "name": "@oh-my-pi/omp-stats", + "version": "12.0.0", + "description": "Local observability dashboard for pi AI usage statistics", + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "bin": { + "omp-stats": "./src/index.ts" + }, + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./src/server": { + "types": "./src/server.ts", + "import": "./src/server.ts" + }, + "./src/db": { + "types": "./src/db.ts", + "import": "./src/db.ts" + } + }, + "files": [ + "src", + "public", + "README.md" + ], + "keywords": [ + "ai", + "observability", + "metrics", + "dashboard", + "llm", + "statistics" + ], + "scripts": { + "dev": "bun run src/index.ts", + "check": "tsgo -p tsconfig.json && tsgo -p tsconfig.client.json", + "build": "bun run build.ts" + }, + "author": "Can Bölük", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/stats" + }, + "dependencies": { + "@oh-my-pi/pi-ai": "workspace:*", + "date-fns": "^4.1.0", + "lucide-react": "^0.563.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "recharts": "^3.7.0", + "@oh-my-pi/pi-utils": "workspace:*" + }, + "devDependencies": { + "@types/bun": "^1.3.9", + "@types/react": "^19.2.10", + "@types/react-dom": "^19.2.3" + }, + "engines": { + "bun": ">=1.3.7" + } +} \ No newline at end of file diff --git a/packages/stats/src/db.ts b/packages/stats/src/db.ts index f2e6d1e91..671f38289 100644 --- a/packages/stats/src/db.ts +++ b/packages/stats/src/db.ts @@ -1,7 +1,6 @@ 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 { getConfigRootDir, getStatsDbPath } from "@oh-my-pi/pi-utils/dirs"; import type { AggregatedStats, FolderStats, @@ -12,7 +11,7 @@ import type { TimeSeriesPoint, } from "./types"; -const DB_PATH = path.join(os.homedir(), ".omp", "stats.db"); +const DB_PATH = getStatsDbPath(); let db: Database | null = null; @@ -23,7 +22,7 @@ export async function initDb(): Promise { if (db) return db; // Ensure directory exists - await fs.mkdir(path.join(os.homedir(), ".omp"), { recursive: true }); + await fs.mkdir(getConfigRootDir(), { recursive: true }); db = new Database(DB_PATH); db.exec("PRAGMA journal_mode = WAL"); diff --git a/packages/stats/src/parser.ts b/packages/stats/src/parser.ts index 236e81d29..9741d7384 100644 --- a/packages/stats/src/parser.ts +++ b/packages/stats/src/parser.ts @@ -1,11 +1,9 @@ import * as fs from "node:fs/promises"; -import * as os from "node:os"; import * as path from "node:path"; import type { AssistantMessage } from "@oh-my-pi/pi-ai"; +import { getSessionsDir } from "@oh-my-pi/pi-utils/dirs"; import type { MessageStats, SessionEntry, SessionMessageEntry } from "./types"; -const SESSIONS_DIR = path.join(os.homedir(), ".omp", "agent", "sessions"); - /** * Extract folder name from session filename. * Session files are named like: --work--pi--/timestamp_uuid.jsonl @@ -95,8 +93,9 @@ export async function parseSessionFile( */ export async function listSessionFolders(): Promise { try { - const entries = await fs.readdir(SESSIONS_DIR, { withFileTypes: true }); - return entries.filter(e => e.isDirectory()).map(e => path.join(SESSIONS_DIR, e.name)); + const sessionsDir = getSessionsDir(); + const entries = await fs.readdir(sessionsDir, { withFileTypes: true }); + return entries.filter(e => e.isDirectory()).map(e => path.join(sessionsDir, e.name)); } catch { return []; } @@ -129,13 +128,6 @@ export async function listAllSessionFiles(): Promise { return allFiles; } -/** - * Get session directory path. - */ -export function getSessionsDir(): string { - return SESSIONS_DIR; -} - /** * Find a specific entry in a session file. */ diff --git a/packages/tui/src/tui.ts b/packages/tui/src/tui.ts index f60361dfe..0ba41e49c 100644 --- a/packages/tui/src/tui.ts +++ b/packages/tui/src/tui.ts @@ -2,8 +2,8 @@ * Minimal TUI implementation with differential rendering */ import * as fs from "node:fs"; -import * as os from "node:os"; import * as path from "node:path"; +import { getCrashLogPath, getDebugLogPath } from "@oh-my-pi/pi-utils/dirs"; import { isKeyRelease, matchesKey } from "./keys"; import type { Terminal } from "./terminal"; import { setCellDimensions, TERMINAL } from "./terminal-capabilities"; @@ -872,7 +872,7 @@ export class TUI extends Container { const debugRedraw = process.env.PI_DEBUG_REDRAW === "1"; const logRedraw = (reason: string): void => { if (!debugRedraw) return; - const logPath = path.join(os.homedir(), ".omp", "agent", "omp-debug.log"); + const logPath = getDebugLogPath(); const msg = `[${new Date().toISOString()}] fullRender: ${reason} (prev=${this.#previousLines.length}, new=${newLines.length}, height=${height})\n`; fs.appendFileSync(logPath, msg); }; @@ -1018,7 +1018,7 @@ export class TUI extends Container { const isImage = TERMINAL.isImageLine(line); if (!isImage && visibleWidth(line) > width) { // Log all lines to crash file for debugging - const crashLogPath = path.join(os.homedir(), ".omp", "agent", "omp-crash.log"); + const crashLogPath = getCrashLogPath(); const crashData = [ `Crash at ${new Date().toISOString()}`, `Terminal width: ${width}`, diff --git a/packages/utils/package.json b/packages/utils/package.json index 58e7588a7..63e7ce746 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -13,6 +13,10 @@ "./cli": { "types": "./src/cli.ts", "import": "./src/cli.ts" + }, + "./dirs": { + "types": "./src/dirs.ts", + "import": "./src/dirs.ts" } }, "files": [ diff --git a/packages/utils/src/dirs.ts b/packages/utils/src/dirs.ts new file mode 100644 index 000000000..5aaedbef5 --- /dev/null +++ b/packages/utils/src/dirs.ts @@ -0,0 +1,250 @@ +/** + * Centralized path helpers for omp config directories. + * + * Uses PI_CONFIG_DIR (default ".omp") for the config root and + * PI_CODING_AGENT_DIR to override the agent directory. + */ +import * as os from "node:os"; +import * as path from "node:path"; +import packageJson from "../../coding-agent/package.json" with { type: "json" }; +import { $env } from "./env"; + +/** App name (e.g. "omp") */ +export const APP_NAME: string = packageJson.ompConfig?.name || "omp"; + +/** Config directory name (e.g. ".omp") */ +export const CONFIG_DIR_NAME: string = packageJson.ompConfig?.configDir || ".omp"; + +/** Version (e.g. "1.0.0") */ +export const VERSION: string = packageJson.version; + +/** + * Get the config root directory (~/.omp). + */ +export function getConfigRootDir(): string { + return path.join(os.homedir(), $env.PI_CONFIG_DIR || CONFIG_DIR_NAME); +} + +/** + * Get the path to the agent directory. + */ +export function getAgentDir(): string { + return $env.PI_CODING_AGENT_DIR || path.join(getConfigRootDir(), "agent"); +} + +/** + * Get the path to the project-local agent directory. + */ +export function getProjectAgentDir(cwd: string = process.cwd()): string { + return path.join(cwd, CONFIG_DIR_NAME); +} + +/** + * Get the reports directory (~/.omp/reports). + */ +export function getReportsDir(): string { + return path.join(getConfigRootDir(), "reports"); +} + +/** + * Get the logs directory (~/.omp/logs). + */ +export function getLogsDir(): string { + return path.join(getConfigRootDir(), "logs"); +} + +/** + * Get the path to today's log file. + */ +export function getLogPath(date?: string): string { + const today = date ?? new Date().toISOString().slice(0, 10); + return path.join(getLogsDir(), `omp.${today}.log`); +} + +/** + * Get the user-level Python modules directory (~/.omp/agent/modules). + */ +export function getAgentModulesDir(agentDir: string = getAgentDir()): string { + return path.join(agentDir, "modules"); +} + +/** + * Get the project-level Python modules directory (.omp/modules). + */ +export function getProjectModulesDir(cwd: string = process.cwd()): string { + return path.join(getProjectAgentDir(cwd), "modules"); +} + +/** + * Get the project-level prompts directory (.omp/prompts). + */ +export function getProjectPromptsDir(cwd: string = process.cwd()): string { + return path.join(getProjectAgentDir(cwd), "prompts"); +} + +/** + * Get the plugins directory (~/.omp/plugins). + */ +export function getPluginsDir(): string { + return path.join(getConfigRootDir(), "plugins"); +} + +/** Where npm installs packages: ~/.omp/plugins/node_modules */ +export function getPluginsNodeModules(): string { + return path.join(getPluginsDir(), "node_modules"); +} + +/** Plugin manifest: ~/.omp/plugins/package.json */ +export function getPluginsPackageJson(): string { + return path.join(getPluginsDir(), "package.json"); +} + +/** Plugin lock file: ~/.omp/plugins/omp-plugins.lock.json */ +export function getPluginsLockfile(): string { + return path.join(getPluginsDir(), "omp-plugins.lock.json"); +} + +/** + * Get the remote mount directory (~/.omp/remote). + */ +export function getRemoteDir(): string { + return path.join(getConfigRootDir(), "remote"); +} + +/** + * Get the SSH control socket directory (~/.omp/ssh-control). + */ +export function getSshControlDir(): string { + return path.join(getConfigRootDir(), "ssh-control"); +} + +/** + * Get the remote host info directory (~/.omp/remote-host). + */ +export function getRemoteHostDir(): string { + return path.join(getConfigRootDir(), "remote-host"); +} + +/** + * Get the managed Python venv directory (~/.omp/python-env). + */ +export function getPythonEnvDir(): string { + return path.join(getConfigRootDir(), "python-env"); +} + +/** + * Get the project-level plugin overrides path (.omp/plugin-overrides.json). + */ +export function getProjectPluginOverridesPath(cwd: string = process.cwd()): string { + return path.join(getProjectAgentDir(cwd), "plugin-overrides.json"); +} + +/** + * Get the MCP config file path. + * @param scope - "user" for ~/.omp/mcp.json or "project" for .omp/mcp.json + */ +export function getMCPConfigPaths(scope: "user" | "project", cwd: string = process.cwd()): string[] { + if (scope === "user") { + return [path.join(getAgentDir(), "mcp.json"), path.join(getAgentDir(), ".mcp.json")]; + } + return [path.join(getProjectAgentDir(cwd), "mcp.json"), path.join(getProjectAgentDir(cwd), ".mcp.json")]; +} + +/** + * Get the primary MCP config file path (first candidate). + * @param scope - "user" for ~/.omp/agent/mcp.json or "project" for .omp/mcp.json + */ +export function getMCPConfigPath(scope: "user" | "project", cwd: string = process.cwd()): string { + return getMCPConfigPaths(scope, cwd)[0]; +} + +/** + * Get the worktree base directory (~/.omp/wt). + */ +export function getWorktreeBaseDir(): string { + return path.join(getConfigRootDir(), "wt"); +} + +/** + * Get the path to a worktree directory. + */ +export function getWorktreeDir(encodedProject: string, id: string): string { + return path.join(getWorktreeBaseDir(), encodedProject, id); +} + +/** + * Get the GPU cache path (~/.omp/gpu_cache.json). + */ +export function getGpuCachePath(): string { + return path.join(getConfigRootDir(), "gpu_cache.json"); +} + +/** + * Get the test auth database path (~/.omp/agent/testauth.db). + */ +export function getTestAuthPath(): string { + return path.join(getAgentDir(), "testauth.db"); +} + +/** + * Get the sessions directory (~/.omp/agent/sessions). + */ +export function getSessionsDir(): string { + return path.join(getAgentDir(), "sessions"); +} + +/** + * Get the natives directory (~/.omp/natives). + */ +export function getNativesDir(): string { + return path.join(getConfigRootDir(), "natives"); +} + +/** + * Get the stats database path (~/.omp/stats.db). + */ +export function getStatsDbPath(): string { + return path.join(getConfigRootDir(), "stats.db"); +} + +/** + * Get the crash log path (~/.omp/agent/omp-crash.log). + */ +export function getCrashLogPath(): string { + return path.join(getAgentDir(), "omp-crash.log"); +} + +/** Gets the path to agent.db (SQLite database for settings and auth storage) */ +export function getAgentDbPath(agentDir: string = getAgentDir()): string { + return path.join(agentDir, "agent.db"); +} + +/** Get path to user's custom themes directory */ +export function getCustomThemesDir(): string { + return path.join(getAgentDir(), "themes"); +} + +/** Get path to tools directory */ +export function getToolsDir(): string { + return path.join(getAgentDir(), "tools"); +} + +/** Get path to slash commands directory */ +export function getCommandsDir(): string { + return path.join(getAgentDir(), "commands"); +} + +/** Get path to prompts directory */ +export function getPromptsDir(): string { + return path.join(getAgentDir(), "prompts"); +} + +/** Get path to content-addressed blob store directory */ +export function getBlobsDir(): string { + return path.join(getAgentDir(), "blobs"); +} + +/** Get path to debug log file */ +export function getDebugLogPath(): string { + return path.join(getAgentDir(), `${APP_NAME}-debug.log`); +} diff --git a/packages/utils/src/logger.ts b/packages/utils/src/logger.ts index b7463d80a..1ff51d11e 100644 --- a/packages/utils/src/logger.ts +++ b/packages/utils/src/logger.ts @@ -5,15 +5,9 @@ * Each log entry includes process.pid for traceability. */ import * as fs from "node:fs"; -import * as os from "node:os"; -import * as path from "node:path"; import winston from "winston"; import DailyRotateFile from "winston-daily-rotate-file"; - -/** Get the logs directory (~/.omp/logs/) */ -function getLogsDir(): string { - return path.join(os.homedir(), ".omp", "logs"); -} +import { getLogsDir } from "./dirs"; /** Ensure logs directory exists */ function ensureLogsDir(): string {