feat: removed .pi configuration directory alias in favor of .omp

- Removed support for `.pi` configuration directory alias in favor of `.omp` across all packages.
- Updated all configuration paths and references from `.pi` to `.omp` in user and project directories.
- Refactored configuration discovery in builtin.ts to simplify directory traversal logic and remove multi-alias support.
- Simplified Python module discovery to use single directory paths instead of arrays of aliases.
- Updated debug and crash log paths from `.pi/agent/` to `.omp/agent/` in TUI package.
This commit is contained in:
can1357
2026-02-12 01:12:03 +01:00
parent f5c828c147
commit da455e63fd
11 changed files with 177 additions and 233 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
/**
* Test helper for resolving API keys from ~/.pi/agent/testauth.db
* Test helper for resolving API keys from ~/.omp/agent/testauth.db
*
* Supports both API key and OAuth credentials.
* OAuth tokens are automatically refreshed if expired and saved back to testauth.db.
@@ -28,7 +28,7 @@ export function e2eApiKey(envVar: string): string | undefined {
return Bun.env[envVar];
}
const AUTH_PATH = path.join(os.homedir(), ".pi", "agent", "testauth.db");
const AUTH_PATH = path.join(os.homedir(), ".omp", "agent", "testauth.db");
type ApiKeyCredential = {
type: "api_key";
@@ -59,7 +59,7 @@ async function saveAuthStorage(storage: AuthStorage): Promise<void> {
}
/**
* Resolve API key for a provider from ~/.pi/agent/testauth.db
* Resolve API key for a provider from ~/.omp/agent/testauth.db
*
* For API key credentials, returns the key directly.
* For OAuth credentials, returns the access token (refreshing if expired and saving back).
+5
View File
@@ -1,6 +1,11 @@
# Changelog
## [Unreleased]
### Breaking Changes
- Removed support for `.pi` configuration directory alias; use `.omp` instead
### Added
- Added `openPath` utility function to centralize cross-platform URL and file path opening
+66 -65
View File
@@ -27,83 +27,83 @@ Many modules now use the **capability/discovery system** (`discovery/builtin.ts`
### 1. Display/Branding Only (no file I/O)
| File | Imports | Purpose |
| ---------------------------- | ----------------------------- | ------------------------ |
| `cli/args.ts` | `APP_NAME`, `CONFIG_DIR_NAME` | Help text, env var names |
| `cli/grep-cli.ts` | `APP_NAME` | Grep command output |
| `cli/jupyter-cli.ts` | `APP_NAME` | Jupyter command output |
| `cli/plugin-cli.ts` | `APP_NAME` | Plugin command output |
| `cli/setup-cli.ts` | `APP_NAME` | Setup command output |
| `cli/shell-cli.ts` | `APP_NAME` | Shell command output |
| `cli/stats-cli.ts` | `APP_NAME` | Stats command output |
| `cli/update-cli.ts` | `APP_NAME`, `VERSION` | Update messages |
| `cli.ts` | `APP_NAME` | Process title |
| `export/html/index.ts` | `APP_NAME` | HTML export title |
| `modes/components/welcome.ts`| `APP_NAME` | Welcome banner |
| `debug/system-info.ts` | `VERSION` | System info display |
| File | Imports | Purpose |
| ----------------------------- | ----------------------------- | ------------------------ |
| `cli/args.ts` | `APP_NAME`, `CONFIG_DIR_NAME` | Help text, env var names |
| `cli/grep-cli.ts` | `APP_NAME` | Grep command output |
| `cli/jupyter-cli.ts` | `APP_NAME` | Jupyter command output |
| `cli/plugin-cli.ts` | `APP_NAME` | Plugin command output |
| `cli/setup-cli.ts` | `APP_NAME` | Setup command output |
| `cli/shell-cli.ts` | `APP_NAME` | Shell command output |
| `cli/stats-cli.ts` | `APP_NAME` | Stats command output |
| `cli/update-cli.ts` | `APP_NAME`, `VERSION` | Update messages |
| `cli.ts` | `APP_NAME` | Process title |
| `export/html/index.ts` | `APP_NAME` | HTML export title |
| `modes/components/welcome.ts` | `APP_NAME` | Welcome banner |
| `debug/system-info.ts` | `VERSION` | System info display |
### 2. Single Fixed Paths (user-level only)
| File | Imports | Path | Purpose |
| ------------------------------------- | -------------------------------- | --------------------------- | ------------------------- |
| `cli/config-cli.ts` | `APP_NAME`, `getAgentDir` | `~/.omp/agent/` | Prints config path |
| `session/agent-session.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | Database path |
| `session/session-manager.ts` | `getAgentDir` | `~/.omp/agent/sessions/` | Session storage |
| `session/agent-storage.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | Settings/auth storage |
| `session/auth-storage.ts` | `getAgentDbPath` | agent.db | Auth credential storage |
| `session/history-storage.ts` | `getAgentDir` | `~/.omp/agent/` | Command history |
| `session/storage-migration.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | JSON→SQLite migration |
| `modes/theme/theme.ts` | `getCustomThemesDir` | `~/.omp/agent/themes/` | Custom themes |
| `modes/controllers/selector-controller.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | Model selector state |
| `utils/changelog.ts` | `getChangelogPath` | Package CHANGELOG.md | Re-exports path |
| `migrations.ts` | `getAgentDir`, `getAgentDbPath` | `~/.omp/agent/` | Auth/session migration |
| `extensibility/plugins/installer.ts` | `getAgentDir` | `~/.omp/agent/plugins/` | Plugin installation |
| `extensibility/plugins/paths.ts` | `CONFIG_DIR_NAME` | `~/.omp/plugins/` | Plugin directories |
| `config/keybindings.ts` | `getAgentDir` | `~/.omp/agent/keybindings.json` | Keybinding config |
| `config/settings.ts` | `getAgentDir`, `getAgentDbPath` | agent.db, config.yml | Settings management |
| `config/prompt-templates.ts` | `CONFIG_DIR_NAME`, `getPromptsDir` | `~/.omp/agent/prompts/` | Prompt template loading |
| `ipy/executor.ts` | `getAgentDir` | `~/.omp/agent/` | Python executor paths |
| `ipy/gateway-coordinator.ts` | `getAgentDir` | `~/.omp/agent/` | Jupyter gateway socket |
| `export/custom-share.ts` | `getAgentDir` | `~/.omp/agent/share/` | Custom share scripts |
| `debug/index.ts` | `getSessionsDir` | `~/.omp/agent/sessions/` | Debug session browser |
| `ssh/connection-manager.ts` | `CONFIG_DIR_NAME` | `~/.omp/ssh/` | SSH control sockets |
| `ssh/sshfs-mount.ts` | `CONFIG_DIR_NAME` | `~/.omp/remote/` | Remote mount points |
| `tools/read.ts` | `CONFIG_DIR_NAME` | Config dir name reference | Internal URL resolution |
| `utils/tools-manager.ts` | `APP_NAME`, `getToolsDir` | `~/.omp/agent/tools/` | Tool binary management |
| File | Imports | Path | Purpose |
| ------------------------------------------ | ---------------------------------- | ------------------------------- | ----------------------- |
| `cli/config-cli.ts` | `APP_NAME`, `getAgentDir` | `~/.omp/agent/` | Prints config path |
| `session/agent-session.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | Database path |
| `session/session-manager.ts` | `getAgentDir` | `~/.omp/agent/sessions/` | Session storage |
| `session/agent-storage.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | Settings/auth storage |
| `session/auth-storage.ts` | `getAgentDbPath` | agent.db | Auth credential storage |
| `session/history-storage.ts` | `getAgentDir` | `~/.omp/agent/` | Command history |
| `session/storage-migration.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | JSON→SQLite migration |
| `modes/theme/theme.ts` | `getCustomThemesDir` | `~/.omp/agent/themes/` | Custom themes |
| `modes/controllers/selector-controller.ts` | `getAgentDbPath` | `~/.omp/agent/agent.db` | Model selector state |
| `utils/changelog.ts` | `getChangelogPath` | Package CHANGELOG.md | Re-exports path |
| `migrations.ts` | `getAgentDir`, `getAgentDbPath` | `~/.omp/agent/` | Auth/session migration |
| `extensibility/plugins/installer.ts` | `getAgentDir` | `~/.omp/agent/plugins/` | Plugin installation |
| `extensibility/plugins/paths.ts` | `CONFIG_DIR_NAME` | `~/.omp/plugins/` | Plugin directories |
| `config/keybindings.ts` | `getAgentDir` | `~/.omp/agent/keybindings.json` | Keybinding config |
| `config/settings.ts` | `getAgentDir`, `getAgentDbPath` | agent.db, config.yml | Settings management |
| `config/prompt-templates.ts` | `CONFIG_DIR_NAME`, `getPromptsDir` | `~/.omp/agent/prompts/` | Prompt template loading |
| `ipy/executor.ts` | `getAgentDir` | `~/.omp/agent/` | Python executor paths |
| `ipy/gateway-coordinator.ts` | `getAgentDir` | `~/.omp/agent/` | Jupyter gateway socket |
| `export/custom-share.ts` | `getAgentDir` | `~/.omp/agent/share/` | Custom share scripts |
| `debug/index.ts` | `getSessionsDir` | `~/.omp/agent/sessions/` | Debug session browser |
| `ssh/connection-manager.ts` | `CONFIG_DIR_NAME` | `~/.omp/ssh/` | SSH control sockets |
| `ssh/sshfs-mount.ts` | `CONFIG_DIR_NAME` | `~/.omp/remote/` | Remote mount points |
| `tools/read.ts` | `CONFIG_DIR_NAME` | Config dir name reference | Internal URL resolution |
| `utils/tools-manager.ts` | `APP_NAME`, `getToolsDir` | `~/.omp/agent/tools/` | Tool binary management |
### 3. Multi-Config Discovery (with fallbacks)
These use helpers to check `.omp`, `.pi`, `.claude`, `.codex`, `.gemini` directories:
| File | Helper Used | Subpath(s) | Levels |
| ---------------------------------------- | ------------------------------------------------------ | --------------------------- | ------------ |
| `main.ts` | `findConfigFile` | `SYSTEM.md`, `APPEND_SYSTEM.md` | user+project |
| `sdk.ts` | `getConfigDirPaths` | `models.yml`, `models.json` | user |
| `lsp/config.ts` | `getConfigDirPaths` | `lsp.json`, `.lsp.json` | user+project |
| `task/discovery.ts` | `getConfigDirs`, `findAllNearestProjectConfigDirs` | `agents/` | user+project |
| `extensibility/plugins/paths.ts` | `getConfigDirPaths` | `plugin-overrides.json` | project |
| `extensibility/custom-commands/loader.ts`| `getConfigDirs` | `commands/` | user+project |
| `web/search/auth.ts` | `getConfigDirPaths`, `getAgentDbPath` | agent.db | user |
| `web/search/providers/codex.ts` | `getConfigDirPaths`, `getAgentDbPath` | auth config | user |
| `web/search/providers/gemini.ts` | `getConfigDirPaths`, `getAgentDbPath` | auth config | user |
| File | Helper Used | Subpath(s) | Levels |
| ----------------------------------------- | -------------------------------------------------- | ------------------------------- | ------------ |
| `main.ts` | `findConfigFile` | `SYSTEM.md`, `APPEND_SYSTEM.md` | user+project |
| `sdk.ts` | `getConfigDirPaths` | `models.yml`, `models.json` | user |
| `lsp/config.ts` | `getConfigDirPaths` | `lsp.json`, `.lsp.json` | user+project |
| `task/discovery.ts` | `getConfigDirs`, `findAllNearestProjectConfigDirs` | `agents/` | user+project |
| `extensibility/plugins/paths.ts` | `getConfigDirPaths` | `plugin-overrides.json` | project |
| `extensibility/custom-commands/loader.ts` | `getConfigDirs` | `commands/` | user+project |
| `web/search/auth.ts` | `getConfigDirPaths`, `getAgentDbPath` | agent.db | user |
| `web/search/providers/codex.ts` | `getConfigDirPaths`, `getAgentDbPath` | auth config | user |
| `web/search/providers/gemini.ts` | `getConfigDirPaths`, `getAgentDbPath` | auth config | user |
### 4. Via Capability/Discovery System
These modules use `discovery/builtin.ts` which has its own config directory resolution:
| Capability | Config Subpaths | Loaded Via |
| --------------- | ---------------------------------- | --------------------------- |
| skills | `skills/` | `skillCapability` |
| slash-commands | `commands/` | `slashCommandCapability` |
| rules | `rules/` | `ruleCapability` |
| prompts | `prompts/` | `promptCapability` |
| instructions | `instructions/` | `instructionCapability` |
| hooks | `hooks/pre/`, `hooks/post/` | `hookCapability` |
| tools | `tools/` | `toolCapability` |
| extensions | `extensions/` | `extensionCapability` |
| mcp | `mcp.json`, `.mcp.json` | `mcpCapability` |
| settings | `settings.json` | `settingsCapability` |
| system-prompt | `SYSTEM.md` | `systemPromptCapability` |
| Capability | Config Subpaths | Loaded Via |
| -------------- | --------------------------- | ------------------------ |
| skills | `skills/` | `skillCapability` |
| slash-commands | `commands/` | `slashCommandCapability` |
| rules | `rules/` | `ruleCapability` |
| prompts | `prompts/` | `promptCapability` |
| instructions | `instructions/` | `instructionCapability` |
| hooks | `hooks/pre/`, `hooks/post/` | `hookCapability` |
| tools | `tools/` | `toolCapability` |
| extensions | `extensions/` | `extensionCapability` |
| mcp | `mcp.json`, `.mcp.json` | `mcpCapability` |
| settings | `settings.json` | `settingsCapability` |
| system-prompt | `SYSTEM.md` | `systemPromptCapability` |
## Subpath Summary
@@ -138,7 +138,7 @@ User-level root (~/.omp/, ~/.pi/, ~/.claude/) - not under agent/:
├── ssh/ ← SSH control sockets
└── remote/ ← SSHFS mount points
Project-level (.omp/, .pi/, .claude/, .codex/, .gemini/):
Project-level (.omp/, .claude/, .codex/, .gemini/):
├── SYSTEM.md ← Project system prompt
├── APPEND_SYSTEM.md ← Appended to system prompt
├── settings.json ← Project settings (via capability)
@@ -166,6 +166,7 @@ Logging is handled by `@oh-my-pi/pi-utils`, not by this package. Logs go to `~/.
### Config Priority
When multiple config directories exist, priority order is:
1. `.omp` (highest)
2. `.pi`
3. `.claude`
+4 -5
View File
@@ -20,7 +20,6 @@ export const VERSION: string = (packageJson as { version: string }).version;
const priorityList = [
{ dir: ".omp", globalAgentDir: ".omp/agent" },
{ dir: ".pi", globalAgentDir: ".pi/agent" },
{ dir: ".claude" },
{ dir: ".codex" },
{ dir: ".gemini" },
@@ -309,8 +308,8 @@ export function getDebugLogPath(): string {
/**
* Config directory bases in priority order (highest first).
* User-level: ~/.omp/agent, ~/.pi/agent, ~/.claude, ~/.codex, ~/.gemini
* Project-level: .omp, .pi, .claude, .codex, .gemini
* User-level: ~/.omp/agent, ~/.claude, ~/.codex, ~/.gemini
* Project-level: .omp, .claude, .codex, .gemini
*/
const USER_CONFIG_BASES = priorityList.map(({ dir, globalAgentDir }) => ({
base: () => path.join(os.homedir(), globalAgentDir ?? dir),
@@ -324,7 +323,7 @@ const PROJECT_CONFIG_BASES = priorityList.map(({ dir }) => ({
export interface ConfigDirEntry {
path: string;
source: string; // e.g., ".omp", ".pi", ".claude"
source: string; // e.g., ".omp", ".claude"
level: "user" | "project";
}
@@ -439,7 +438,7 @@ export function findConfigFileWithMeta(
/**
* Find all nearest config directories by walking up from cwd.
* Returns one entry per config base (.omp, .pi, .claude) - the nearest one found.
* 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[] {
+90 -118
View File
@@ -1,8 +1,7 @@
/**
* Builtin Provider (.omp / .pi)
* Builtin Provider (.omp)
*
* Primary provider for OMP native configs. Supports all capabilities.
* .pi is an alias for backwards compatibility.
*/
import * as path from "node:path";
import { logger } from "@oh-my-pi/pi-utils";
@@ -40,33 +39,56 @@ const DESCRIPTION = "Native OMP configuration from ~/.omp and .omp/";
const PRIORITY = 100;
const PATHS = SOURCE_PATHS.native;
const PROJECT_DIRS = [PATHS.projectDir, ...PATHS.aliases];
const USER_DIRS = [PATHS.userBase, ...PATHS.aliases];
async function ifNonEmptyDir(...seg: string[]): Promise<string | null> {
let dir = path.join(...seg);
const entries = await readDirEntries(dir);
if (entries.length > 0) {
if (!path.isAbsolute(dir)) {
dir = path.resolve(dir);
}
return dir;
}
return null;
}
async function getConfigDirs(ctx: LoadContext): Promise<Array<{ dir: string; level: "user" | "project" }>> {
const result: Array<{ dir: string; level: "user" | "project" }> = [];
for (const name of PROJECT_DIRS) {
const projectDir = path.join(ctx.cwd, name);
const entries = await readDirEntries(projectDir);
if (entries.length > 0) {
result.push({ dir: projectDir, level: "project" });
break;
}
const projectDir = await ifNonEmptyDir(ctx.cwd, PATHS.projectDir);
if (projectDir) {
result.push({ dir: projectDir, level: "project" });
}
for (const name of USER_DIRS) {
const userDir = path.join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, ""));
const entries = await readDirEntries(userDir);
if (entries.length > 0) {
result.push({ dir: userDir, level: "user" });
break;
}
const userDir = await ifNonEmptyDir(ctx.home, PATHS.userAgent);
if (userDir) {
result.push({ dir: userDir, level: "user" });
}
return result;
}
function getAncestorDirs(cwd: string): Array<{ dir: string; depth: number }> {
const ancestors: Array<{ dir: string; depth: number }> = [];
let current = cwd;
let depth = 0;
while (true) {
ancestors.push({ dir: current, depth });
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
depth++;
}
return ancestors;
}
async function findNearestProjectConfigDir(cwd: string): Promise<{ dir: string; depth: number } | null> {
for (const ancestor of getAncestorDirs(cwd)) {
const configDir = await ifNonEmptyDir(ancestor.dir, PATHS.projectDir);
if (configDir) return { dir: configDir, depth: ancestor.depth };
}
return null;
}
// MCP
async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
const items: MCPServer[] = [];
@@ -141,34 +163,27 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
return result;
};
const projectDirs = await Promise.all(
PROJECT_DIRS.map(async name => {
const dir = path.join(ctx.cwd, name);
const entries = await readDirEntries(dir);
return entries.length > 0 ? dir : null;
const paths = [
{ path: path.join(ctx.cwd, PATHS.projectDir, "mcp.json"), level: "project" as const },
{ path: path.join(ctx.cwd, PATHS.projectDir, ".mcp.json"), level: "project" as const },
{ path: path.join(ctx.home, PATHS.userAgent, "mcp.json"), level: "user" as const },
{ path: path.join(ctx.home, PATHS.userAgent, ".mcp.json"), level: "user" as const },
];
const contents = await Promise.allSettled(
paths.map(async p => {
const content = await readFile(p.path);
if (content) {
return { path: p.path, content, level: p.level };
}
return null;
}),
);
const userPaths = USER_DIRS.map(name => path.join(ctx.home, name, "mcp.json"));
const projectDir = projectDirs.find(dir => dir !== null);
if (projectDir) {
const projectCandidates = ["mcp.json", ".mcp.json"].map(filename => path.join(projectDir, filename));
const projectContents = await Promise.all(projectCandidates.map(path => readFile(path)));
for (let i = 0; i < projectCandidates.length; i++) {
const content = projectContents[i];
if (content) {
items.push(...parseMcpServers(content, projectCandidates[i], "project"));
break;
}
}
}
const userContents = await Promise.all(userPaths.map(path => readFile(path)));
for (let i = 0; i < userPaths.length; i++) {
const content = userContents[i];
if (content) {
items.push(...parseMcpServers(content, userPaths[i], "user"));
break;
for (const result of contents) {
if (result.status === "fulfilled" && result.value) {
const { path, content, level } = result.value;
items.push(...parseMcpServers(content, path, level));
}
}
@@ -187,49 +202,29 @@ registerProvider<MCPServer>(mcpCapability.id, {
async function loadSystemPrompt(ctx: LoadContext): Promise<LoadResult<SystemPrompt>> {
const items: SystemPrompt[] = [];
const userPaths = USER_DIRS.map(name =>
path.join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, ""), "SYSTEM.md"),
);
const userContents = await Promise.all(userPaths.map(p => readFile(p)));
for (let i = 0; i < userPaths.length; i++) {
const content = userContents[i];
if (content) {
items.push({
path: userPaths[i],
content,
level: "user",
_source: createSourceMeta(PROVIDER_ID, userPaths[i], "user"),
});
break;
}
const userPath = path.join(ctx.home, PATHS.userAgent, "SYSTEM.md");
const userContent = await readFile(userPath);
if (userContent) {
items.push({
path: userPath,
content: userContent,
level: "user",
_source: createSourceMeta(PROVIDER_ID, userPath, "user"),
});
}
const ancestors: string[] = [];
let current = ctx.cwd;
while (true) {
ancestors.push(current);
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
}
for (const dir of ancestors) {
const configDirs = PROJECT_DIRS.map(name => path.join(dir, name));
const entriesResults = await Promise.all(configDirs.map(d => readDirEntries(d)));
const validConfigDir = configDirs.find((_, i) => entriesResults[i].length > 0);
if (!validConfigDir) continue;
const projectPath = path.join(validConfigDir, "SYSTEM.md");
const content = await readFile(projectPath);
if (content) {
const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd);
if (nearestProjectConfigDir) {
const projectPath = path.join(nearestProjectConfigDir.dir, "SYSTEM.md");
const projectContent = await readFile(projectPath);
if (projectContent) {
items.push({
path: projectPath,
content,
content: projectContent,
level: "project",
_source: createSourceMeta(PROVIDER_ID, projectPath, "project"),
});
}
break;
}
return { items, warnings: [] };
@@ -782,55 +777,32 @@ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFil
const items: ContextFile[] = [];
const warnings: string[] = [];
const userPaths = USER_DIRS.map(name =>
path.join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, ""), "AGENTS.md"),
);
const userContents = await Promise.all(userPaths.map(p => readFile(p)));
for (let i = 0; i < userPaths.length; i++) {
const content = userContents[i];
if (content) {
items.push({
path: userPaths[i],
content,
level: "user",
_source: createSourceMeta(PROVIDER_ID, userPaths[i], "user"),
});
break;
}
const userPath = path.join(ctx.home, PATHS.userAgent, "AGENTS.md");
const userContent = await readFile(userPath);
if (userContent) {
items.push({
path: userPath,
content: userContent,
level: "user",
_source: createSourceMeta(PROVIDER_ID, userPath, "user"),
});
}
const ancestors: Array<{ dir: string; depth: number }> = [];
let current = ctx.cwd;
let depth = 0;
while (true) {
ancestors.push({ dir: current, depth });
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
depth++;
}
for (const { dir, depth: ancestorDepth } of ancestors) {
const configDirs = PROJECT_DIRS.map(name => path.join(dir, name));
const entriesResults = await Promise.all(configDirs.map(d => readDirEntries(d)));
const validConfigDir = configDirs.find((_, i) => entriesResults[i].length > 0);
if (!validConfigDir) continue;
const projectPath = path.join(validConfigDir, "AGENTS.md");
const content = await readFile(projectPath);
if (content) {
const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd);
if (nearestProjectConfigDir) {
const projectPath = path.join(nearestProjectConfigDir.dir, "AGENTS.md");
const projectContent = await readFile(projectPath);
if (projectContent) {
items.push({
path: projectPath,
content,
content: projectContent,
level: "project",
depth: ancestorDepth,
depth: nearestProjectConfigDir.depth,
_source: createSourceMeta(PROVIDER_ID, projectPath, "project"),
});
return { items, warnings };
}
break;
}
return { items, warnings };
}
@@ -42,7 +42,6 @@ export const SOURCE_PATHS = {
userBase: ".omp",
userAgent: ".omp/agent",
projectDir: ".omp",
aliases: [".pi"], // .pi is an alias for backwards compat
},
claude: {
userBase: ".claude",
+4 -4
View File
@@ -68,11 +68,11 @@ export async function discoverPythonModules(options: DiscoverPythonModulesOption
const cwd = options.cwd ?? process.cwd();
const homeDir = options.homeDir ?? os.homedir();
const userDirs = [path.join(homeDir, ".omp", "agent", "modules"), path.join(homeDir, ".pi", "agent", "modules")];
const projectDirs = [path.resolve(cwd, ".omp", "modules"), path.resolve(cwd, ".pi", "modules")];
const userDir = path.join(homeDir, ".omp", "agent", "modules");
const projectDir = path.resolve(cwd, ".omp", "modules");
const userCandidates = (await Promise.all(userDirs.map(dir => listModuleCandidates(dir, "user")))).flat();
const projectCandidates = (await Promise.all(projectDirs.map(dir => listModuleCandidates(dir, "project")))).flat();
const userCandidates = await listModuleCandidates(userDir, "user");
const projectCandidates = await listModuleCandidates(projectDir, "project");
const byName = new Map<string, ModuleCandidate>();
for (const candidate of userCandidates) {
@@ -1,11 +1,7 @@
import { afterEach, describe, expect, it } from "bun:test";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import {
discoverPythonModules,
loadPythonModules,
type PythonModuleExecutor,
} from "@oh-my-pi/pi-coding-agent/ipy/modules";
import { loadPythonModules, type PythonModuleExecutor } from "@oh-my-pi/pi-coding-agent/ipy/modules";
import { TempDir } from "@oh-my-pi/pi-utils";
const fixturesDir = path.resolve(import.meta.dir, "../../test/fixtures/python-modules");
@@ -28,32 +24,6 @@ describe("python modules", () => {
tempRoot = null;
});
it("discovers modules with project override and sorted order", async () => {
tempRoot = TempDir.createSync("@omp-python-modules-");
const homeDir = path.join(tempRoot.path(), "home");
const cwd = path.join(tempRoot.path(), "project");
await writeModule(path.join(homeDir, ".omp", "agent", "modules"), "alpha.py", "user-omp");
await writeModule(path.join(homeDir, ".pi", "agent", "modules"), "beta.py", "user-pi");
await writeModule(path.join(homeDir, ".pi", "agent", "modules"), "delta.py", "user-pi");
await writeModule(path.join(cwd, ".omp", "modules"), "alpha.py", "project-omp");
await writeModule(path.join(cwd, ".omp", "modules"), "beta.py", "project-omp");
await writeModule(path.join(cwd, ".pi", "modules"), "gamma.py", "project-pi");
const modules = await discoverPythonModules({ cwd, homeDir });
const names = modules.map(module => path.basename(module.path));
expect(names).toEqual(["alpha.py", "beta.py", "delta.py", "gamma.py"]);
expect(modules.map(module => ({ name: path.basename(module.path), source: module.source }))).toEqual([
{ name: "alpha.py", source: "project" },
{ name: "beta.py", source: "project" },
{ name: "delta.py", source: "user" },
{ name: "gamma.py", source: "project" },
]);
expect(modules.find(module => module.path.endsWith("alpha.py"))?.content).toContain("project-omp");
expect(modules.find(module => module.path.endsWith("delta.py"))?.content).toContain("user-pi");
});
it("loads modules in sorted order with silent execution", async () => {
tempRoot = TempDir.createSync("@omp-python-modules-");
const homeDir = path.join(tempRoot.path(), "home");
@@ -24,7 +24,7 @@ describe("Settings", () => {
fs.rmSync(testDir, { recursive: true });
}
fs.mkdirSync(agentDir, { recursive: true });
fs.mkdirSync(path.join(projectDir, ".pi"), { recursive: true });
fs.mkdirSync(path.join(projectDir, ".omp"), { recursive: true });
});
const getConfigPath = () => path.join(agentDir, "config.yml");
+2 -2
View File
@@ -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(), ".pi", "agent", "pi-debug.log");
const logPath = path.join(os.homedir(), ".omp", "agent", "omp-debug.log");
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(), ".pi", "agent", "pi-crash.log");
const crashLogPath = path.join(os.homedir(), ".omp", "agent", "omp-crash.log");
const crashData = [
`Crash at ${new Date().toISOString()}`,
`Terminal width: ${width}`,
+1 -3
View File
@@ -73,11 +73,10 @@ describe("CombinedAutocompleteProvider", () => {
});
it("includes hidden paths but excludes .git", async () => {
for (const dir of [".pi", ".github", ".git"]) {
for (const dir of [".github", ".git"]) {
fs.mkdirSync(path.join(baseDir, dir), { recursive: true });
}
fs.mkdirSync(path.join(baseDir, ".github", "workflows"), { recursive: true });
fs.writeFileSync(path.join(baseDir, ".pi", "config.json"), "{}");
fs.writeFileSync(path.join(baseDir, ".github", "workflows", "ci.yml"), "name: ci");
fs.writeFileSync(path.join(baseDir, ".git", "config"), "[core]");
@@ -86,7 +85,6 @@ describe("CombinedAutocompleteProvider", () => {
const result = await provider.getSuggestions([line], 0, line.length);
const values = result?.items.map(item => item.value) ?? [];
expect(values).toContain("@.pi/");
expect(values).toContain("@.github/");
expect(values.some(value => value === "@.git" || value.startsWith("@.git/"))).toBe(false);
});