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:
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user