feat: renamed binary from pi to omp with config migration support
- Renamed CLI binary and package from 'pi' to 'omp' across all platforms and installers. - Changed environment variable prefix from PI_* to OMP_* with automatic migration support. - Changed configuration directory from ~/.pi/ to ~/.omp/ with multi-path fallback discovery. - Added getConfigDirPaths(), findConfigFile(), and readConfigFile() functions for config resolution across .omp, .pi, and .claude directories.
This commit is contained in:
@@ -18,19 +18,19 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: bun-linux-x64
|
||||
artifact: pi-linux-x64
|
||||
artifact: omp-linux-x64
|
||||
- os: ubuntu-latest
|
||||
target: bun-linux-arm64
|
||||
artifact: pi-linux-arm64
|
||||
artifact: omp-linux-arm64
|
||||
- os: macos-latest
|
||||
target: bun-darwin-x64
|
||||
artifact: pi-darwin-x64
|
||||
artifact: omp-darwin-x64
|
||||
- os: macos-latest
|
||||
target: bun-darwin-arm64
|
||||
artifact: pi-darwin-arm64
|
||||
artifact: omp-darwin-arm64
|
||||
- os: ubuntu-latest
|
||||
target: bun-windows-x64
|
||||
artifact: pi-windows-x64.exe
|
||||
artifact: omp-windows-x64.exe
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ Parallel execution framework with specialized agents and real-time streaming:
|
||||
- **Parallel exploration**: Reviewer agent can spawn explore agents for large codebase analysis
|
||||
- **Real-time artifact streaming**: Task outputs stream as they're created, not just at completion
|
||||
- **Output tool**: Read full agent outputs by ID when truncated previews aren't sufficient
|
||||
- User-level (`~/.pi/agent/agents/`) and project-level (`.pi/agents/`) custom agents
|
||||
- User-level (`~/.omp/agent/agents/`) and project-level (`.omp/agents/`) custom agents
|
||||
- Concurrency-limited batch execution with progress tracking
|
||||
|
||||
## + Model Roles
|
||||
@@ -101,7 +101,7 @@ Configure different models for different purposes with automatic discovery:
|
||||
- **Three roles**: `default` (main model), `smol` (fast/cheap), `slow` (comprehensive reasoning)
|
||||
- **Auto-discovery**: Smol finds haiku → flash → mini; Slow finds codex → gpt → opus → pro
|
||||
- **Role-based selection**: Task tool agents can use `model: pi/smol` for cost-effective exploration
|
||||
- CLI args (`--smol`, `--slow`) and env vars (`PI_SMOL_MODEL`, `PI_SLOW_MODEL`)
|
||||
- CLI args (`--smol`, `--slow`) and env vars (`OMP_SMOL_MODEL`, `OMP_SLOW_MODEL`)
|
||||
- Configure via `/model` selector with keybindings (Enter=default, S=smol, L=slow)
|
||||
|
||||
## + Ask Tool (Interactive Questioning)
|
||||
@@ -136,7 +136,7 @@ Structured code review with priority-based findings:
|
||||
|
||||
Programmable commands with full API access:
|
||||
|
||||
- Create at `~/.pi/agent/commands/[name]/index.ts` or `.pi/commands/[name]/index.ts`
|
||||
- Create at `~/.omp/agent/commands/[name]/index.ts` or `.omp/commands/[name]/index.ts`
|
||||
- Export factory returning `{ name, description, execute(args, ctx) }`
|
||||
- Full access to `HookCommandContext` for UI dialogs, session control, shell execution
|
||||
- Return string to send as LLM prompt, or void for fire-and-forget actions
|
||||
@@ -151,8 +151,8 @@ Programmable commands with full API access:
|
||||
Full Model Context Protocol support with external tool integration:
|
||||
|
||||
- Stdio and HTTP transports for connecting to MCP servers
|
||||
- Plugin CLI (`pi plugin install/enable/configure/doctor`)
|
||||
- Hot-loadable plugins from `~/.pi/plugins/` with npm/bun integration
|
||||
- Plugin CLI (`omp plugin install/enable/configure/doctor`)
|
||||
- Hot-loadable plugins from `~/.omp/plugins/` with npm/bun integration
|
||||
- Automatic Exa MCP server filtering with API key extraction
|
||||
|
||||
## + Web Search & Fetch
|
||||
@@ -190,7 +190,7 @@ Handles whitespace and indentation variance automatically:
|
||||
|
||||
- **Git context**: System prompt includes branch, status, recent commits
|
||||
- **Bun runtime**: Native TypeScript execution, faster startup, all packages migrated
|
||||
- **Centralized file logging**: Debug logs with daily rotation to `~/.pi/logs/`
|
||||
- **Centralized file logging**: Debug logs with daily rotation to `~/.omp/logs/`
|
||||
- **Clipboard export**: `/export --copy` copies session as formatted text
|
||||
- **Bash interceptor**: Optionally block shell commands that have dedicated tools
|
||||
- **Hidden tools**: Custom tools can be excluded from default list unless explicitly requested
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
"name": "@oh-my-pi/pi-coding-agent",
|
||||
"version": "2.3.1337",
|
||||
"bin": {
|
||||
"pi": "src/cli.ts",
|
||||
"omp": "src/cli.ts",
|
||||
},
|
||||
"dependencies": {
|
||||
"@oh-my-pi/pi-agent-core": "workspace:*",
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "pi-monorepo",
|
||||
"name": "omp-monorepo",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
|
||||
@@ -321,7 +321,7 @@ describe("AI Providers Empty Message Tests", () => {
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// OAuth-based providers (credentials from ~/.pi/agent/oauth.json)
|
||||
// OAuth-based providers (credentials from ~/.omp/agent/auth.json)
|
||||
// =========================================================================
|
||||
|
||||
describe("Anthropic OAuth Provider Empty Messages", () => {
|
||||
|
||||
@@ -272,7 +272,7 @@ describe("Tool Results with Images", () => {
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// OAuth-based providers (credentials from ~/.pi/agent/oauth.json)
|
||||
// OAuth-based providers (credentials from ~/.omp/agent/auth.json)
|
||||
// =========================================================================
|
||||
|
||||
describe("Anthropic OAuth Provider (claude-sonnet-4-5)", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Test helper for resolving API keys from ~/.pi/agent/auth.json
|
||||
* Test helper for resolving API keys from ~/.omp/agent/auth.json
|
||||
*
|
||||
* Supports both API key and OAuth credentials.
|
||||
* OAuth tokens are automatically refreshed if expired and saved back to auth.json.
|
||||
@@ -10,7 +10,7 @@ import { dirname, join } from "node:path";
|
||||
import { getOAuthApiKey } from "../src/utils/oauth/index";
|
||||
import type { OAuthCredentials, OAuthProvider } from "../src/utils/oauth/types";
|
||||
|
||||
const AUTH_PATH = join(homedir(), ".pi", "agent", "auth.json");
|
||||
const AUTH_PATH = join(homedir(), ".omp", "agent", "auth.json");
|
||||
|
||||
type ApiKeyCredential = {
|
||||
type: "api_key";
|
||||
@@ -45,7 +45,7 @@ async function saveAuthStorage(storage: AuthStorage): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve API key for a provider from ~/.pi/agent/auth.json
|
||||
* Resolve API key for a provider from ~/.omp/agent/auth.json
|
||||
*
|
||||
* For API key credentials, returns the key directly.
|
||||
* For OAuth credentials, returns the access token (refreshing if expired and saving back).
|
||||
|
||||
@@ -655,7 +655,7 @@ describe("Generate E2E Tests", () => {
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// OAuth-based providers (credentials from ~/.pi/agent/oauth.json)
|
||||
// OAuth-based providers (credentials from ~/.omp/agent/auth.json)
|
||||
// Tokens are resolved at module level (see oauthTokens above)
|
||||
// =========================================================================
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ describe("Token Statistics on Abort", () => {
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// OAuth-based providers (credentials from ~/.pi/agent/oauth.json)
|
||||
// OAuth-based providers (credentials from ~/.omp/agent/auth.json)
|
||||
// =========================================================================
|
||||
|
||||
describe("Anthropic OAuth Provider", () => {
|
||||
|
||||
@@ -170,7 +170,7 @@ describe("Tool Call Without Result Tests", () => {
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// OAuth-based providers (credentials from ~/.pi/agent/oauth.json)
|
||||
// OAuth-based providers (credentials from ~/.omp/agent/auth.json)
|
||||
// =========================================================================
|
||||
|
||||
describe("Anthropic OAuth Provider", () => {
|
||||
|
||||
@@ -341,7 +341,7 @@ describe("AI Providers Unicode Surrogate Pair Tests", () => {
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// OAuth-based providers (credentials from ~/.pi/agent/oauth.json)
|
||||
// OAuth-based providers (credentials from ~/.omp/agent/auth.json)
|
||||
// =========================================================================
|
||||
|
||||
describe("Anthropic OAuth Provider Unicode Handling", () => {
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
|
||||
- Added automatic migration of `PI_*` environment variables to `OMP_*` equivalents for backwards compatibility
|
||||
- Added multi-path config discovery supporting `.omp`, `.pi`, and `.claude` directories with priority ordering
|
||||
- Added `getConfigDirPaths()`, `findConfigFile()`, and `readConfigFile()` functions for unified config resolution
|
||||
- Added documentation for config module usage patterns
|
||||
|
||||
### Changed
|
||||
|
||||
- Renamed CLI binary from `pi` to `omp` and updated all command references
|
||||
- Changed config directory from `.pi` to `.omp` with fallback support for legacy paths
|
||||
- Renamed environment variables from `PI_*` to `OMP_*` prefix (e.g., `OMP_SMOL_MODEL`, `OMP_SLOW_MODEL`)
|
||||
- Changed model role alias prefix from `pi/` to `omp/` (e.g., `omp/slow` instead of `pi/slow`)
|
||||
|
||||
## [2.3.1337] - 2026-01-03
|
||||
|
||||
@@ -10,7 +23,7 @@
|
||||
|
||||
### Added
|
||||
|
||||
- Added `pi update` command to check for and install updates from GitHub releases or via bun
|
||||
- Added `omp update` command to check for and install updates from GitHub releases or via bun
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -23,7 +36,7 @@
|
||||
### Added
|
||||
|
||||
- Added shell environment snapshot to preserve user aliases, functions, and shell options when executing bash commands
|
||||
- Added support for `PI_BASH_NO_CI`, `PI_BASH_NO_LOGIN`, and `PI_SHELL_PREFIX` environment variables for shell customization
|
||||
- Added support for `OMP_BASH_NO_CI`, `OMP_BASH_NO_LOGIN`, and `OMP_SHELL_PREFIX` environment variables for shell customization
|
||||
- Added zsh support alongside bash for shell detection and configuration
|
||||
|
||||
### Changed
|
||||
@@ -37,7 +50,7 @@
|
||||
- Added provider tabs to model selector with Tab/Arrow navigation for filtering models by provider
|
||||
- Added context menu to model selector for choosing model role (Default, Smol, Slow) instead of keyboard shortcuts
|
||||
- Added LSP diagnostics display in tool execution output showing errors and warnings after file edits
|
||||
- Added centralized file logger with daily rotation to `~/.pi/logs/` for debugging production issues
|
||||
- Added centralized file logger with daily rotation to `~/.omp/logs/` for debugging production issues
|
||||
- Added `logger` property to hook and custom tool APIs for error/warning/debug logging
|
||||
- Added `output` tool to read full agent/task outputs by ID when truncated previews are insufficient
|
||||
- Added `task` tool to reviewer agent, enabling parallel exploration of large codebases during reviews
|
||||
@@ -48,8 +61,8 @@
|
||||
- Added `explicitTools` option to `createAgentSession` for enabling hidden tools by name
|
||||
- Added example review tools (`report_finding`, `submit_review`) with structured findings accumulation and verdict rendering
|
||||
- Added `/review` example command for interactive code review with branch comparison, uncommitted changes, and commit review modes
|
||||
- Custom TypeScript slash commands: Create programmable commands at `~/.pi/agent/commands/[name]/index.ts` or `.pi/commands/[name]/index.ts`. Commands export a factory returning `{ name, description, execute(args, ctx) }`. Return a string to send as LLM prompt, or void for fire-and-forget actions. Full access to `HookCommandContext` for UI dialogs, session control, and shell execution.
|
||||
- Claude command directories: Markdown slash commands now also load from `~/.claude/commands/` and `.claude/commands/` (parallel to existing `.pi/commands/` support)
|
||||
- Custom TypeScript slash commands: Create programmable commands at `~/.omp/agent/commands/[name]/index.ts` or `.omp/commands/[name]/index.ts`. Commands export a factory returning `{ name, description, execute(args, ctx) }`. Return a string to send as LLM prompt, or void for fire-and-forget actions. Full access to `HookCommandContext` for UI dialogs, session control, and shell execution.
|
||||
- Claude command directories: Markdown slash commands now also load from `~/.claude/commands/` and `.claude/commands/` (parallel to existing `.omp/commands/` support)
|
||||
- `commands.enableClaudeUser` and `commands.enableClaudeProject` settings to disable Claude command directory loading
|
||||
- `/export --copy` option to copy entire session as formatted text to clipboard
|
||||
|
||||
@@ -106,7 +119,7 @@
|
||||
- Smol model auto-discovery using priority chain: haiku > flash > mini
|
||||
- Slow model auto-discovery using priority chain: gpt-5.2-codex > codex > gpt > opus > pro
|
||||
- CLI args for model roles: `--smol <model>` and `--slow <model>` (ephemeral, not persisted)
|
||||
- Env var overrides: `PI_SMOL_MODEL` and `PI_SLOW_MODEL`
|
||||
- Env var overrides: `OMP_SMOL_MODEL` and `OMP_SLOW_MODEL`
|
||||
- Title generation now uses configured smol model from settings
|
||||
- LSP diagnostics on edit: Edit tool can now return LSP diagnostics after editing code files. Disabled by default to avoid noise during multi-edit sequences. Enable via `lsp.diagnosticsOnEdit` setting.
|
||||
- LSP workspace diagnostics: New `lsp action=workspace_diagnostics` command checks the entire project for errors. Auto-detects project type and uses appropriate checker (rust-analyzer/cargo for Rust, tsc for TypeScript, go build for Go, pyright for Python).
|
||||
@@ -118,7 +131,7 @@
|
||||
- Edit fuzzy match setting: Added `edit.fuzzyMatch` setting (enabled by default) to control whether the edit tool accepts high-confidence fuzzy matches for whitespace/indentation differences. Toggle via `/settings`.
|
||||
- Multi-server LSP diagnostics: Diagnostics now query all applicable language servers for a file type. For TypeScript/JavaScript projects with Biome, this means both type errors (from tsserver) and lint errors (from Biome) are reported together.
|
||||
- Comprehensive LSP server configurations for 40+ languages including Rust, Go, Python, Java, Kotlin, Scala, Haskell, OCaml, Elixir, Ruby, PHP, C#, Lua, Nix, and many more. Each server includes sensible defaults for args, settings, and init options.
|
||||
- Extended LSP config file search paths: Now searches for `lsp.json`, `.lsp.json` in project root and `.pi/` subdirectory, plus user-level configs in `~/.pi/` and home directory.
|
||||
- Extended LSP config file search paths: Now searches for `lsp.json`, `.lsp.json` in project root and `.omp/` subdirectory, plus user-level configs in `~/.omp/` and home directory.
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -343,8 +356,8 @@ See [docs/custom-tools.md](docs/custom-tools.md) and [examples/custom-tools/](ex
|
||||
```typescript
|
||||
import { discoverAuthStorage, discoverModels } from "@oh-my-pi/pi-coding-agent";
|
||||
|
||||
const authStorage = discoverAuthStorage(); // ~/.pi/agent/auth.json
|
||||
const modelRegistry = discoverModels(authStorage); // + ~/.pi/agent/models.json
|
||||
const authStorage = discoverAuthStorage(); // ~/.omp/agent/auth.json
|
||||
const modelRegistry = discoverModels(authStorage); // + ~/.omp/agent/models.json
|
||||
|
||||
// Get all models (built-in + custom)
|
||||
const allModels = modelRegistry.getAll();
|
||||
@@ -479,25 +492,25 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Sessions saved to wrong directory**: In v0.30.0, sessions were being saved to `~/.pi/agent/` instead of `~/.pi/agent/sessions/<encoded-cwd>/`, breaking `--resume` and `/resume`. Misplaced sessions are automatically migrated on startup. ([#320](https://github.com/badlogic/pi-mono/issues/320) by [@aliou](https://github.com/aliou))
|
||||
- **Sessions saved to wrong directory**: In v0.30.0, sessions were being saved to `~/.omp/agent/` instead of `~/.omp/agent/sessions/<encoded-cwd>/`, breaking `--resume` and `/resume`. Misplaced sessions are automatically migrated on startup. ([#320](https://github.com/badlogic/pi-mono/issues/320) by [@aliou](https://github.com/aliou))
|
||||
- **Custom system prompts missing context**: When using a custom system prompt string, project context files (AGENTS.md), skills, date/time, and working directory were not appended. ([#321](https://github.com/badlogic/pi-mono/issues/321))
|
||||
|
||||
## [0.30.0] - 2025-12-25
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- **SessionManager API**: The second parameter of `create()`, `continueRecent()`, and `list()` changed from `agentDir` to `sessionDir`. When provided, it specifies the session directory directly (no cwd encoding). When omitted, uses default (`~/.pi/agent/sessions/<encoded-cwd>/`). `open()` no longer takes `agentDir`. ([#313](https://github.com/badlogic/pi-mono/pull/313))
|
||||
- **SessionManager API**: The second parameter of `create()`, `continueRecent()`, and `list()` changed from `agentDir` to `sessionDir`. When provided, it specifies the session directory directly (no cwd encoding). When omitted, uses default (`~/.omp/agent/sessions/<encoded-cwd>/`). `open()` no longer takes `agentDir`. ([#313](https://github.com/badlogic/pi-mono/pull/313))
|
||||
|
||||
### Added
|
||||
|
||||
- **`--session-dir` flag**: Use a custom directory for sessions instead of the default `~/.pi/agent/sessions/<encoded-cwd>/`. Works with `-c` (continue) and `-r` (resume) flags. ([#313](https://github.com/badlogic/pi-mono/pull/313) by [@scutifer](https://github.com/scutifer))
|
||||
- **`--session-dir` flag**: Use a custom directory for sessions instead of the default `~/.omp/agent/sessions/<encoded-cwd>/`. Works with `-c` (continue) and `-r` (resume) flags. ([#313](https://github.com/badlogic/pi-mono/pull/313) by [@scutifer](https://github.com/scutifer))
|
||||
- **Reverse model cycling and model selector**: Shift+Ctrl+P cycles models backward, Ctrl+L opens model selector (retaining text in editor). ([#315](https://github.com/badlogic/pi-mono/pull/315) by [@mitsuhiko](https://github.com/mitsuhiko))
|
||||
|
||||
## [0.29.1] - 2025-12-25
|
||||
|
||||
### Added
|
||||
|
||||
- **Automatic custom system prompt loading**: Pi now auto-loads `SYSTEM.md` files to replace the default system prompt. Project-local `.pi/SYSTEM.md` takes precedence over global `~/.pi/agent/SYSTEM.md`. CLI `--system-prompt` flag overrides both. ([#309](https://github.com/badlogic/pi-mono/issues/309))
|
||||
- **Automatic custom system prompt loading**: OMP now auto-loads `SYSTEM.md` files to replace the default system prompt. Project-local `.omp/SYSTEM.md` takes precedence over global `~/.omp/agent/SYSTEM.md`. CLI `--system-prompt` flag overrides both. ([#309](https://github.com/badlogic/pi-mono/issues/309))
|
||||
- **Unified `/settings` command**: New settings menu consolidating thinking level, theme, queue mode, auto-compact, show images, hide thinking, and collapse changelog. Replaces individual `/thinking`, `/queue`, `/theme`, `/autocompact`, and `/show-images` commands. ([#310](https://github.com/badlogic/pi-mono/issues/310))
|
||||
|
||||
### Fixed
|
||||
@@ -524,7 +537,7 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
||||
|
||||
### Changed
|
||||
|
||||
- **Credential storage refactored**: API keys and OAuth tokens are now stored in `~/.pi/agent/auth.json` instead of `oauth.json` and `settings.json`. Existing credentials are automatically migrated on first run. ([#296](https://github.com/badlogic/pi-mono/issues/296))
|
||||
- **Credential storage refactored**: API keys and OAuth tokens are now stored in `~/.omp/agent/auth.json` instead of `oauth.json` and `settings.json`. Existing credentials are automatically migrated on first run. ([#296](https://github.com/badlogic/pi-mono/issues/296))
|
||||
|
||||
- **SDK API changes** ([#296](https://github.com/badlogic/pi-mono/issues/296)):
|
||||
|
||||
@@ -592,13 +605,13 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Symlinked skill directories**: Skills in symlinked directories (e.g., `~/.pi/agent/skills/my-skills -> /path/to/skills`) are now correctly discovered and loaded.
|
||||
- **Symlinked skill directories**: Skills in symlinked directories (e.g., `~/.omp/agent/skills/my-skills -> /path/to/skills`) are now correctly discovered and loaded.
|
||||
|
||||
## [0.27.3] - 2025-12-24
|
||||
|
||||
### Added
|
||||
|
||||
- **API keys in settings.json**: Store API keys in `~/.pi/agent/settings.json` under the `apiKeys` field (e.g., `{ "apiKeys": { "anthropic": "sk-..." } }`). Settings keys take priority over environment variables. ([#295](https://github.com/badlogic/pi-mono/issues/295))
|
||||
- **API keys in settings.json**: Store API keys in `~/.omp/agent/settings.json` under the `apiKeys` field (e.g., `{ "apiKeys": { "anthropic": "sk-..." } }`). Settings keys take priority over environment variables. ([#295](https://github.com/badlogic/pi-mono/issues/295))
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -619,7 +632,7 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
||||
|
||||
### Added
|
||||
|
||||
- **Startup timing instrumentation**: Set `PI_TIMING=1` to see startup performance breakdown (interactive mode only).
|
||||
- **Startup timing instrumentation**: Set `OMP_TIMING=1` to see startup performance breakdown (interactive mode only).
|
||||
|
||||
## [0.27.0] - 2025-12-22
|
||||
|
||||
@@ -649,7 +662,7 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
||||
|
||||
- **SDK for programmatic usage**: New `createAgentSession()` factory with full control over model, tools, hooks, skills, session persistence, and settings. Philosophy: "omit to discover, provide to override". Includes 12 examples and comprehensive documentation. ([#272](https://github.com/badlogic/pi-mono/issues/272))
|
||||
|
||||
- **Project-specific settings**: Settings now load from both `~/.pi/agent/settings.json` (global) and `<cwd>/.pi/settings.json` (project). Project settings override global with deep merge for nested objects. Project settings are read-only (for version control). ([#276](https://github.com/badlogic/pi-mono/pull/276))
|
||||
- **Project-specific settings**: Settings now load from both `~/.omp/agent/settings.json` (global) and `<cwd>/.omp/settings.json` (project). Project settings override global with deep merge for nested objects. Project settings are read-only (for version control). ([#276](https://github.com/badlogic/pi-mono/pull/276))
|
||||
|
||||
- **SettingsManager static factories**: `SettingsManager.create(cwd?, agentDir?)` for file-based settings, `SettingsManager.inMemory(settings?)` for testing. Added `applyOverrides()` for programmatic overrides.
|
||||
|
||||
@@ -669,7 +682,7 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
||||
|
||||
- **External editor support**: Press `Ctrl+G` to edit your message in an external editor. Uses `$VISUAL` or `$EDITOR` environment variable. On successful save, the message is replaced; on cancel, the original is kept. ([#266](https://github.com/badlogic/pi-mono/pull/266) by [@aliou](https://github.com/aliou))
|
||||
|
||||
- **Process suspension**: Press `Ctrl+Z` to suspend pi and return to the shell. Resume with `fg` as usual. ([#267](https://github.com/badlogic/pi-mono/pull/267) by [@aliou](https://github.com/aliou))
|
||||
- **Process suspension**: Press `Ctrl+Z` to suspend omp and return to the shell. Resume with `fg` as usual. ([#267](https://github.com/badlogic/pi-mono/pull/267) by [@aliou](https://github.com/aliou))
|
||||
|
||||
- **Configurable skills directories**: Added granular control over skill sources with `enableCodexUser`, `enableClaudeUser`, `enableClaudeProject`, `enablePiUser`, `enablePiProject` toggles, plus `customDirectories` and `ignoredSkills` settings. ([#269](https://github.com/badlogic/pi-mono/pull/269) by [@nicobailon](https://github.com/nicobailon))
|
||||
|
||||
@@ -753,7 +766,7 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
||||
|
||||
- **OpenRouter models with colons in IDs**: Fixed parsing of OpenRouter model IDs that contain colons (e.g., `openrouter:meta-llama/llama-4-scout:free`). ([#242](https://github.com/badlogic/pi-mono/pull/242) by [@aliou](https://github.com/aliou))
|
||||
|
||||
- **Global AGENTS.md loaded twice**: Fixed global AGENTS.md being loaded twice when present in both `~/.pi/agent/` and the current directory. ([#239](https://github.com/badlogic/pi-mono/pull/239) by [@aliou](https://github.com/aliou))
|
||||
- **Global AGENTS.md loaded twice**: Fixed global AGENTS.md being loaded twice when present in both `~/.omp/agent/` and the current directory. ([#239](https://github.com/badlogic/pi-mono/pull/239) by [@aliou](https://github.com/aliou))
|
||||
|
||||
- **Kitty keyboard protocol on Linux**: Fixed keyboard input not working in Ghostty on Linux when Num Lock is enabled. The Kitty protocol includes Caps Lock and Num Lock state in modifier values, which broke key detection. Now correctly masks out lock key bits when matching keyboard shortcuts. ([#243](https://github.com/badlogic/pi-mono/issues/243))
|
||||
|
||||
@@ -785,13 +798,13 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
||||
|
||||
### Fixed
|
||||
|
||||
- **JSON mode stdout flush**: Fixed race condition where `pi --mode json` could exit before all output was written to stdout, causing consumers to miss final events.
|
||||
- **JSON mode stdout flush**: Fixed race condition where `omp --mode json` could exit before all output was written to stdout, causing consumers to miss final events.
|
||||
|
||||
- **Symlinked tools, hooks, and slash commands**: Discovery now correctly follows symlinks when scanning for custom tools, hooks, and slash commands. ([#219](https://github.com/badlogic/pi-mono/pull/219), [#232](https://github.com/badlogic/pi-mono/pull/232) by [@aliou](https://github.com/aliou))
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- **Custom tools now require `index.ts` entry point**: Auto-discovered custom tools must be in a subdirectory with an `index.ts` file. The old pattern `~/.pi/agent/tools/mytool.ts` must become `~/.pi/agent/tools/mytool/index.ts`. This allows multi-file tools to import helper modules. Explicit paths via `--tool` or `settings.json` still work with any `.ts` file.
|
||||
- **Custom tools now require `index.ts` entry point**: Auto-discovered custom tools must be in a subdirectory with an `index.ts` file. The old pattern `~/.omp/agent/tools/mytool.ts` must become `~/.omp/agent/tools/mytool/index.ts`. This allows multi-file tools to import helper modules. Explicit paths via `--tool` or `settings.json` still work with any `.ts` file.
|
||||
|
||||
- **Hook `tool_result` event restructured**: The `ToolResultEvent` now exposes full tool result data instead of just text. ([#233](https://github.com/badlogic/pi-mono/pull/233))
|
||||
- Removed: `result: string` field
|
||||
@@ -859,13 +872,13 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
||||
|
||||
- Fixed TUI performance regression caused by Box component lacking render caching. Built-in tools now use Text directly (like v0.22.5), and Box has proper caching for custom tool rendering.
|
||||
|
||||
- Fixed custom tools failing to load from `~/.pi/agent/tools/` when pi is installed globally. Module imports (`@sinclair/typebox`, `@oh-my-pi/pi-tui`, `@oh-my-pi/pi-ai`) are now resolved via aliases.
|
||||
- Fixed custom tools failing to load from `~/.omp/agent/tools/` when omp is installed globally. Module imports (`@sinclair/typebox`, `@oh-my-pi/pi-tui`, `@oh-my-pi/pi-ai`) are now resolved via aliases.
|
||||
|
||||
## [0.23.0] - 2025-12-17
|
||||
|
||||
### Added
|
||||
|
||||
- **Custom tools**: Extend pi with custom tools written in TypeScript. Tools can provide custom TUI rendering, interact with users via `pi.ui` (select, confirm, input, notify), and maintain state across sessions via `onSession` callback. See [docs/custom-tools.md](docs/custom-tools.md) and [examples/custom-tools/](examples/custom-tools/). ([#190](https://github.com/badlogic/pi-mono/issues/190))
|
||||
- **Custom tools**: Extend omp with custom tools written in TypeScript. Tools can provide custom TUI rendering, interact with users via `omp.ui` (select, confirm, input, notify), and maintain state across sessions via `onSession` callback. See [docs/custom-tools.md](docs/custom-tools.md) and [examples/custom-tools/](examples/custom-tools/). ([#190](https://github.com/badlogic/pi-mono/issues/190))
|
||||
|
||||
- **Hook and tool examples**: Added `examples/hooks/` and `examples/custom-tools/` with working examples. Examples are now bundled in npm and binary releases.
|
||||
|
||||
@@ -955,7 +968,7 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- **Pi skills now use `SKILL.md` convention**: Pi skills must now be named `SKILL.md` inside a directory, matching Codex CLI format. Previously any `*.md` file was treated as a skill. Migrate by renaming `~/.pi/agent/skills/foo.md` to `~/.pi/agent/skills/foo/SKILL.md`.
|
||||
- **OMP skills now use `SKILL.md` convention**: OMP skills must now be named `SKILL.md` inside a directory, matching Codex CLI format. Previously any `*.md` file was treated as a skill. Migrate by renaming `~/.omp/agent/skills/foo.md` to `~/.omp/agent/skills/foo/SKILL.md`.
|
||||
|
||||
### Added
|
||||
|
||||
@@ -971,7 +984,7 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
|
||||
|
||||
### Added
|
||||
|
||||
- **Skills system**: Auto-discover and load instruction files on-demand. Supports Claude Code (`~/.claude/skills/*/SKILL.md`), Codex CLI (`~/.codex/skills/`), and Pi-native formats (`~/.pi/agent/skills/`, `.pi/skills/`). Skills are listed in system prompt with descriptions, agent loads them via read tool when needed. Supports `{baseDir}` placeholder. Disable with `--no-skills` or `skills.enabled: false` in settings. ([#169](https://github.com/badlogic/pi-mono/issues/169))
|
||||
- **Skills system**: Auto-discover and load instruction files on-demand. Supports Claude Code (`~/.claude/skills/*/SKILL.md`), Codex CLI (`~/.codex/skills/`), and OMP-native formats (`~/.omp/agent/skills/`, `.omp/skills/`). Skills are listed in system prompt with descriptions, agent loads them via read tool when needed. Supports `{baseDir}` placeholder. Disable with `--no-skills` or `skills.enabled: false` in settings. ([#169](https://github.com/badlogic/pi-mono/issues/169))
|
||||
|
||||
- **Version flag**: Added `--version` / `-v` flag to display the current version and exit. ([#170](https://github.com/badlogic/pi-mono/pull/170))
|
||||
|
||||
@@ -989,7 +1002,7 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
|
||||
|
||||
- **In-memory branching for `--no-session` mode**: Branching now works correctly in `--no-session` mode without creating any session files. The conversation is truncated in memory.
|
||||
|
||||
- **Git branch indicator now works in subdirectories**: The footer's git branch detection now walks up the directory hierarchy to find the git root, so it works when running pi from a subdirectory of a repository. ([#156](https://github.com/badlogic/pi-mono/issues/156))
|
||||
- **Git branch indicator now works in subdirectories**: The footer's git branch detection now walks up the directory hierarchy to find the git root, so it works when running omp from a subdirectory of a repository. ([#156](https://github.com/badlogic/pi-mono/issues/156))
|
||||
|
||||
## [0.18.1] - 2025-12-10
|
||||
|
||||
@@ -1005,7 +1018,7 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
|
||||
|
||||
### Added
|
||||
|
||||
- **Hooks system**: TypeScript modules that extend agent behavior by subscribing to lifecycle events. Hooks can intercept tool calls, prompt for confirmation, modify results, and inject messages from external sources. Auto-discovered from `~/.pi/agent/hooks/*.ts` and `.pi/hooks/*.ts`. Thanks to [@nicobailon](https://github.com/nicobailon) for the collaboration on the design and implementation. ([#145](https://github.com/badlogic/pi-mono/issues/145), supersedes [#158](https://github.com/badlogic/pi-mono/pull/158))
|
||||
- **Hooks system**: TypeScript modules that extend agent behavior by subscribing to lifecycle events. Hooks can intercept tool calls, prompt for confirmation, modify results, and inject messages from external sources. Auto-discovered from `~/.omp/agent/hooks/*.ts` and `.omp/hooks/*.ts`. Thanks to [@nicobailon](https://github.com/nicobailon) for the collaboration on the design and implementation. ([#145](https://github.com/badlogic/pi-mono/issues/145), supersedes [#158](https://github.com/badlogic/pi-mono/pull/158))
|
||||
|
||||
- **`pi.send()` API**: Hooks can inject messages into the agent session from external sources (file watchers, webhooks, CI systems). If streaming, messages are queued; otherwise a new agent loop starts immediately.
|
||||
|
||||
@@ -1083,7 +1096,7 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
|
||||
|
||||
- **xhigh thinking level**: Added `xhigh` thinking level for OpenAI codex-max models. Cycle through thinking levels with Shift+Tab; `xhigh` appears only when using a codex-max model. ([#143](https://github.com/badlogic/pi-mono/issues/143))
|
||||
|
||||
- **Collapse changelog setting**: Add `"collapseChangelog": true` to `~/.pi/agent/settings.json` to show a condensed "Updated to vX.Y.Z" message instead of the full changelog after updates. Use `/changelog` to view the full changelog. ([#148](https://github.com/badlogic/pi-mono/issues/148))
|
||||
- **Collapse changelog setting**: Add `"collapseChangelog": true` to `~/.omp/agent/settings.json` to show a condensed "Updated to vX.Y.Z" message instead of the full changelog after updates. Use `/changelog` to view the full changelog. ([#148](https://github.com/badlogic/pi-mono/issues/148))
|
||||
|
||||
- **Bash mode**: Execute shell commands directly from the editor by prefixing with `!` (e.g., `!ls -la`). Output streams in real-time, is added to the LLM context, and persists in session history. Supports multiline commands, cancellation (Escape), truncation for large outputs, and preview/expand toggle (Ctrl+O). Also available in RPC mode via `{"type":"bash","command":"..."}`. ([#112](https://github.com/badlogic/pi-mono/pull/112), original implementation by [@markusylisiurunen](https://github.com/markusylisiurunen))
|
||||
|
||||
@@ -1103,7 +1116,7 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
|
||||
|
||||
### Added
|
||||
|
||||
- **Flexible Windows shell configuration**: The bash tool now supports multiple shell sources beyond Git Bash. Resolution order: (1) custom `shellPath` in settings.json, (2) Git Bash in standard locations, (3) any bash.exe on PATH. This enables Cygwin, MSYS2, and other bash environments. Configure with `~/.pi/agent/settings.json`: `{"shellPath": "C:\\cygwin64\\bin\\bash.exe"}`.
|
||||
- **Flexible Windows shell configuration**: The bash tool now supports multiple shell sources beyond Git Bash. Resolution order: (1) custom `shellPath` in settings.json, (2) Git Bash in standard locations, (3) any bash.exe on PATH. This enables Cygwin, MSYS2, and other bash environments. Configure with `~/.omp/agent/settings.json`: `{"shellPath": "C:\\cygwin64\\bin\\bash.exe"}`.
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1197,7 +1210,7 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
|
||||
|
||||
### Added
|
||||
|
||||
- **Forking/Rebranding Support**: All branding (app name, config directory, environment variable names) is now configurable via `piConfig` in `package.json`. Forks can change `piConfig.name` and `piConfig.configDir` to rebrand the CLI without code changes. Affects CLI banner, help text, config paths, and error messages. ([#95](https://github.com/badlogic/pi-mono/pull/95))
|
||||
- **Forking/Rebranding Support**: All branding (app name, config directory, environment variable names) is now configurable via `ompConfig` in `package.json`. Forks can change `ompConfig.name` and `ompConfig.configDir` to rebrand the CLI without code changes. Affects CLI banner, help text, config paths, and error messages. ([#95](https://github.com/badlogic/pi-mono/pull/95))
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1316,7 +1329,7 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
|
||||
|
||||
### Added
|
||||
|
||||
- **File-based Slash Commands**: Create custom reusable prompts as `.txt` files in `~/.pi/slash-commands/`. Files become `/filename` commands with first-line descriptions. Supports `{{selection}}` placeholder for referencing selected/attached content.
|
||||
- **File-based Slash Commands**: Create custom reusable prompts as `.txt` files in `~/.omp/slash-commands/`. Files become `/filename` commands with first-line descriptions. Supports `{{selection}}` placeholder for referencing selected/attached content.
|
||||
- **`/branch` Command**: Create conversation branches from any previous user message. Opens a selector to pick a message, then creates a new session file starting from that point. Original message text is placed in the editor for modification.
|
||||
- **Unified Content References**: Both `@path` in messages and `--file path` CLI arguments now use the same attachment system with consistent MIME type detection.
|
||||
- **Drag & Drop Files**: Drop files onto the terminal to attach them to your message. Supports multiple files and both text and image content.
|
||||
@@ -1378,7 +1391,7 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
|
||||
|
||||
### Added
|
||||
|
||||
- Added thinking level persistence. Default level stored in `~/.pi/settings.json`, restored on startup. Per-session overrides saved in session files.
|
||||
- Added thinking level persistence. Default level stored in `~/.omp/settings.json`, restored on startup. Per-session overrides saved in session files.
|
||||
- Added model cycling shortcut: `Ctrl+I` cycles through available models (or scoped models with `-m` flag).
|
||||
- Added automatic retry with exponential backoff for transient API errors (network issues, 500s, overload).
|
||||
- Cumulative token usage now shown in footer (total tokens used across all messages in session).
|
||||
@@ -1395,7 +1408,7 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
|
||||
|
||||
### Added
|
||||
|
||||
- Add custom model configuration via `~/.pi/models.json`
|
||||
- Add custom model configuration via `~/.omp/models.json`
|
||||
|
||||
## [0.10.0] - 2025-11-25
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ src/
|
||||
│ ├── session-manager.ts # SessionManager class - JSONL persistence
|
||||
│ ├── settings-manager.ts # SettingsManager class - user preferences
|
||||
│ ├── skills.ts # loadSkills(), skill discovery from multiple locations
|
||||
│ ├── slash-commands.ts # loadSlashCommands() from ~/.pi/agent/commands/
|
||||
│ ├── slash-commands.ts # loadSlashCommands() from ~/.omp/agent/commands/
|
||||
│ ├── system-prompt.ts # buildSystemPrompt(), loadProjectContextFiles()
|
||||
│ │
|
||||
│ ├── oauth/ # OAuth authentication (thin wrapper)
|
||||
@@ -198,7 +198,7 @@ Handles user preferences:
|
||||
|
||||
Extensibility layer for intercepting agent behavior:
|
||||
|
||||
- **loader.ts**: Discovers and loads hooks from `~/.pi/agent/hooks/`, `.pi/hooks/`, and CLI
|
||||
- **loader.ts**: Discovers and loads hooks from `~/.omp/agent/hooks/`, `.omp/hooks/`, and CLI
|
||||
- **runner.ts**: Dispatches events to registered hooks
|
||||
- **tool-wrapper.ts**: Wraps tools to emit `tool_call` and `tool_result` events
|
||||
- **types.ts**: Event types (`session`, `tool_call`, `tool_result`, `message`, `error`)
|
||||
@@ -209,7 +209,7 @@ See [docs/hooks.md](docs/hooks.md) for full documentation.
|
||||
|
||||
System for adding LLM-callable tools:
|
||||
|
||||
- **loader.ts**: Discovers and loads tools from `~/.pi/agent/tools/`, `.pi/tools/`, and CLI
|
||||
- **loader.ts**: Discovers and loads tools from `~/.omp/agent/tools/`, `.omp/tools/`, and CLI
|
||||
- **types.ts**: `CustomToolFactory`, `CustomToolDefinition`, `CustomToolResult`
|
||||
|
||||
See [docs/custom-tools.md](docs/custom-tools.md) for full documentation.
|
||||
|
||||
+100
-100
@@ -1,4 +1,4 @@
|
||||
# pi
|
||||
# omp
|
||||
|
||||
A terminal-based coding agent with multi-model support, mid-session model switching, and a simple CLI for headless coding tasks.
|
||||
|
||||
@@ -51,7 +51,7 @@ Works on Linux, macOS, and Windows (requires bash; see [Windows Setup](#windows-
|
||||
**npm (recommended):**
|
||||
|
||||
```bash
|
||||
npm install -g @oh-my-pi/pi-coding-agent
|
||||
npm install -g @oh-my-pi/omp-coding-agent
|
||||
```
|
||||
|
||||
**Standalone binary:**
|
||||
@@ -60,23 +60,23 @@ Download from [GitHub Releases](https://github.com/badlogic/pi-mono/releases):
|
||||
|
||||
| Platform | Archive |
|
||||
| ------------------- | ------------------------ |
|
||||
| macOS Apple Silicon | `pi-darwin-arm64.tar.gz` |
|
||||
| macOS Intel | `pi-darwin-x64.tar.gz` |
|
||||
| Linux x64 | `pi-linux-x64.tar.gz` |
|
||||
| Linux ARM64 | `pi-linux-arm64.tar.gz` |
|
||||
| Windows x64 | `pi-windows-x64.zip` |
|
||||
| macOS Apple Silicon | `omp-darwin-arm64.tar.gz` |
|
||||
| macOS Intel | `omp-darwin-x64.tar.gz` |
|
||||
| Linux x64 | `omp-linux-x64.tar.gz` |
|
||||
| Linux ARM64 | `omp-linux-arm64.tar.gz` |
|
||||
| Windows x64 | `omp-windows-x64.zip` |
|
||||
|
||||
```bash
|
||||
# macOS/Linux
|
||||
tar -xzf pi-darwin-arm64.tar.gz
|
||||
./pi
|
||||
tar -xzf omp-darwin-arm64.tar.gz
|
||||
./omp
|
||||
|
||||
# Windows
|
||||
unzip pi-windows-x64.zip
|
||||
pi.exe
|
||||
unzip omp-windows-x64.zip
|
||||
omp.exe
|
||||
```
|
||||
|
||||
**macOS note:** The binary is unsigned. If blocked, run: `xattr -c ./pi`
|
||||
**macOS note:** The binary is unsigned. If blocked, run: `xattr -c ./omp`
|
||||
|
||||
**Build from source** (requires [Bun](https://bun.sh) 1.0+):
|
||||
|
||||
@@ -84,14 +84,14 @@ pi.exe
|
||||
git clone https://github.com/can1357/oh-my-pi.git
|
||||
cd pi-mono && npm install
|
||||
cd packages/coding-agent && npm run build:binary
|
||||
./dist/pi
|
||||
./dist/omp
|
||||
```
|
||||
|
||||
### Windows Setup
|
||||
|
||||
Pi requires a bash shell on Windows. Checked locations (in order):
|
||||
Omp requires a bash shell on Windows. Checked locations (in order):
|
||||
|
||||
1. Custom path from `~/.pi/agent/settings.json`
|
||||
1. Custom path from `~/.omp/agent/settings.json`
|
||||
2. Git Bash (`C:\Program Files\Git\bin\bash.exe`)
|
||||
3. `bash.exe` on PATH (Cygwin, MSYS2, WSL)
|
||||
|
||||
@@ -100,7 +100,7 @@ For most users, [Git for Windows](https://git-scm.com/download/win) is sufficien
|
||||
**Custom shell path:**
|
||||
|
||||
```json
|
||||
// ~/.pi/agent/settings.json
|
||||
// ~/.omp/agent/settings.json
|
||||
{
|
||||
"shellPath": "C:\\cygwin64\\bin\\bash.exe"
|
||||
}
|
||||
@@ -110,7 +110,7 @@ For most users, [Git for Windows](https://git-scm.com/download/win) is sufficien
|
||||
|
||||
**Option 1: Auth file** (recommended)
|
||||
|
||||
Add API keys to `~/.pi/agent/auth.json`:
|
||||
Add API keys to `~/.omp/agent/auth.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -148,7 +148,7 @@ Use `/login` to authenticate with subscription-based or free-tier providers:
|
||||
| Google Antigravity | Gemini 3, Claude, GPT-OSS | Free (Google account) |
|
||||
|
||||
```bash
|
||||
pi
|
||||
omp
|
||||
/login # Select provider, authorize in browser
|
||||
```
|
||||
|
||||
@@ -165,13 +165,13 @@ pi
|
||||
- Antigravity uses a sandbox endpoint with access to Gemini 3, Claude (sonnet/opus thinking), and GPT-OSS models
|
||||
- Both are free with any Google account, subject to rate limits
|
||||
|
||||
Credentials stored in `~/.pi/agent/auth.json`. Use `/logout` to clear.
|
||||
Credentials stored in `~/.omp/agent/auth.json`. Use `/logout` to clear.
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
export ANTHROPIC_API_KEY=sk-ant-...
|
||||
pi
|
||||
omp
|
||||
```
|
||||
|
||||
Then chat:
|
||||
@@ -303,18 +303,18 @@ See [docs/session.md](docs/session.md) for the file format and programmatic API.
|
||||
|
||||
### Session Management
|
||||
|
||||
Sessions auto-save to `~/.pi/agent/sessions/` organized by working directory.
|
||||
Sessions auto-save to `~/.omp/agent/sessions/` organized by working directory.
|
||||
|
||||
```bash
|
||||
pi --continue # Continue most recent session
|
||||
pi -c # Short form
|
||||
omp --continue # Continue most recent session
|
||||
omp -c # Short form
|
||||
|
||||
pi --resume # Browse and select from past sessions
|
||||
pi -r # Short form
|
||||
omp --resume # Browse and select from past sessions
|
||||
omp -r # Short form
|
||||
|
||||
pi --no-session # Ephemeral mode (don't save)
|
||||
omp --no-session # Ephemeral mode (don't save)
|
||||
|
||||
pi --session /path/to/file.jsonl # Use specific session file
|
||||
omp --session /path/to/file.jsonl # Use specific session file
|
||||
```
|
||||
|
||||
### Context Compaction
|
||||
@@ -330,7 +330,7 @@ Long sessions can exhaust context windows. Compaction summarizes older messages
|
||||
|
||||
When disabled, neither case triggers automatic compaction (use `/compact` manually if needed).
|
||||
|
||||
**Configuration** (`~/.pi/agent/settings.json`):
|
||||
**Configuration** (`~/.omp/agent/settings.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -368,9 +368,9 @@ See [docs/compaction.md](docs/compaction.md) for how compaction works internally
|
||||
|
||||
### Project Context Files
|
||||
|
||||
Pi loads `AGENTS.md` (or `CLAUDE.md`) files at startup in this order:
|
||||
Omp loads `AGENTS.md` (or `CLAUDE.md`) files at startup in this order:
|
||||
|
||||
1. **Global:** `~/.pi/agent/AGENTS.md`
|
||||
1. **Global:** `~/.omp/agent/AGENTS.md`
|
||||
2. **Parent directories:** Walking up from current directory
|
||||
3. **Current directory:** `./AGENTS.md`
|
||||
|
||||
@@ -398,10 +398,10 @@ Use these for:
|
||||
|
||||
Replace the default system prompt entirely by creating a `SYSTEM.md` file:
|
||||
|
||||
1. **Project-local:** `.pi/SYSTEM.md` (takes precedence)
|
||||
2. **Global:** `~/.pi/agent/SYSTEM.md` (fallback)
|
||||
1. **Project-local:** `.omp/SYSTEM.md` (takes precedence)
|
||||
2. **Global:** `~/.omp/agent/SYSTEM.md` (fallback)
|
||||
|
||||
This is useful when using pi as different types of agents across repos (coding assistant, personal assistant, domain-specific agent, etc.).
|
||||
This is useful when using omp as different types of agents across repos (coding assistant, personal assistant, domain-specific agent, etc.).
|
||||
|
||||
```markdown
|
||||
You are a technical writing assistant. Help users write clear documentation.
|
||||
@@ -417,7 +417,7 @@ The `--system-prompt` CLI flag overrides both files. Use `--append-system-prompt
|
||||
|
||||
### Custom Models and Providers
|
||||
|
||||
Add custom models (Ollama, vLLM, LM Studio, etc.) via `~/.pi/agent/models.json`:
|
||||
Add custom models (Ollama, vLLM, LM Studio, etc.) via `~/.omp/agent/models.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -488,18 +488,18 @@ Add custom models (Ollama, vLLM, LM Studio, etc.) via `~/.pi/agent/models.json`:
|
||||
4. Saved default from settings
|
||||
5. First available model with valid API key
|
||||
|
||||
> pi can help you create custom provider and model configurations.
|
||||
> omp can help you create custom provider and model configurations.
|
||||
|
||||
### Settings File
|
||||
|
||||
Settings are loaded from two locations and merged:
|
||||
|
||||
1. **Global:** `~/.pi/agent/settings.json` - user preferences
|
||||
2. **Project:** `<cwd>/.pi/settings.json` - project-specific overrides (version control friendly)
|
||||
1. **Global:** `~/.omp/agent/settings.json` - user preferences
|
||||
2. **Project:** `<cwd>/.omp/settings.json` - project-specific overrides (version control friendly)
|
||||
|
||||
Project settings override global settings. For nested objects, individual keys merge. Settings changed via TUI (model, thinking level, etc.) are saved to global preferences only.
|
||||
|
||||
Global `~/.pi/agent/settings.json` stores persistent preferences:
|
||||
Global `~/.omp/agent/settings.json` stores persistent preferences:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -563,18 +563,18 @@ Global `~/.pi/agent/settings.json` stores persistent preferences:
|
||||
|
||||
Built-in themes: `dark` (default), `light`. Auto-detected on first run.
|
||||
|
||||
Select theme via `/settings` or set in `~/.pi/agent/settings.json`.
|
||||
Select theme via `/settings` or set in `~/.omp/agent/settings.json`.
|
||||
|
||||
**Custom themes:** Create `~/.pi/agent/themes/*.json`. Custom themes support live reload.
|
||||
**Custom themes:** Create `~/.omp/agent/themes/*.json`. Custom themes support live reload.
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.pi/agent/themes
|
||||
cp $(npm root -g)/@oh-my-pi/pi-coding-agent/dist/theme/dark.json ~/.pi/agent/themes/my-theme.json
|
||||
mkdir -p ~/.omp/agent/themes
|
||||
cp $(npm root -g)/@oh-my-pi/omp-coding-agent/dist/theme/dark.json ~/.omp/agent/themes/my-theme.json
|
||||
```
|
||||
|
||||
Select with `/settings`, then edit the file. Changes apply on save.
|
||||
|
||||
> See [Theme Documentation](docs/theme.md) on how to create custom themes in detail. Pi can help you create a new one.
|
||||
> See [Theme Documentation](docs/theme.md) on how to create custom themes in detail. Omp can help you create a new one.
|
||||
|
||||
**VS Code terminal fix:** Set `terminal.integrated.minimumContrastRatio` to `1` for accurate colors.
|
||||
|
||||
@@ -584,8 +584,8 @@ Define reusable prompts as Markdown files:
|
||||
|
||||
**Locations:**
|
||||
|
||||
- Global: `~/.pi/agent/commands/*.md`
|
||||
- Project: `.pi/commands/*.md`
|
||||
- Global: `~/.omp/agent/commands/*.md`
|
||||
- Project: `.omp/commands/*.md`
|
||||
|
||||
**Format:**
|
||||
|
||||
@@ -618,11 +618,11 @@ Usage: `/component Button "onClick handler" "disabled support"`
|
||||
- `$1` = `Button`
|
||||
- `$@` = all arguments joined
|
||||
|
||||
**Namespacing:** Subdirectories create prefixes. `.pi/commands/frontend/component.md` → `/component (project:frontend)`
|
||||
**Namespacing:** Subdirectories create prefixes. `.omp/commands/frontend/component.md` → `/component (project:frontend)`
|
||||
|
||||
### Skills
|
||||
|
||||
Skills are self-contained capability packages that the agent loads on-demand. Pi implements the [Agent Skills standard](https://agentskills.io/specification), warning about violations but remaining lenient.
|
||||
Skills are self-contained capability packages that the agent loads on-demand. Omp implements the [Agent Skills standard](https://agentskills.io/specification), warning about violations but remaining lenient.
|
||||
|
||||
A skill provides specialized workflows, setup instructions, helper scripts, and reference documentation for specific tasks. Skills are loaded when the agent decides a task matches the description, or when you explicitly ask to use one.
|
||||
|
||||
@@ -637,8 +637,8 @@ A skill provides specialized workflows, setup instructions, helper scripts, and
|
||||
|
||||
**Skill locations:**
|
||||
|
||||
- Pi user: `~/.pi/agent/skills/**/SKILL.md` (recursive)
|
||||
- Pi project: `.pi/skills/**/SKILL.md` (recursive)
|
||||
- Omp user: `~/.omp/agent/skills/**/SKILL.md` (recursive)
|
||||
- Omp project: `.omp/skills/**/SKILL.md` (recursive)
|
||||
- Claude Code: `~/.claude/skills/*/SKILL.md` and `.claude/skills/*/SKILL.md`
|
||||
- Codex CLI: `~/.codex/skills/**/SKILL.md` (recursive)
|
||||
|
||||
@@ -669,13 +669,13 @@ cd /path/to/brave-search && npm install
|
||||
- `name`: Required. Must match parent directory name. Lowercase, hyphens, max 64 chars.
|
||||
- `description`: Required. Max 1024 chars. Determines when the skill is loaded.
|
||||
|
||||
**Disable skills:** `pi --no-skills` or set `skills.enabled: false` in settings.
|
||||
**Disable skills:** `omp --no-skills` or set `skills.enabled: false` in settings.
|
||||
|
||||
> See [docs/skills.md](docs/skills.md) for details, examples, and links to skill repositories. pi can help you create new skills.
|
||||
> See [docs/skills.md](docs/skills.md) for details, examples, and links to skill repositories. omp can help you create new skills.
|
||||
|
||||
### Hooks
|
||||
|
||||
Hooks are TypeScript modules that extend pi's behavior by subscribing to lifecycle events. Use them to:
|
||||
Hooks are TypeScript modules that extend omp's behavior by subscribing to lifecycle events. Use them to:
|
||||
|
||||
- **Block dangerous commands** (permission gates for `rm -rf`, `sudo`, etc.)
|
||||
- **Checkpoint code state** (git stash at each turn, restore on `/branch`)
|
||||
@@ -685,17 +685,17 @@ Hooks are TypeScript modules that extend pi's behavior by subscribing to lifecyc
|
||||
|
||||
**Hook locations:**
|
||||
|
||||
- Global: `~/.pi/agent/hooks/*.ts`
|
||||
- Project: `.pi/hooks/*.ts`
|
||||
- Global: `~/.omp/agent/hooks/*.ts`
|
||||
- Project: `.omp/hooks/*.ts`
|
||||
- CLI: `--hook <path>` (for debugging)
|
||||
|
||||
**Quick example** (permission gate):
|
||||
|
||||
```typescript
|
||||
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/hooks";
|
||||
import type { HookAPI } from "@oh-my-pi/omp-coding-agent/hooks";
|
||||
|
||||
export default function (pi: HookAPI) {
|
||||
pi.on("tool_call", async (event, ctx) => {
|
||||
export default function (omp: HookAPI) {
|
||||
omp.on("tool_call", async (event, ctx) => {
|
||||
if (event.toolName === "bash" && /sudo/.test(event.input.command as string)) {
|
||||
const ok = await ctx.ui.confirm("Allow sudo?", event.input.command as string);
|
||||
if (!ok) return { block: true, reason: "Blocked by user" };
|
||||
@@ -707,18 +707,18 @@ export default function (pi: HookAPI) {
|
||||
|
||||
**Sending messages from hooks:**
|
||||
|
||||
Use `pi.sendMessage(message, triggerTurn?)` to inject messages into the session. Messages are persisted as `CustomMessageEntry` and sent to the LLM. If the agent is streaming, the message is queued; otherwise a new agent loop starts if `triggerTurn` is true.
|
||||
Use `omp.sendMessage(message, triggerTurn?)` to inject messages into the session. Messages are persisted as `CustomMessageEntry` and sent to the LLM. If the agent is streaming, the message is queued; otherwise a new agent loop starts if `triggerTurn` is true.
|
||||
|
||||
```typescript
|
||||
import * as fs from "node:fs";
|
||||
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/hooks";
|
||||
import type { HookAPI } from "@oh-my-pi/omp-coding-agent/hooks";
|
||||
|
||||
export default function (pi: HookAPI) {
|
||||
pi.on("session_start", async () => {
|
||||
export default function (omp: HookAPI) {
|
||||
omp.on("session_start", async () => {
|
||||
fs.watch("/tmp/trigger.txt", () => {
|
||||
const content = fs.readFileSync("/tmp/trigger.txt", "utf-8").trim();
|
||||
if (content) {
|
||||
pi.sendMessage(
|
||||
omp.sendMessage(
|
||||
{
|
||||
customType: "file-trigger",
|
||||
content,
|
||||
@@ -732,7 +732,7 @@ export default function (pi: HookAPI) {
|
||||
}
|
||||
```
|
||||
|
||||
> See [Hooks Documentation](docs/hooks.md) for full API reference. pi can help you create new hooks
|
||||
> See [Hooks Documentation](docs/hooks.md) for full API reference. omp can help you create new hooks
|
||||
|
||||
> See [examples/hooks/](examples/hooks/) for working examples including permission gates, git checkpointing, and path protection.
|
||||
|
||||
@@ -742,8 +742,8 @@ Custom tools let you extend the built-in toolset (read, write, edit, bash, ...)
|
||||
|
||||
**Tool locations (auto-discovered):**
|
||||
|
||||
- Global: `~/.pi/agent/tools/*/index.ts`
|
||||
- Project: `.pi/tools/*/index.ts`
|
||||
- Global: `~/.omp/agent/tools/*/index.ts`
|
||||
- Project: `.omp/tools/*/index.ts`
|
||||
|
||||
**Explicit paths:**
|
||||
|
||||
@@ -754,9 +754,9 @@ Custom tools let you extend the built-in toolset (read, write, edit, bash, ...)
|
||||
|
||||
```typescript
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import type { CustomToolFactory } from "@oh-my-pi/pi-coding-agent";
|
||||
import type { CustomToolFactory } from "@oh-my-pi/omp-coding-agent";
|
||||
|
||||
const factory: CustomToolFactory = (pi) => ({
|
||||
const factory: CustomToolFactory = (omp) => ({
|
||||
name: "greet",
|
||||
label: "Greeting",
|
||||
description: "Generate a greeting",
|
||||
@@ -778,14 +778,14 @@ export default factory;
|
||||
|
||||
**Features:**
|
||||
|
||||
- Access to `pi.cwd`, `pi.exec()`, `pi.ui` (select/confirm/input dialogs)
|
||||
- Access to `omp.cwd`, `omp.exec()`, `omp.ui` (select/confirm/input dialogs)
|
||||
- Session lifecycle via `onSession` callback (for state reconstruction)
|
||||
- Custom rendering via `renderCall()` and `renderResult()` methods
|
||||
- Streaming results via `onUpdate` callback
|
||||
- Abort handling via `signal` parameter
|
||||
- Multiple tools from one factory (return an array)
|
||||
|
||||
> See [Custom Tools Documentation](docs/custom-tools.md) for the full API reference, TUI component guide, and examples. pi can help you create custom tools.
|
||||
> See [Custom Tools Documentation](docs/custom-tools.md) for the full API reference, TUI component guide, and examples. omp can help you create custom tools.
|
||||
|
||||
> See [examples/custom-tools/](examples/custom-tools/) for working examples including a todo list with session state management and a question tool with UI interaction.
|
||||
|
||||
@@ -794,7 +794,7 @@ export default factory;
|
||||
## CLI Reference
|
||||
|
||||
```bash
|
||||
pi [options] [@files...] [messages...]
|
||||
omp [options] [@files...] [messages...]
|
||||
```
|
||||
|
||||
### Options
|
||||
@@ -828,9 +828,9 @@ pi [options] [@files...] [messages...]
|
||||
Include files with `@` prefix:
|
||||
|
||||
```bash
|
||||
pi @prompt.md "Answer this"
|
||||
pi @screenshot.png "What's in this image?"
|
||||
pi @requirements.md @design.png "Implement this"
|
||||
omp @prompt.md "Answer this"
|
||||
omp @screenshot.png "What's in this image?"
|
||||
omp @requirements.md @design.png "Implement this"
|
||||
```
|
||||
|
||||
Text files wrapped in `<file name="path">content</file>`. Images attached as base64.
|
||||
@@ -839,40 +839,40 @@ Text files wrapped in `<file name="path">content</file>`. Images attached as bas
|
||||
|
||||
```bash
|
||||
# Interactive mode
|
||||
pi
|
||||
omp
|
||||
|
||||
# Interactive with initial prompt
|
||||
pi "List all .ts files in src/"
|
||||
omp "List all .ts files in src/"
|
||||
|
||||
# Non-interactive
|
||||
pi -p "List all .ts files in src/"
|
||||
omp -p "List all .ts files in src/"
|
||||
|
||||
# With files
|
||||
pi -p @code.ts "Review this code"
|
||||
omp -p @code.ts "Review this code"
|
||||
|
||||
# JSON event stream
|
||||
pi --mode json "List files"
|
||||
omp --mode json "List files"
|
||||
|
||||
# RPC mode (headless)
|
||||
pi --mode rpc --no-session
|
||||
omp --mode rpc --no-session
|
||||
|
||||
# Continue session
|
||||
pi -c "What did we discuss?"
|
||||
omp -c "What did we discuss?"
|
||||
|
||||
# Specific model
|
||||
pi --provider openai --model gpt-4o "Help me refactor"
|
||||
omp --provider openai --model gpt-4o "Help me refactor"
|
||||
|
||||
# Model cycling with thinking levels
|
||||
pi --models sonnet:high,haiku:low
|
||||
omp --models sonnet:high,haiku:low
|
||||
|
||||
# Limit to specific provider with glob pattern
|
||||
pi --models "github-copilot/*"
|
||||
omp --models "github-copilot/*"
|
||||
|
||||
# Read-only mode
|
||||
pi --tools read,grep,find,ls -p "Review the architecture"
|
||||
omp --tools read,grep,find,ls -p "Review the architecture"
|
||||
|
||||
# Export session
|
||||
pi --export session.jsonl output.html
|
||||
omp --export session.jsonl output.html
|
||||
```
|
||||
|
||||
---
|
||||
@@ -908,10 +908,10 @@ For adding new tools, see [Custom Tools](#custom-tools) in the Configuration sec
|
||||
|
||||
### SDK
|
||||
|
||||
For embedding pi in Node.js/TypeScript applications, use the SDK:
|
||||
For embedding omp in Node.js/TypeScript applications, use the SDK:
|
||||
|
||||
```typescript
|
||||
import { createAgentSession, discoverAuthStorage, discoverModels, SessionManager } from "@oh-my-pi/pi-coding-agent";
|
||||
import { createAgentSession, discoverAuthStorage, discoverModels, SessionManager } from "@oh-my-pi/omp-coding-agent";
|
||||
|
||||
const authStorage = discoverAuthStorage();
|
||||
const modelRegistry = discoverModels(authStorage);
|
||||
@@ -942,16 +942,16 @@ The SDK provides full control over:
|
||||
- Settings (`SettingsManager`)
|
||||
- API key resolution and OAuth
|
||||
|
||||
**Philosophy:** "Omit to discover, provide to override." Omit an option and pi discovers from standard locations. Provide an option and your value is used.
|
||||
**Philosophy:** "Omit to discover, provide to override." Omit an option and omp discovers from standard locations. Provide an option and your value is used.
|
||||
|
||||
> See [SDK Documentation](docs/sdk.md) for the full API reference. See [examples/sdk/](examples/sdk/) for working examples from minimal to full control.
|
||||
|
||||
### RPC Mode
|
||||
|
||||
For embedding pi from other languages or with process isolation:
|
||||
For embedding omp from other languages or with process isolation:
|
||||
|
||||
```bash
|
||||
pi --mode rpc --no-session
|
||||
omp --mode rpc --no-session
|
||||
```
|
||||
|
||||
Send JSON commands on stdin:
|
||||
@@ -966,8 +966,8 @@ Send JSON commands on stdin:
|
||||
### HTML Export
|
||||
|
||||
```bash
|
||||
pi --export session.jsonl # Auto-generated filename
|
||||
pi --export session.jsonl output.html # Custom filename
|
||||
omp --export session.jsonl # Auto-generated filename
|
||||
omp --export session.jsonl output.html # Custom filename
|
||||
```
|
||||
|
||||
Works with both session files and streaming event logs from `--mode json`.
|
||||
@@ -976,11 +976,11 @@ Works with both session files and streaming event logs from `--mode json`.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Pi is opinionated about what it won't do. These are intentional design decisions to minimize context bloat and avoid anti-patterns.
|
||||
Omp is opinionated about what it won't do. These are intentional design decisions to minimize context bloat and avoid anti-patterns.
|
||||
|
||||
**No MCP.** Build CLI tools with READMEs (see [Skills](#skills)). The agent reads them on demand. [Would you like to know more?](https://mariozechner.at/posts/2025-11-02-what-if-you-dont-need-mcp/)
|
||||
|
||||
**No sub-agents.** Spawn pi instances via tmux, or [build your own sub-agent tool](examples/custom-tools/subagent/) with [custom tools](#custom-tools). Full observability and steerability.
|
||||
**No sub-agents.** Spawn omp instances via tmux, or [build your own sub-agent tool](examples/custom-tools/subagent/) with [custom tools](#custom-tools). Full observability and steerability.
|
||||
|
||||
**No permission popups.** Security theater. Run in a container or build your own with [Hooks](#hooks).
|
||||
|
||||
@@ -1002,9 +1002,9 @@ Configure via `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"piConfig": {
|
||||
"name": "pi",
|
||||
"configDir": ".pi"
|
||||
"ompConfig": {
|
||||
"name": "omp",
|
||||
"configDir": ".omp"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1025,7 +1025,7 @@ Never use `__dirname` directly for package assets.
|
||||
|
||||
### Debug Command
|
||||
|
||||
`/debug` (hidden) writes rendered lines with ANSI codes to `~/.pi/agent/pi-debug.log` for TUI debugging, as well as the last set of messages that were sent to the LLM.
|
||||
`/debug` (hidden) writes rendered lines with ANSI codes to `~/.omp/agent/omp-debug.log` for TUI debugging, as well as the last set of messages that were sent to the LLM.
|
||||
|
||||
For architecture and contribution guidelines, see [DEVELOPMENT.md](./DEVELOPMENT.md).
|
||||
|
||||
@@ -1037,5 +1037,5 @@ MIT
|
||||
|
||||
## See Also
|
||||
|
||||
- [@oh-my-pi/pi-ai](https://www.npmjs.com/package/@oh-my-pi/pi-ai): Core LLM toolkit
|
||||
- [@oh-my-pi/pi-agent](https://www.npmjs.com/package/@oh-my-pi/pi-agent): Agent framework
|
||||
- [@oh-my-pi/omp-ai](https://www.npmjs.com/package/@oh-my-pi/omp-ai): Core LLM toolkit
|
||||
- [@oh-my-pi/omp-agent](https://www.npmjs.com/package/@oh-my-pi/omp-agent): Agent framework
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Compaction & Branch Summarization
|
||||
|
||||
LLMs have limited context windows. When conversations grow too long, pi uses compaction to summarize older content while preserving recent work. This page covers both auto-compaction and branch summarization.
|
||||
LLMs have limited context windows. When conversations grow too long, omp uses compaction to summarize older content while preserving recent work. This page covers both auto-compaction and branch summarization.
|
||||
|
||||
**Source files:**
|
||||
|
||||
@@ -12,7 +12,7 @@ LLMs have limited context windows. When conversations grow too long, pi uses com
|
||||
|
||||
## Overview
|
||||
|
||||
Pi has two summarization mechanisms:
|
||||
OMP has two summarization mechanisms:
|
||||
|
||||
| Mechanism | Trigger | Purpose |
|
||||
| -------------------- | ---------------------------------------- | ----------------------------------------- |
|
||||
@@ -31,13 +31,13 @@ Auto-compaction triggers when:
|
||||
contextTokens > contextWindow - reserveTokens
|
||||
```
|
||||
|
||||
By default, `reserveTokens` is 16384 tokens (configurable in `~/.pi/agent/settings.json` or `<project-dir>/.pi/settings.json`). This leaves room for the LLM's response.
|
||||
By default, `reserveTokens` is 16384 tokens (configurable in `~/.omp/agent/settings.json` or `<project-dir>/.omp/settings.json`). This leaves room for the LLM's response.
|
||||
|
||||
You can also trigger manually with `/compact [instructions]`, where optional instructions focus the summary.
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Find cut point**: Walk backwards from newest message, accumulating token estimates until `keepRecentTokens` (default 20k, configurable in `~/.pi/agent/settings.json` or `<project-dir>/.pi/settings.json`) is reached
|
||||
2. **Find cut point**: Walk backwards from newest message, accumulating token estimates until `keepRecentTokens` (default 20k, configurable in `~/.omp/agent/settings.json` or `<project-dir>/.omp/settings.json`) is reached
|
||||
2. **Extract messages**: Collect messages from previous compaction (or start) up to cut point
|
||||
3. **Generate summary**: Call LLM to summarize with structured format
|
||||
4. **Append entry**: Save `CompactionEntry` with summary and `firstKeptEntryId`
|
||||
@@ -99,7 +99,7 @@ Split turn (one huge turn exceeds budget):
|
||||
turnPrefixMessages = [usr, ass, tool, ass, tool, tool]
|
||||
```
|
||||
|
||||
For split turns, pi generates two summaries and merges them:
|
||||
For split turns, omp generates two summaries and merges them:
|
||||
|
||||
1. **History summary**: Previous context (if any)
|
||||
2. **Turn prefix summary**: The early part of the split turn
|
||||
@@ -147,7 +147,7 @@ See [`prepareCompaction()`](../src/core/compaction/compaction.ts) and [`compact(
|
||||
|
||||
### When It Triggers
|
||||
|
||||
When you use `/tree` to navigate to a different branch, pi offers to summarize the work you're leaving. This injects context from the left branch into the new branch.
|
||||
When you use `/tree` to navigate to a different branch, omp offers to summarize the work you're leaving. This injects context from the left branch into the new branch.
|
||||
|
||||
### How It Works
|
||||
|
||||
@@ -176,7 +176,7 @@ After navigation with summary:
|
||||
|
||||
### Cumulative File Tracking
|
||||
|
||||
Both compaction and branch summarization track files cumulatively. When generating a summary, pi extracts file operations from:
|
||||
Both compaction and branch summarization track files cumulatively. When generating a summary, omp extracts file operations from:
|
||||
|
||||
- Tool calls in the messages being summarized
|
||||
- Previous compaction or branch summary `details` (if any)
|
||||
@@ -382,7 +382,7 @@ See `SessionBeforeTreeEvent` and `TreePreparation` in the types file.
|
||||
|
||||
## Settings
|
||||
|
||||
Configure compaction in `~/.pi/agent/settings.json` or `<project-dir>/.pi/settings.json`:
|
||||
Configure compaction in `~/.omp/agent/settings.json` or `<project-dir>/.omp/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
# Config Module Usage Map
|
||||
|
||||
This document shows how each file uses the config module and what subpaths they access.
|
||||
|
||||
## Overview Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ config.ts exports │
|
||||
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ Constants: APP_NAME, CONFIG_DIR_NAME, VERSION, ENV_AGENT_DIR │
|
||||
│ Single paths: getAgentDir, getAuthPath, getModelsPath, getCommandsDir, ... │
|
||||
│ Multi-config: getConfigDirs, getConfigDirPaths, findConfigFile, │
|
||||
│ readConfigFile, findNearestProjectConfigDir, ... │
|
||||
└─────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Usage by Category
|
||||
|
||||
### 1. Display/Branding Only (no file I/O)
|
||||
|
||||
| File | Imports | Purpose |
|
||||
|------|---------|---------|
|
||||
| `cli/args.ts` | `APP_NAME`, `CONFIG_DIR_NAME`, `ENV_AGENT_DIR` | Help text, env var names |
|
||||
| `cli/plugin-cli.ts` | `APP_NAME` | Command output |
|
||||
| `cli/update-cli.ts` | `APP_NAME`, `VERSION` | Update messages |
|
||||
| `core/export-html/index.ts` | `APP_NAME` | HTML export title |
|
||||
| `modes/interactive/components/welcome.ts` | `APP_NAME` | Welcome banner |
|
||||
| `utils/tools-manager.ts` | `APP_NAME` | Tool download messages |
|
||||
|
||||
### 2. Single Fixed Paths (user-level only)
|
||||
|
||||
| File | Imports | Path | Purpose |
|
||||
|------|---------|------|---------|
|
||||
| `core/logger.ts` | `CONFIG_DIR_NAME` | `~/.omp/logs/` | Log file directory |
|
||||
| `core/agent-session.ts` | `getAuthPath` | `~/.omp/agent/auth.json` | Error messages |
|
||||
| `core/session-manager.ts` | `getAgentDir` | `~/.omp/agent/sessions/` | Session storage |
|
||||
| `modes/interactive/theme/theme.ts` | `getCustomThemesDir` | `~/.omp/agent/themes/` | Custom themes |
|
||||
| `modes/interactive/interactive-mode.ts` | `getAuthPath`, `getDebugLogPath` | auth.json, debug log | Status messages |
|
||||
| `utils/changelog.ts` | `getChangelogPath` | Package CHANGELOG.md | Re-exports |
|
||||
| `core/system-prompt.ts` | `getAgentDir`, `getDocsPath`, `getExamplesPath`, `getReadmePath` | Package assets + AGENTS.md | System prompt building |
|
||||
| `migrations.ts` | `getAgentDir` | `~/.omp/agent/` | Auth/session migration |
|
||||
| `core/plugins/installer.ts` | `getAgentDir` | `~/.omp/agent/` | Plugin installation |
|
||||
| `core/plugins/paths.ts` | `CONFIG_DIR_NAME` | `~/.omp/plugins/` | Plugin directories |
|
||||
|
||||
### 3. Multi-Config Discovery (with fallbacks)
|
||||
|
||||
These use the new helpers to check `.omp`, `.pi`, `.claude` directories:
|
||||
|
||||
| File | Helper Used | Subpath(s) | Levels |
|
||||
|------|-------------|------------|--------|
|
||||
| `main.ts` | `findConfigFile` | `SYSTEM.md` | project |
|
||||
| `core/sdk.ts` | `getConfigDirPaths` | `auth.json`, `models.json` | user |
|
||||
| `core/settings-manager.ts` | `readConfigFile` | `settings.json` | user+project |
|
||||
| `core/skills.ts` | `getConfigDirPaths` | `skills/` | user+project |
|
||||
| `core/slash-commands.ts` | `getConfigDirPaths` | `commands/` | project |
|
||||
| `core/hooks/loader.ts` | `getConfigDirPaths` | `hooks/` | project |
|
||||
| `core/custom-tools/loader.ts` | `getConfigDirPaths` | `tools/` | project |
|
||||
| `core/custom-commands/loader.ts` | `getConfigDirPaths` | `commands/` | project |
|
||||
| `core/plugins/paths.ts` | `getConfigDirPaths` | `plugin-overrides.json` | project |
|
||||
| `core/mcp/config.ts` | `getConfigDirPaths` | `mcp.json` | user+project |
|
||||
| `core/tools/lsp/config.ts` | `getConfigDirPaths` | `lsp.json`, `.lsp.json` | user+project |
|
||||
| `core/tools/task/commands.ts` | `getConfigDirPaths`, `findAllNearestProjectConfigDirs` | `commands/` | user+project |
|
||||
| `core/tools/task/discovery.ts` | `getConfigDirs`, `findAllNearestProjectConfigDirs` | `agents/` | user+project |
|
||||
| `core/tools/task/model-resolver.ts` | `readConfigFile` | `settings.json` | user |
|
||||
| `core/tools/web-search/auth.ts` | `getConfigDirPaths` | `` (root for models.json, auth.json) | user |
|
||||
|
||||
## Subpath Summary
|
||||
|
||||
```
|
||||
User-level (~/.omp/agent/, ~/.pi/agent/, ~/.claude/):
|
||||
├── auth.json ← sdk.ts, web-search/auth.ts
|
||||
├── models.json ← sdk.ts, web-search/auth.ts
|
||||
├── settings.json ← settings-manager.ts, task/model-resolver.ts
|
||||
├── commands/ ← slash-commands.ts, custom-commands/loader.ts, task/commands.ts
|
||||
├── hooks/ ← hooks/loader.ts
|
||||
├── tools/ ← custom-tools/loader.ts
|
||||
├── skills/ ← skills.ts
|
||||
├── themes/ ← theme.ts (user-level only, no fallback)
|
||||
├── sessions/ ← session-manager.ts (user-level only, no fallback)
|
||||
├── agents/ ← task/discovery.ts
|
||||
└── AGENTS.md ← system-prompt.ts
|
||||
|
||||
User-level root (~/.omp/, ~/.pi/, ~/.claude/) - not under agent/:
|
||||
├── mcp.json ← mcp/config.ts
|
||||
├── plugins/ ← plugins/paths.ts (primary only)
|
||||
└── logs/ ← logger.ts (primary only)
|
||||
|
||||
Project-level (.omp/, .pi/, .claude/):
|
||||
├── SYSTEM.md ← main.ts
|
||||
├── settings.json ← settings-manager.ts
|
||||
├── commands/ ← slash-commands.ts, custom-commands/loader.ts, task/commands.ts
|
||||
├── hooks/ ← hooks/loader.ts
|
||||
├── tools/ ← custom-tools/loader.ts
|
||||
├── skills/ ← skills.ts
|
||||
├── agents/ ← task/discovery.ts
|
||||
├── plugin-overrides.json ← plugins/paths.ts
|
||||
├── lsp.json ← lsp/config.ts
|
||||
└── .mcp.json ← mcp/config.ts
|
||||
|
||||
Special paths (not under agent/):
|
||||
├── ~/.omp/plugins/ ← plugins/paths.ts
|
||||
└── ~/.omp/logs/ ← logger.ts
|
||||
```
|
||||
|
||||
## Files Using Manual Paths (Intentionally)
|
||||
|
||||
These files construct paths manually because they only use the primary config dir:
|
||||
|
||||
| File | Current Approach | Reason |
|
||||
|------|------------------|--------|
|
||||
| `core/logger.ts` | `CONFIG_DIR_NAME` for logs dir | Logs only written to primary (~/.omp/logs/) |
|
||||
| `core/plugins/paths.ts` | `CONFIG_DIR_NAME` for plugins dir | Plugins only installed in primary (~/.omp/plugins/) |
|
||||
@@ -1,4 +1,4 @@
|
||||
> pi can create custom tools. Ask it to build one for your use case.
|
||||
> omp can create custom tools. Ask it to build one for your use case.
|
||||
|
||||
# Custom Tools
|
||||
|
||||
@@ -32,7 +32,7 @@ See [examples/custom-tools/](../examples/custom-tools/) for working examples.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Create a file `~/.pi/agent/tools/hello/index.ts`:
|
||||
Create a file `~/.omp/agent/tools/hello/index.ts`:
|
||||
|
||||
```typescript
|
||||
import type { CustomToolFactory } from "@oh-my-pi/pi-coding-agent";
|
||||
@@ -57,7 +57,7 @@ const factory: CustomToolFactory = (pi) => ({
|
||||
export default factory;
|
||||
```
|
||||
|
||||
The tool is automatically discovered and available in your next pi session.
|
||||
The tool is automatically discovered and available in your next omp session.
|
||||
|
||||
## Tool Locations
|
||||
|
||||
@@ -65,15 +65,15 @@ Tools must be in a subdirectory with an `index.ts` entry point:
|
||||
|
||||
| Location | Scope | Auto-discovered |
|
||||
| ----------------------------------- | --------------------- | --------------- |
|
||||
| `~/.pi/agent/tools/*/index.ts` | Global (all projects) | Yes |
|
||||
| `.pi/tools/*/index.ts` | Project-local | Yes |
|
||||
| `~/.omp/agent/tools/*/index.ts` | Global (all projects) | Yes |
|
||||
| `.omp/tools/*/index.ts` | Project-local | Yes |
|
||||
| `settings.json` `customTools` array | Configured paths | Yes |
|
||||
| `--tool <path>` CLI flag | One-off/debugging | No |
|
||||
|
||||
**Example structure:**
|
||||
|
||||
```
|
||||
~/.pi/agent/tools/
|
||||
~/.omp/agent/tools/
|
||||
├── hello/
|
||||
│ └── index.ts # Entry point (auto-discovered)
|
||||
└── complex-tool/
|
||||
@@ -222,7 +222,7 @@ async execute(toolCallId, params, onUpdate, ctx, signal) {
|
||||
async execute(toolCallId, params, onUpdate, ctx, signal) {
|
||||
const { path } = params as { path: string };
|
||||
|
||||
// Throw on error - pi will catch it and report to the LLM
|
||||
// Throw on error - omp will catch it and report to the LLM
|
||||
if (!fs.existsSync(path)) {
|
||||
throw new Error(`File not found: ${path}`);
|
||||
}
|
||||
@@ -537,5 +537,5 @@ See [`examples/custom-tools/todo/index.ts`](../examples/custom-tools/todo/index.
|
||||
Test with:
|
||||
|
||||
```bash
|
||||
pi --tool packages/coding-agent/examples/custom-tools/todo/index.ts
|
||||
omp --tool packages/coding-agent/examples/custom-tools/todo/index.ts
|
||||
```
|
||||
|
||||
@@ -27,7 +27,7 @@ Extensions can be loaded from:
|
||||
### Directories
|
||||
```
|
||||
./my-hooks/
|
||||
~/.pi/agent/hooks/
|
||||
~/.omp/agent/hooks/
|
||||
```
|
||||
|
||||
### npm Packages
|
||||
@@ -51,7 +51,7 @@ git:https://github.com/user/repo#branch # branch
|
||||
|
||||
### Permanent (settings.json)
|
||||
```
|
||||
~/.pi/agent/
|
||||
~/.omp/agent/
|
||||
hooks/
|
||||
my-local-hook.ts # root-level file
|
||||
complex-hook/ # directory with entry point
|
||||
@@ -86,7 +86,7 @@ git:https://github.com/user/repo#branch # branch
|
||||
|
||||
### Ephemeral (CLI flags)
|
||||
```
|
||||
/tmp/pi-extensions/
|
||||
/tmp/omp-extensions/
|
||||
hooks/
|
||||
npm/
|
||||
my-hook@1.0.0/
|
||||
@@ -148,8 +148,8 @@ scan(baseDir, config):
|
||||
```
|
||||
|
||||
**Default directories scanned (always, regardless of settings.json):**
|
||||
- `~/.pi/agent/<type>/`
|
||||
- `<cwd>/.pi/<type>/`
|
||||
- `~/.omp/agent/<type>/`
|
||||
- `<cwd>/.omp/<type>/`
|
||||
|
||||
## Extension Packs
|
||||
|
||||
@@ -248,58 +248,58 @@ Patterns are glob patterns matched against extension paths.
|
||||
|
||||
### Adding Sources
|
||||
```bash
|
||||
pi --hook <path|npm:|git:> # add hook source (repeatable)
|
||||
pi --tool <path|npm:|git:> # add custom tool source (repeatable)
|
||||
pi --skill <path|npm:|git:> # add skill source (repeatable)
|
||||
pi --theme <path|npm:|git:> # add theme source (repeatable)
|
||||
omp --hook <path|npm:|git:> # add hook source (repeatable)
|
||||
omp --tool <path|npm:|git:> # add custom tool source (repeatable)
|
||||
omp --skill <path|npm:|git:> # add skill source (repeatable)
|
||||
omp --theme <path|npm:|git:> # add theme source (repeatable)
|
||||
```
|
||||
|
||||
**Installation locations for npm/git sources:**
|
||||
|
||||
| Source | Install location |
|
||||
|--------|------------------|
|
||||
| CLI flags | `/tmp/pi-extensions/<type>/npm/` or `git/` |
|
||||
| Global settings (`~/.pi/agent/settings.json`) | `~/.pi/agent/<type>/npm/` or `git/` |
|
||||
| Project settings (`<cwd>/.pi/settings.json`) | `<cwd>/.pi/<type>/npm/` or `git/` |
|
||||
| CLI flags | `/tmp/omp-extensions/<type>/npm/` or `git/` |
|
||||
| Global settings (`~/.omp/agent/settings.json`) | `~/.omp/agent/<type>/npm/` or `git/` |
|
||||
| Project settings (`<cwd>/.omp/settings.json`) | `<cwd>/.omp/<type>/npm/` or `git/` |
|
||||
|
||||
File/directory paths are used directly (no installation).
|
||||
|
||||
- **CLI = ephemeral**: cached in temp until OS clears `/tmp/`
|
||||
- **Global settings = permanent**: installed to user's agent directory
|
||||
- **Project settings = project-local**: installed to project's `.pi/` directory
|
||||
- **Project settings = project-local**: installed to project's `.omp/` directory
|
||||
|
||||
Examples:
|
||||
- `--hook npm:my-hook@1.0.0` → `/tmp/pi-extensions/hooks/npm/my-hook@1.0.0/`
|
||||
- Global settings.json `npm:my-hook@1.0.0` → `~/.pi/agent/hooks/npm/my-hook@1.0.0/`
|
||||
- Project settings.json `npm:my-hook@1.0.0` → `<cwd>/.pi/hooks/npm/my-hook@1.0.0/`
|
||||
- `--hook npm:my-hook@1.0.0` → `/tmp/omp-extensions/hooks/npm/my-hook@1.0.0/`
|
||||
- Global settings.json `npm:my-hook@1.0.0` → `~/.omp/agent/hooks/npm/my-hook@1.0.0/`
|
||||
- Project settings.json `npm:my-hook@1.0.0` → `<cwd>/.omp/hooks/npm/my-hook@1.0.0/`
|
||||
|
||||
This encourages: try via CLI, if you like it, add to settings.json for permanent install.
|
||||
|
||||
### Filtering
|
||||
```bash
|
||||
pi --hooks "pattern1,pattern2,!excluded" # filter hooks
|
||||
pi --custom-tools "pattern1,!excluded" # filter custom tools
|
||||
pi --skills "pattern1,pattern2" # filter skills
|
||||
pi --themes "pattern1" # filter themes
|
||||
omp --hooks "pattern1,pattern2,!excluded" # filter hooks
|
||||
omp --custom-tools "pattern1,!excluded" # filter custom tools
|
||||
omp --skills "pattern1,pattern2" # filter skills
|
||||
omp --themes "pattern1" # filter themes
|
||||
```
|
||||
|
||||
### Disabling
|
||||
```bash
|
||||
pi --no-hooks # disable all hooks
|
||||
pi --no-custom-tools # disable all custom tools
|
||||
pi --no-skills # disable all skills (already exists)
|
||||
omp --no-hooks # disable all hooks
|
||||
omp --no-custom-tools # disable all custom tools
|
||||
omp --no-skills # disable all skills (already exists)
|
||||
```
|
||||
|
||||
### Built-in Tools
|
||||
```bash
|
||||
pi --tools read,bash,edit,write # select which built-in tools to enable (unchanged)
|
||||
omp --tools read,bash,edit,write # select which built-in tools to enable (unchanged)
|
||||
```
|
||||
|
||||
## Settings Hierarchy
|
||||
|
||||
Extensions are configured in settings.json at two levels:
|
||||
- **Global**: `~/.pi/agent/settings.json`
|
||||
- **Project**: `<cwd>/.pi/settings.json`
|
||||
- **Global**: `~/.omp/agent/settings.json`
|
||||
- **Project**: `<cwd>/.omp/settings.json`
|
||||
|
||||
**Merge behavior:**
|
||||
- `paths`: **additive** - project paths are added to global paths
|
||||
@@ -307,7 +307,7 @@ Extensions are configured in settings.json at two levels:
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
// Global: ~/.pi/agent/settings.json
|
||||
// Global: ~/.omp/agent/settings.json
|
||||
{
|
||||
"hooks": {
|
||||
"paths": ["npm:audit-hooks@1.0.0"],
|
||||
@@ -315,7 +315,7 @@ Extensions are configured in settings.json at two levels:
|
||||
}
|
||||
}
|
||||
|
||||
// Project: .pi/settings.json
|
||||
// Project: .omp/settings.json
|
||||
{
|
||||
"hooks": {
|
||||
"paths": ["./project-hooks/"],
|
||||
@@ -368,9 +368,9 @@ Extensions are configured in settings.json at two levels:
|
||||
## Installation Flow
|
||||
|
||||
Target directory depends on source:
|
||||
- **CLI flags**: `/tmp/pi-extensions/<type>/npm/` or `git/`
|
||||
- **Global settings.json**: `~/.pi/agent/<type>/npm/` or `git/`
|
||||
- **Project settings.json**: `<cwd>/.pi/<type>/npm/` or `git/`
|
||||
- **CLI flags**: `/tmp/omp-extensions/<type>/npm/` or `git/`
|
||||
- **Global settings.json**: `~/.omp/agent/<type>/npm/` or `git/`
|
||||
- **Project settings.json**: `<cwd>/.omp/<type>/npm/` or `git/`
|
||||
|
||||
### Atomic Installation
|
||||
|
||||
@@ -382,7 +382,7 @@ To prevent corrupted state from interrupted installs (Ctrl+C):
|
||||
|
||||
### npm Packages
|
||||
1. Parse specifier: `npm:@scope/pkg@1.2.3` → name: `@scope/pkg`, version: `1.2.3`
|
||||
2. Determine target dir based on source (CLI → temp, global → agent dir, project → cwd/.pi/)
|
||||
2. Determine target dir based on source (CLI → temp, global → agent dir, project → cwd/.omp/)
|
||||
3. If `<target>/` exists and has matching version in package.json → skip install
|
||||
4. Otherwise:
|
||||
- Remove stale `<target>.installing/` if exists
|
||||
@@ -393,7 +393,7 @@ To prevent corrupted state from interrupted installs (Ctrl+C):
|
||||
|
||||
### Git Repositories
|
||||
1. Parse specifier: `git:https://github.com/user/repo@v1.0.0`
|
||||
2. Determine target dir based on source (CLI → temp, global → agent dir, project → cwd/.pi/)
|
||||
2. Determine target dir based on source (CLI → temp, global → agent dir, project → cwd/.omp/)
|
||||
3. If `<target>/` exists → skip clone
|
||||
4. Otherwise:
|
||||
- Remove stale `<target>.installing/` if exists
|
||||
@@ -409,18 +409,18 @@ To prevent corrupted state from interrupted installs (Ctrl+C):
|
||||
Adds extension to settings.json and installs to disk.
|
||||
|
||||
```bash
|
||||
pi install <type> <source> # global (default)
|
||||
pi install <type> -p <source> # project-local
|
||||
pi install <type> --project <source> # project-local
|
||||
omp install <type> <source> # global (default)
|
||||
omp install <type> -p <source> # project-local
|
||||
omp install <type> --project <source> # project-local
|
||||
|
||||
# Examples:
|
||||
pi install hook npm:@scope/my-hook@1.0.0
|
||||
# → adds to ~/.pi/agent/settings.json
|
||||
# → installs to ~/.pi/agent/hooks/npm/@scope/my-hook@1.0.0/
|
||||
omp install hook npm:@scope/my-hook@1.0.0
|
||||
# → adds to ~/.omp/agent/settings.json
|
||||
# → installs to ~/.omp/agent/hooks/npm/@scope/my-hook@1.0.0/
|
||||
|
||||
pi install tool -p git:https://github.com/user/tool@v1.0.0
|
||||
# → adds to <cwd>/.pi/settings.json
|
||||
# → installs to <cwd>/.pi/tools/git/github.com/user/tool@v1.0.0/
|
||||
omp install tool -p git:https://github.com/user/tool@v1.0.0
|
||||
# → adds to <cwd>/.omp/settings.json
|
||||
# → installs to <cwd>/.omp/tools/git/github.com/user/tool@v1.0.0/
|
||||
```
|
||||
|
||||
### Remove
|
||||
@@ -428,12 +428,12 @@ pi install tool -p git:https://github.com/user/tool@v1.0.0
|
||||
Removes extension from settings.json and deletes from disk.
|
||||
|
||||
```bash
|
||||
pi remove <type> <name> # from global
|
||||
pi remove <type> -p <name> # from project
|
||||
omp remove <type> <name> # from global
|
||||
omp remove <type> -p <name> # from project
|
||||
|
||||
# Examples:
|
||||
pi remove hook my-hook # from ~/.pi/agent/settings.json + delete
|
||||
pi remove skill -p brave-search # from <cwd>/.pi/settings.json + delete
|
||||
omp remove hook my-hook # from ~/.omp/agent/settings.json + delete
|
||||
omp remove skill -p brave-search # from <cwd>/.omp/settings.json + delete
|
||||
```
|
||||
|
||||
### Update
|
||||
@@ -441,15 +441,15 @@ pi remove skill -p brave-search # from <cwd>/.pi/settings.json + delete
|
||||
Updates npm/git extensions to latest versions.
|
||||
|
||||
```bash
|
||||
pi update # all (project + global)
|
||||
pi update -p # project only
|
||||
pi update <type>... # specific types
|
||||
pi update -p <type>... # project, specific types
|
||||
omp update # all (project + global)
|
||||
omp update -p # project only
|
||||
omp update <type>... # specific types
|
||||
omp update -p <type>... # project, specific types
|
||||
|
||||
# Examples:
|
||||
pi update # update everything
|
||||
pi update hook tool # update hooks and tools
|
||||
pi update -p skill # update project skills only
|
||||
omp update # update everything
|
||||
omp update hook tool # update hooks and tools
|
||||
omp update -p skill # update project skills only
|
||||
```
|
||||
|
||||
**Update behavior:**
|
||||
@@ -461,13 +461,13 @@ pi update -p skill # update project skills only
|
||||
## Loading Flow (Full)
|
||||
|
||||
1. **Collect sources:**
|
||||
- Default directories: `~/.pi/agent/<type>/`, `./.pi/<type>/`
|
||||
- Default directories: `~/.omp/agent/<type>/`, `./.omp/<type>/`
|
||||
- settings.json `<type>.paths`
|
||||
- CLI `--<type>` arguments
|
||||
|
||||
2. **Install remote sources:**
|
||||
3. **Install remote sources:**
|
||||
- Process `npm:` and `git:` specifiers
|
||||
- Install to `~/.pi/agent/<type>/npm/` or `git/`
|
||||
- Install to `~/.omp/agent/<type>/npm/` or `git/`
|
||||
|
||||
3. **Scan all sources:**
|
||||
- Recursively discover extensions
|
||||
@@ -621,7 +621,7 @@ export interface LoadExtensionsOptions {
|
||||
cwd: string;
|
||||
agentDir: string;
|
||||
globalPaths: string[]; // from global settings.json → install to agentDir
|
||||
projectPaths: string[]; // from project settings.json → install to cwd/.pi/
|
||||
projectPaths: string[]; // from project settings.json → install to cwd/.omp/
|
||||
cliPaths: string[]; // from CLI flags → install to /tmp/
|
||||
filter: string[]; // combined filter patterns
|
||||
}
|
||||
@@ -656,7 +656,7 @@ export function getSkillsDir(): string {
|
||||
|
||||
// getToolsDir() already exists
|
||||
// getThemesDir() = bundled themes (in package)
|
||||
// getCustomThemesDir() = ~/.pi/agent/themes/ (user themes) - already exists
|
||||
// getCustomThemesDir() = ~/.omp/agent/themes/ (user themes) - already exists
|
||||
```
|
||||
|
||||
### `src/core/settings-manager.ts`
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
> pi can create hooks. Ask it to build one for your use case.
|
||||
> omp can create hooks. Ask it to build one for your use case.
|
||||
|
||||
# Hooks
|
||||
|
||||
Hooks are TypeScript modules that extend pi's behavior by subscribing to lifecycle events. They can intercept tool calls, prompt the user, modify results, inject messages, and more.
|
||||
Hooks are TypeScript modules that extend omp's behavior by subscribing to lifecycle events. They can intercept tool calls, prompt the user, modify results, inject messages, and more.
|
||||
|
||||
**Key capabilities:**
|
||||
|
||||
@@ -24,7 +24,7 @@ See [examples/hooks/](../examples/hooks/) for working implementations, including
|
||||
|
||||
## Quick Start
|
||||
|
||||
Create `~/.pi/agent/hooks/my-hook.ts`:
|
||||
Create `~/.omp/agent/hooks/my-hook.ts`:
|
||||
|
||||
```typescript
|
||||
import type { HookAPI } from "@oh-my-pi/pi-coding-agent";
|
||||
@@ -46,17 +46,17 @@ export default function (pi: HookAPI) {
|
||||
Test with `--hook` flag:
|
||||
|
||||
```bash
|
||||
pi --hook ./my-hook.ts
|
||||
omp --hook ./my-hook.ts
|
||||
```
|
||||
|
||||
## Hook Locations
|
||||
|
||||
Hooks are auto-discovered from:
|
||||
|
||||
| Location | Scope |
|
||||
| ------------------------ | --------------------- |
|
||||
| `~/.pi/agent/hooks/*.ts` | Global (all projects) |
|
||||
| `.pi/hooks/*.ts` | Project-local |
|
||||
| Location | Scope |
|
||||
| ------------------------- | --------------------- |
|
||||
| `~/.omp/agent/hooks/*.ts` | Global (all projects) |
|
||||
| `.omp/hooks/*.ts` | Project-local |
|
||||
|
||||
Additional paths via `settings.json`:
|
||||
|
||||
@@ -99,7 +99,7 @@ Hooks are loaded via [jiti](https://github.com/unjs/jiti), so TypeScript works w
|
||||
### Lifecycle Overview
|
||||
|
||||
```
|
||||
pi starts
|
||||
omp starts
|
||||
│
|
||||
└─► session_start
|
||||
│
|
||||
@@ -509,7 +509,7 @@ Read-only access to session state. See `ReadonlySessionManager` in [`src/core/se
|
||||
```typescript
|
||||
// Session info
|
||||
ctx.sessionManager.getCwd(); // Working directory
|
||||
ctx.sessionManager.getSessionDir(); // Session directory (~/.pi/agent/sessions)
|
||||
ctx.sessionManager.getSessionDir(); // Session directory (~/.omp/agent/sessions)
|
||||
ctx.sessionManager.getSessionId(); // Current session ID
|
||||
ctx.sessionManager.getSessionFile(); // Session file path (undefined with --no-session)
|
||||
|
||||
@@ -864,4 +864,4 @@ In print mode, `select()` returns `undefined`, `confirm()` returns `false`, `inp
|
||||
1. Open VS Code in hooks directory
|
||||
2. Open JavaScript Debug Terminal (Ctrl+Shift+P → "JavaScript Debug Terminal")
|
||||
3. Set breakpoints
|
||||
4. Run `pi --hook ./my-hook.ts`
|
||||
4. Run `omp --hook ./my-hook.ts`
|
||||
|
||||
@@ -7,7 +7,7 @@ RPC mode enables headless operation of the coding agent via a JSON protocol over
|
||||
## Starting RPC Mode
|
||||
|
||||
```bash
|
||||
pi --mode rpc [options]
|
||||
omp --mode rpc [options]
|
||||
```
|
||||
|
||||
Common options:
|
||||
@@ -407,7 +407,7 @@ If output was truncated, includes `fullOutputPath`:
|
||||
"exitCode": 0,
|
||||
"cancelled": false,
|
||||
"truncated": true,
|
||||
"fullOutputPath": "/tmp/pi-bash-abc123.log"
|
||||
"fullOutputPath": "/tmp/omp-bash-abc123.log"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -980,7 +980,7 @@ import subprocess
|
||||
import json
|
||||
|
||||
proc = subprocess.Popen(
|
||||
["pi", "--mode", "rpc", "--no-session"],
|
||||
["omp", "--mode", "rpc", "--no-session"],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
text=True
|
||||
@@ -1017,7 +1017,7 @@ See [`test/rpc-example.ts`](../test/rpc-example.ts) for a complete interactive e
|
||||
const { spawn } = require("child_process");
|
||||
const readline = require("readline");
|
||||
|
||||
const agent = spawn("pi", ["--mode", "rpc", "--no-session"]);
|
||||
const agent = spawn("omp", ["--mode", "rpc", "--no-session"]);
|
||||
|
||||
readline.createInterface({ input: agent.stdout }).on("line", (line) => {
|
||||
const event = JSON.parse(line);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
> pi can help you use the SDK. Ask it to build an integration for your use case.
|
||||
> omp can help you use the SDK. Ask it to build an integration for your use case.
|
||||
|
||||
# SDK
|
||||
|
||||
The SDK provides programmatic access to pi's agent capabilities. Use it to embed pi in other applications, build custom interfaces, or integrate with automated workflows.
|
||||
The SDK provides programmatic access to omp's agent capabilities. Use it to embed omp in other applications, build custom interfaces, or integrate with automated workflows.
|
||||
|
||||
**Example use cases:**
|
||||
|
||||
@@ -60,7 +60,7 @@ The main factory function. Creates an `AgentSession` with configurable options.
|
||||
```typescript
|
||||
import { createAgentSession } from "@oh-my-pi/pi-coding-agent";
|
||||
|
||||
// Minimal: all defaults (discovers everything from cwd and ~/.pi/agent)
|
||||
// Minimal: all defaults (discovers everything from cwd and ~/.omp/agent)
|
||||
const { session } = await createAgentSession();
|
||||
|
||||
// Custom: override specific options
|
||||
@@ -220,16 +220,16 @@ const { session } = await createAgentSession({
|
||||
cwd: process.cwd(), // default
|
||||
|
||||
// Global config directory
|
||||
agentDir: "~/.pi/agent", // default (expands ~)
|
||||
agentDir: "~/.omp/agent", // default (expands ~)
|
||||
});
|
||||
```
|
||||
|
||||
`cwd` is used for:
|
||||
|
||||
- Project hooks (`.pi/hooks/`)
|
||||
- Project tools (`.pi/tools/`)
|
||||
- Project skills (`.pi/skills/`)
|
||||
- Project commands (`.pi/commands/`)
|
||||
- Project hooks (`.omp/hooks/`)
|
||||
- Project tools (`.omp/tools/`)
|
||||
- Project skills (`.omp/skills/`)
|
||||
- Project commands (`.omp/commands/`)
|
||||
- Context files (`AGENTS.md` walking up from cwd)
|
||||
- Session directory naming
|
||||
|
||||
@@ -300,7 +300,7 @@ API key resolution priority (handled by AuthStorage):
|
||||
```typescript
|
||||
import { AuthStorage, ModelRegistry, discoverAuthStorage, discoverModels } from "@oh-my-pi/pi-coding-agent";
|
||||
|
||||
// Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.json
|
||||
// Default: uses ~/.omp/agent/auth.json and ~/.omp/agent/models.json
|
||||
const authStorage = discoverAuthStorage();
|
||||
const modelRegistry = discoverModels(authStorage);
|
||||
|
||||
@@ -721,8 +721,8 @@ const { session } = await createAgentSession({
|
||||
|
||||
Settings load from two locations and merge:
|
||||
|
||||
1. Global: `~/.pi/agent/settings.json`
|
||||
2. Project: `<cwd>/.pi/settings.json`
|
||||
1. Global: `~/.omp/agent/settings.json`
|
||||
2. Project: `<cwd>/.omp/settings.json`
|
||||
|
||||
Project overrides global. Nested objects merge keys. Setters only modify global (project is read-only for version control).
|
||||
|
||||
@@ -749,8 +749,8 @@ import {
|
||||
} from "@oh-my-pi/pi-coding-agent";
|
||||
|
||||
// Auth and Models
|
||||
const authStorage = discoverAuthStorage(); // ~/.pi/agent/auth.json
|
||||
const modelRegistry = discoverModels(authStorage); // + ~/.pi/agent/models.json
|
||||
const authStorage = discoverAuthStorage(); // ~/.omp/agent/auth.json
|
||||
const modelRegistry = discoverModels(authStorage); // + ~/.omp/agent/models.json
|
||||
const allModels = modelRegistry.getAll(); // All models (built-in + custom)
|
||||
const available = await modelRegistry.getAvailable(); // Only models with API keys
|
||||
const model = modelRegistry.find("provider", "id"); // Find specific model
|
||||
@@ -896,7 +896,7 @@ await session.prompt("Get status and list files.");
|
||||
For subprocess-based integration, use RPC mode instead of the SDK:
|
||||
|
||||
```bash
|
||||
pi --mode rpc --no-session
|
||||
omp --mode rpc --no-session
|
||||
```
|
||||
|
||||
See [RPC documentation](rpc.md) for the JSON protocol.
|
||||
|
||||
@@ -87,7 +87,7 @@ export interface BranchSummaryEntry extends SessionEntryBase {
|
||||
```
|
||||
|
||||
- [x] `fromId` field references the abandoned leaf
|
||||
- [x] `fromHook` field distinguishes pi-generated vs hook-generated summaries
|
||||
- [x] `fromHook` field distinguishes omp-generated vs hook-generated summaries
|
||||
- [x] `details` field for file tracking
|
||||
- [x] Branch summarizer implemented with structured output format
|
||||
- [x] Uses serialization approach (same as compaction) to prevent model confusion
|
||||
|
||||
@@ -5,7 +5,7 @@ Sessions are stored as JSONL (JSON Lines) files. Each line is a JSON object with
|
||||
## File Location
|
||||
|
||||
```
|
||||
~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl
|
||||
~/.omp/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl
|
||||
```
|
||||
|
||||
Where `<path>` is the working directory with `/` replaced by `-`.
|
||||
@@ -90,7 +90,7 @@ Created when context is compacted. Stores a summary of earlier messages.
|
||||
|
||||
Optional fields:
|
||||
- `details`: Compaction-implementation specific data (e.g., file operations for default implementation, or custom data for custom hook implementations)
|
||||
- `fromHook`: `true` if generated by a hook, `false`/`undefined` if pi-generated
|
||||
- `fromHook`: `true` if generated by a hook, `false`/`undefined` if omp-generated
|
||||
|
||||
### BranchSummaryEntry
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
> pi can create skills. Ask it to build one for your use case.
|
||||
> omp can create skills. Ask it to build one for your use case.
|
||||
|
||||
# Skills
|
||||
|
||||
Skills are self-contained capability packages that the agent loads on-demand. A skill provides specialized workflows, setup instructions, helper scripts, and reference documentation for specific tasks.
|
||||
|
||||
Pi implements the [Agent Skills standard](https://agentskills.io/specification).
|
||||
OMP implements the [Agent Skills standard](https://agentskills.io/specification).
|
||||
|
||||
**Example use cases:**
|
||||
- Web search and content extraction (Brave Search API)
|
||||
@@ -144,12 +144,12 @@ Skills are discovered from these locations (later wins on name collision):
|
||||
1. `~/.codex/skills/**/SKILL.md` (Codex CLI, recursive)
|
||||
2. `~/.claude/skills/*/SKILL.md` (Claude Code user, one level)
|
||||
3. `<cwd>/.claude/skills/*/SKILL.md` (Claude Code project, one level)
|
||||
4. `~/.pi/agent/skills/**/SKILL.md` (Pi user, recursive)
|
||||
5. `<cwd>/.pi/skills/**/SKILL.md` (Pi project, recursive)
|
||||
4. `~/.omp/agent/skills/**/SKILL.md` (OMP user, recursive)
|
||||
5. `<cwd>/.omp/skills/**/SKILL.md` (OMP project, recursive)
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure skill loading in `~/.pi/agent/settings.json`:
|
||||
Configure skill loading in `~/.omp/agent/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -173,8 +173,8 @@ Configure skill loading in `~/.pi/agent/settings.json`:
|
||||
| `enableCodexUser` | `true` | Load from `~/.codex/skills/` |
|
||||
| `enableClaudeUser` | `true` | Load from `~/.claude/skills/` |
|
||||
| `enableClaudeProject` | `true` | Load from `<cwd>/.claude/skills/` |
|
||||
| `enablePiUser` | `true` | Load from `~/.pi/agent/skills/` |
|
||||
| `enablePiProject` | `true` | Load from `<cwd>/.pi/skills/` |
|
||||
| `enableOmpUser` | `true` | Load from `~/.omp/agent/skills/` |
|
||||
| `enableOmpProject` | `true` | Load from `<cwd>/.omp/skills/` |
|
||||
| `customDirectories` | `[]` | Additional directories to scan (supports `~` expansion) |
|
||||
| `ignoredSkills` | `[]` | Glob patterns to exclude (e.g., `["deprecated-*", "test-skill"]`) |
|
||||
| `includeSkills` | `[]` | Glob patterns to include (empty = all; e.g., `["git-*", "docker"]`) |
|
||||
@@ -187,20 +187,20 @@ Use `--skills` to filter skills for a specific invocation:
|
||||
|
||||
```bash
|
||||
# Only load specific skills
|
||||
pi --skills git,docker
|
||||
omp --skills git,docker
|
||||
|
||||
# Glob patterns
|
||||
pi --skills "git-*,docker-*"
|
||||
omp --skills "git-*,docker-*"
|
||||
|
||||
# All skills matching a prefix
|
||||
pi --skills "aws-*"
|
||||
omp --skills "aws-*"
|
||||
```
|
||||
|
||||
This overrides the `includeSkills` setting for the current session.
|
||||
|
||||
## How Skills Work
|
||||
|
||||
1. At startup, pi scans skill locations and extracts names + descriptions
|
||||
1. At startup, omp scans skill locations and extracts names + descriptions
|
||||
2. The system prompt includes available skills in XML format
|
||||
3. When a task matches, the agent uses `read` to load the full SKILL.md
|
||||
4. The agent follows the instructions, using relative paths to reference scripts/assets
|
||||
@@ -209,7 +209,7 @@ This is progressive disclosure: only descriptions are always in context, full in
|
||||
|
||||
## Validation Warnings
|
||||
|
||||
Pi validates skills against the Agent Skills standard and warns (but still loads) non-compliant skills:
|
||||
OMP validates skills against the Agent Skills standard and warns (but still loads) non-compliant skills:
|
||||
|
||||
- Name doesn't match parent directory
|
||||
- Name exceeds 64 characters
|
||||
@@ -260,9 +260,9 @@ cd /path/to/brave-search && npm install
|
||||
|
||||
## Compatibility
|
||||
|
||||
**Claude Code**: Pi reads skills from `~/.claude/skills/*/SKILL.md`. The `allowed-tools` and `model` frontmatter fields are ignored.
|
||||
**Claude Code**: OMP reads skills from `~/.claude/skills/*/SKILL.md`. The `allowed-tools` and `model` frontmatter fields are ignored.
|
||||
|
||||
**Codex CLI**: Pi reads skills from `~/.codex/skills/` recursively. Hidden files/directories and symlinks are skipped.
|
||||
**Codex CLI**: OMP reads skills from `~/.codex/skills/` recursively. Hidden files/directories and symlinks are skipped.
|
||||
|
||||
## Skill Repositories
|
||||
|
||||
@@ -275,10 +275,10 @@ For inspiration and ready-to-use skills:
|
||||
|
||||
CLI:
|
||||
```bash
|
||||
pi --no-skills
|
||||
omp --no-skills
|
||||
```
|
||||
|
||||
Settings (`~/.pi/agent/settings.json`):
|
||||
Settings (`~/.omp/agent/settings.json`):
|
||||
```json
|
||||
{
|
||||
"skills": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
> pi can create themes. Ask it to build one for your use case.
|
||||
> omp can create themes. Ask it to build one for your use case.
|
||||
|
||||
# Pi Coding Agent Themes
|
||||
# OMP Coding Agent Themes
|
||||
|
||||
Themes allow you to customize the colors used throughout the coding agent TUI.
|
||||
|
||||
@@ -205,7 +205,7 @@ This is useful for:
|
||||
|
||||
## Built-in Themes
|
||||
|
||||
Pi comes with two built-in themes:
|
||||
OMP comes with two built-in themes:
|
||||
|
||||
### `dark` (default)
|
||||
|
||||
@@ -227,26 +227,26 @@ Themes are configured in the settings (accessible via `/settings`):
|
||||
|
||||
Or use the `/theme` command interactively.
|
||||
|
||||
On first run, Pi detects your terminal's background and sets a sensible default (`dark` or `light`).
|
||||
On first run, OMP detects your terminal's background and sets a sensible default (`dark` or `light`).
|
||||
|
||||
## Custom Themes
|
||||
|
||||
### Theme Locations
|
||||
|
||||
Custom themes are loaded from `~/.pi/agent/themes/*.json`.
|
||||
Custom themes are loaded from `~/.omp/agent/themes/*.json`.
|
||||
|
||||
### Creating a Custom Theme
|
||||
|
||||
1. **Create theme directory:**
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.pi/agent/themes
|
||||
mkdir -p ~/.omp/agent/themes
|
||||
```
|
||||
|
||||
2. **Create theme file:**
|
||||
|
||||
```bash
|
||||
vim ~/.pi/agent/themes/my-theme.json
|
||||
vim ~/.omp/agent/themes/my-theme.json
|
||||
```
|
||||
|
||||
3. **Define all colors:**
|
||||
@@ -411,14 +411,14 @@ Example usage:
|
||||
|
||||
### Terminal Compatibility
|
||||
|
||||
Pi uses 24-bit RGB colors (`\x1b[38;2;R;G;Bm`). Most modern terminals support this:
|
||||
OMP uses 24-bit RGB colors (`\x1b[38;2;R;G;Bm`). Most modern terminals support this:
|
||||
|
||||
- ✅ iTerm2, Alacritty, Kitty, WezTerm
|
||||
- ✅ Windows Terminal
|
||||
- ✅ VS Code integrated terminal
|
||||
- ✅ Modern GNOME Terminal, Konsole
|
||||
|
||||
For older terminals with only 256-color support, Pi automatically falls back to the nearest 256-color approximation.
|
||||
For older terminals with only 256-color support, OMP automatically falls back to the nearest 256-color approximation.
|
||||
|
||||
To check if your terminal supports truecolor:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
> pi can create TUI components. Ask it to build one for your use case.
|
||||
> omp can create TUI components. Ask it to build one for your use case.
|
||||
|
||||
# TUI Components
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Examples
|
||||
|
||||
Example code for pi-coding-agent SDK, hooks, and custom tools.
|
||||
Example code for omp-coding-agent SDK, hooks, and custom tools.
|
||||
|
||||
## Directories
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Custom Tools Examples
|
||||
|
||||
Example custom tools for pi-coding-agent.
|
||||
Example custom tools for omp-coding-agent.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -38,13 +38,13 @@ See [subagent/README.md](subagent/README.md) for full documentation.
|
||||
|
||||
```bash
|
||||
# Test directly (can point to any .ts file)
|
||||
pi --tool examples/custom-tools/todo/index.ts
|
||||
omp --tool examples/custom-tools/todo/index.ts
|
||||
|
||||
# Or copy entire folder to tools directory for persistent use
|
||||
cp -r todo ~/.pi/agent/tools/
|
||||
cp -r todo ~/.omp/agent/tools/
|
||||
```
|
||||
|
||||
Then in pi:
|
||||
Then in omp:
|
||||
|
||||
```
|
||||
> add a todo "test custom tools"
|
||||
|
||||
@@ -4,7 +4,7 @@ Delegate tasks to specialized subagents with isolated context windows.
|
||||
|
||||
## Features
|
||||
|
||||
- **Isolated context**: Each subagent runs in a separate `pi` process
|
||||
- **Isolated context**: Each subagent runs in a separate `omp` process
|
||||
- **Streaming output**: See tool calls and progress as they happen
|
||||
- **Parallel streaming**: All parallel tasks stream updates simultaneously
|
||||
- **Markdown rendering**: Final output rendered with proper formatting (expanded view)
|
||||
@@ -35,30 +35,30 @@ From the repository root, symlink the files:
|
||||
|
||||
```bash
|
||||
# Symlink the tool (must be in a subdirectory with index.ts)
|
||||
mkdir -p ~/.pi/agent/tools/subagent
|
||||
ln -sf "$(pwd)/packages/coding-agent/examples/custom-tools/subagent/subagent.ts" ~/.pi/agent/tools/subagent/index.ts
|
||||
ln -sf "$(pwd)/packages/coding-agent/examples/custom-tools/subagent/agents.ts" ~/.pi/agent/tools/subagent/agents.ts
|
||||
mkdir -p ~/.omp/agent/tools/subagent
|
||||
ln -sf "$(pwd)/packages/coding-agent/examples/custom-tools/subagent/subagent.ts" ~/.omp/agent/tools/subagent/index.ts
|
||||
ln -sf "$(pwd)/packages/coding-agent/examples/custom-tools/subagent/agents.ts" ~/.omp/agent/tools/subagent/agents.ts
|
||||
|
||||
# Symlink agents
|
||||
mkdir -p ~/.pi/agent/agents
|
||||
mkdir -p ~/.omp/agent/agents
|
||||
for f in packages/coding-agent/examples/custom-tools/subagent/agents/*.md; do
|
||||
ln -sf "$(pwd)/$f" ~/.pi/agent/agents/$(basename "$f")
|
||||
ln -sf "$(pwd)/$f" ~/.omp/agent/agents/$(basename "$f")
|
||||
done
|
||||
|
||||
# Symlink workflow commands
|
||||
mkdir -p ~/.pi/agent/commands
|
||||
mkdir -p ~/.omp/agent/commands
|
||||
for f in packages/coding-agent/examples/custom-tools/subagent/commands/*.md; do
|
||||
ln -sf "$(pwd)/$f" ~/.pi/agent/commands/$(basename "$f")
|
||||
ln -sf "$(pwd)/$f" ~/.omp/agent/commands/$(basename "$f")
|
||||
done
|
||||
```
|
||||
|
||||
## Security Model
|
||||
|
||||
This tool executes a separate `pi` subprocess with a delegated system prompt and tool/model configuration.
|
||||
This tool executes a separate `omp` subprocess with a delegated system prompt and tool/model configuration.
|
||||
|
||||
**Project-local agents** (`.pi/agents/*.md`) are repo-controlled prompts that can instruct the model to read files, run bash commands, etc.
|
||||
**Project-local agents** (`.omp/agents/*.md`) are repo-controlled prompts that can instruct the model to read files, run bash commands, etc.
|
||||
|
||||
**Default behavior:** Only loads **user-level agents** from `~/.pi/agent/agents`.
|
||||
**Default behavior:** Only loads **user-level agents** from `~/.omp/agent/agents`.
|
||||
|
||||
To enable project-local agents, pass `agentScope: "both"` (or `"project"`). Only do this for repositories you trust.
|
||||
|
||||
@@ -136,8 +136,8 @@ System prompt for the agent goes here.
|
||||
```
|
||||
|
||||
**Locations:**
|
||||
- `~/.pi/agent/agents/*.md` - User-level (always loaded)
|
||||
- `.pi/agents/*.md` - Project-level (only with `agentScope: "project"` or `"both"`)
|
||||
- `~/.omp/agent/agents/*.md` - User-level (always loaded)
|
||||
- `.omp/agents/*.md` - Project-level (only with `agentScope: "project"` or `"both"`)
|
||||
|
||||
Project agents override user agents with the same name when `agentScope: "both"`.
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ function isDirectory(p: string): boolean {
|
||||
function findNearestProjectAgentsDir(cwd: string): string | null {
|
||||
let currentDir = cwd;
|
||||
while (true) {
|
||||
const candidate = path.join(currentDir, ".pi", "agents");
|
||||
const candidate = path.join(currentDir, ".omp", "agents");
|
||||
if (isDirectory(candidate)) return candidate;
|
||||
|
||||
const parentDir = path.dirname(currentDir);
|
||||
@@ -125,7 +125,7 @@ function findNearestProjectAgentsDir(cwd: string): string | null {
|
||||
}
|
||||
|
||||
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
||||
const userDir = path.join(os.homedir(), ".pi", "agent", "agents");
|
||||
const userDir = path.join(os.homedir(), ".omp", "agent", "agents");
|
||||
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
||||
|
||||
const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Subagent Tool - Delegate tasks to specialized agents
|
||||
*
|
||||
* Spawns a separate `pi` process for each subagent invocation,
|
||||
* Spawns a separate `omp` process for each subagent invocation,
|
||||
* giving it an isolated context window.
|
||||
*
|
||||
* Supports three modes:
|
||||
@@ -205,7 +205,7 @@ async function mapWithConcurrencyLimit<TIn, TOut>(
|
||||
}
|
||||
|
||||
function writePromptToTempFile(agentName: string, prompt: string): { dir: string; filePath: string } {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omp-subagent-"));
|
||||
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
||||
const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
|
||||
fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
|
||||
@@ -280,7 +280,7 @@ async function runSingleAgent(
|
||||
let wasAborted = false;
|
||||
|
||||
const exitCode = await new Promise<number>((resolve) => {
|
||||
const proc = Bun.spawn(["pi", ...args], {
|
||||
const proc = Bun.spawn(["omp", ...args], {
|
||||
cwd: cwd ?? pi.cwd,
|
||||
stdin: "ignore",
|
||||
stdout: "pipe",
|
||||
@@ -440,8 +440,8 @@ const factory: CustomToolFactory = (pi) => {
|
||||
return [
|
||||
"Delegate tasks to specialized subagents with isolated context.",
|
||||
"Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
|
||||
'Default agent scope is "user" (from ~/.pi/agent/agents).',
|
||||
'To enable project-local agents in .pi/agents, set agentScope: "both" (or "project").',
|
||||
'Default agent scope is "user" (from ~/.omp/agent/agents).',
|
||||
'To enable project-local agents in .omp/agents, set agentScope: "both" (or "project").',
|
||||
`User agents: ${userList.text}${userSuffix}.`,
|
||||
`Project agents${projectDirNote}: ${projectList.text}${projectSuffix}.`,
|
||||
].join(" ");
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# Hooks Examples
|
||||
|
||||
Example hooks for pi-coding-agent.
|
||||
Example hooks for omp-coding-agent.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Load a hook with --hook flag
|
||||
pi --hook examples/hooks/permission-gate.ts
|
||||
omp --hook examples/hooks/permission-gate.ts
|
||||
|
||||
# Or copy to hooks directory for auto-discovery
|
||||
cp permission-gate.ts ~/.pi/agent/hooks/
|
||||
cp permission-gate.ts ~/.omp/agent/hooks/
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function (pi: HookAPI) {
|
||||
|
||||
// Generate a simple commit message
|
||||
const firstLine = lastAssistantText.split("\n")[0] || "Work in progress";
|
||||
const commitMessage = `[pi] ${firstLine.slice(0, 50)}${firstLine.length > 50 ? "..." : ""}`;
|
||||
const commitMessage = `[omp] ${firstLine.slice(0, 50)}${firstLine.length > 50 ? "..." : ""}`;
|
||||
|
||||
// Stage and commit
|
||||
await pi.exec("git", ["add", "-A"]);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* which can be cheaper/faster than the main conversation model.
|
||||
*
|
||||
* Usage:
|
||||
* pi --hook examples/hooks/custom-compaction.ts
|
||||
* omp --hook examples/hooks/custom-compaction.ts
|
||||
*/
|
||||
|
||||
import { complete, getModel } from "@oh-my-pi/pi-ai";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Minimal SDK Usage
|
||||
*
|
||||
* Uses all defaults: discovers skills, hooks, tools, context files
|
||||
* from cwd and ~/.pi/agent. Model chosen from settings or first available.
|
||||
* from cwd and ~/.omp/agent. Model chosen from settings or first available.
|
||||
*/
|
||||
|
||||
import { createAgentSession } from "@oh-my-pi/pi-coding-agent";
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { createAgentSession, discoverSkills, SessionManager, type Skill } from "@oh-my-pi/pi-coding-agent";
|
||||
|
||||
// Discover all skills from cwd/.pi/skills, ~/.pi/agent/skills, etc.
|
||||
// Discover all skills from cwd/.omp/skills, ~/.omp/agent/skills, etc.
|
||||
const allSkills = discoverSkills();
|
||||
console.log(
|
||||
"Discovered skills:",
|
||||
|
||||
@@ -82,7 +82,7 @@ session.subscribe((event) => {
|
||||
await session.prompt("What's the weather in Tokyo?");
|
||||
console.log();
|
||||
|
||||
// Merge with discovered tools from cwd/.pi/tools and ~/.pi/agent/tools:
|
||||
// Merge with discovered tools from cwd/.omp/tools and ~/.omp/agent/tools:
|
||||
// const discovered = await discoverCustomTools();
|
||||
// customTools: [...discovered, { tool: myTool }]
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
SessionManager,
|
||||
} from "@oh-my-pi/pi-coding-agent";
|
||||
|
||||
// Discover commands from cwd/.pi/commands/ and ~/.pi/agent/commands/
|
||||
// Discover commands from cwd/.omp/commands/ and ~/.omp/agent/commands/
|
||||
const discovered = discoverSlashCommands();
|
||||
console.log("Discovered slash commands:");
|
||||
for (const cmd of discovered) {
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
SessionManager,
|
||||
} from "@oh-my-pi/pi-coding-agent";
|
||||
|
||||
// Default: discoverAuthStorage() uses ~/.pi/agent/auth.json
|
||||
// discoverModels() loads built-in + custom models from ~/.pi/agent/models.json
|
||||
// Default: discoverAuthStorage() uses ~/.omp/agent/auth.json
|
||||
// discoverModels() loads built-in + custom models from ~/.omp/agent/models.json
|
||||
const authStorage = discoverAuthStorage();
|
||||
const modelRegistry = discoverModels(authStorage);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SDK Examples
|
||||
|
||||
Programmatic usage of pi-coding-agent via `createAgentSession()`.
|
||||
Programmatic usage of omp-coding-agent via `createAgentSession()`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -115,7 +115,7 @@ await session.prompt("Hello");
|
||||
| `authStorage` | `discoverAuthStorage()` | Credential storage |
|
||||
| `modelRegistry` | `discoverModels(authStorage)` | Model registry |
|
||||
| `cwd` | `process.cwd()` | Working directory |
|
||||
| `agentDir` | `~/.pi/agent` | Config directory |
|
||||
| `agentDir` | `~/.omp/agent` | Config directory |
|
||||
| `model` | From settings/first available | Model to use |
|
||||
| `thinkingLevel` | From settings/"off" | off, low, medium, high |
|
||||
| `systemPrompt` | Discovered | String or `(default) => modified` |
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
"version": "2.3.1337",
|
||||
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
||||
"type": "module",
|
||||
"piConfig": {
|
||||
"name": "pi",
|
||||
"configDir": ".pi"
|
||||
"ompConfig": {
|
||||
"name": "omp",
|
||||
"configDir": ".omp"
|
||||
},
|
||||
"bin": {
|
||||
"pi": "src/cli.ts"
|
||||
"omp": "src/cli.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
@@ -32,7 +32,7 @@
|
||||
"check": "tsgo --noEmit",
|
||||
"clean": "rm -rf dist",
|
||||
"build": "tsgo -p tsconfig.build.json && chmod +x dist/cli.js",
|
||||
"build:binary": "bun build --compile ./src/cli.ts --outfile dist/pi",
|
||||
"build:binary": "bun build --compile ./src/cli.ts --outfile dist/omp",
|
||||
"generate-template": "bun scripts/generate-template.ts",
|
||||
"postinstall": "[ -f scripts/generate-template.ts ] && bun run generate-template || true",
|
||||
"test": "bun test",
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Migrate sessions from ~/.pi/agent/*.jsonl to proper session directories.
|
||||
# Migrate sessions from ~/.omp/agent/*.jsonl to proper session directories.
|
||||
# This fixes sessions created by the bug in v0.30.0 where sessions were
|
||||
# saved to ~/.pi/agent/ instead of ~/.pi/agent/sessions/<encoded-cwd>/.
|
||||
# saved to ~/.omp/agent/ instead of ~/.omp/agent/sessions/<encoded-cwd>/.
|
||||
#
|
||||
# Usage: ./migrate-sessions.sh [--dry-run]
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
AGENT_DIR="${PI_AGENT_DIR:-$HOME/.pi/agent}"
|
||||
AGENT_DIR="${OMP_AGENT_DIR:-$HOME/.omp/agent}"
|
||||
DRY_RUN=false
|
||||
|
||||
if [[ "$1" == "--dry-run" ]]; then
|
||||
|
||||
@@ -153,8 +153,8 @@ ${chalk.bold("Usage:")}
|
||||
|
||||
${chalk.bold("Options:")}
|
||||
--model <pattern> Model to use (fuzzy match: "opus", "gpt-5.2", or "p-openai/gpt-5.2")
|
||||
--smol <id> Smol/fast model for lightweight tasks (or PI_SMOL_MODEL env)
|
||||
--slow <id> Slow/reasoning model for thorough analysis (or PI_SLOW_MODEL env)
|
||||
--smol <id> Smol/fast model for lightweight tasks (or OMP_SMOL_MODEL env)
|
||||
--slow <id> Slow/reasoning model for thorough analysis (or OMP_SLOW_MODEL env)
|
||||
--api-key <key> API key (defaults to env vars)
|
||||
--system-prompt <text> System prompt (default: coding assistant prompt)
|
||||
--append-system-prompt <text> Append text or file contents to the system prompt
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/**
|
||||
* Plugin CLI command handlers.
|
||||
*
|
||||
* Handles `pi plugin <command>` subcommands for plugin lifecycle management.
|
||||
* Handles `omp plugin <command>` subcommands for plugin lifecycle management.
|
||||
*/
|
||||
|
||||
import chalk from "chalk";
|
||||
import { APP_NAME } from "../config";
|
||||
import { PluginManager, parseSettingValue, validateSetting } from "../core/plugins/index";
|
||||
|
||||
// =============================================================================
|
||||
@@ -153,12 +154,12 @@ async function handleInstall(
|
||||
flags: { json?: boolean; force?: boolean; dryRun?: boolean },
|
||||
): Promise<void> {
|
||||
if (packages.length === 0) {
|
||||
console.error(chalk.red("Usage: pi plugin install <package[@version]>[features] ..."));
|
||||
console.error(chalk.red(`Usage: ${APP_NAME} plugin install <package[@version]>[features] ...`));
|
||||
console.error(chalk.dim("Examples:"));
|
||||
console.error(chalk.dim(" pi plugin install @oh-my-pi/exa"));
|
||||
console.error(chalk.dim(" pi plugin install @oh-my-pi/exa[search,websets]"));
|
||||
console.error(chalk.dim(" pi plugin install @oh-my-pi/exa[*] # all features"));
|
||||
console.error(chalk.dim(" pi plugin install @oh-my-pi/exa[] # no optional features"));
|
||||
console.error(chalk.dim(` ${APP_NAME} plugin install @oh-my-pi/exa`));
|
||||
console.error(chalk.dim(` ${APP_NAME} plugin install @oh-my-pi/exa[search,websets]`));
|
||||
console.error(chalk.dim(` ${APP_NAME} plugin install @oh-my-pi/exa[*] # all features`));
|
||||
console.error(chalk.dim(` ${APP_NAME} plugin install @oh-my-pi/exa[] # no optional features`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -190,7 +191,7 @@ async function handleInstall(
|
||||
|
||||
async function handleUninstall(manager: PluginManager, packages: string[], flags: { json?: boolean }): Promise<void> {
|
||||
if (packages.length === 0) {
|
||||
console.error(chalk.red("Usage: pi plugin uninstall <package> ..."));
|
||||
console.error(chalk.red(`Usage: ${APP_NAME} plugin uninstall <package> ...`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -220,7 +221,7 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
|
||||
|
||||
if (plugins.length === 0) {
|
||||
console.log(chalk.dim("No plugins installed"));
|
||||
console.log(chalk.dim("\nInstall plugins with: pi plugin install <package>"));
|
||||
console.log(chalk.dim(`\nInstall plugins with: ${APP_NAME} plugin install <package>`));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -254,7 +255,7 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
|
||||
|
||||
async function handleLink(manager: PluginManager, paths: string[], flags: { json?: boolean }): Promise<void> {
|
||||
if (paths.length === 0) {
|
||||
console.error(chalk.red("Usage: pi plugin link <path>"));
|
||||
console.error(chalk.red(`Usage: ${APP_NAME} plugin link <path>`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -313,7 +314,9 @@ async function handleFeatures(
|
||||
flags: { json?: boolean; enable?: string; disable?: string; set?: string },
|
||||
): Promise<void> {
|
||||
if (args.length === 0) {
|
||||
console.error(chalk.red("Usage: pi plugin features <plugin> [--enable f1,f2] [--disable f1] [--set f1,f2]"));
|
||||
console.error(
|
||||
chalk.red(`Usage: ${APP_NAME} plugin features <plugin> [--enable f1,f2] [--disable f1] [--set f1,f2]`),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -404,7 +407,9 @@ async function handleConfig(
|
||||
flags: { json?: boolean; local?: boolean },
|
||||
): Promise<void> {
|
||||
if (args.length === 0) {
|
||||
console.error(chalk.red("Usage: pi plugin config <list|get|set|delete|validate> <plugin> [key] [value]"));
|
||||
console.error(
|
||||
chalk.red(`Usage: ${APP_NAME} plugin config <list|get|set|delete|validate> <plugin> [key] [value]`),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -560,7 +565,7 @@ async function handleConfigValidate(manager: PluginManager, flags: { json?: bool
|
||||
|
||||
async function handleEnable(manager: PluginManager, plugins: string[], flags: { json?: boolean }): Promise<void> {
|
||||
if (plugins.length === 0) {
|
||||
console.error(chalk.red("Usage: pi plugin enable <plugin> ..."));
|
||||
console.error(chalk.red(`Usage: ${APP_NAME} plugin enable <plugin> ...`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -582,7 +587,7 @@ async function handleEnable(manager: PluginManager, plugins: string[], flags: {
|
||||
|
||||
async function handleDisable(manager: PluginManager, plugins: string[], flags: { json?: boolean }): Promise<void> {
|
||||
if (plugins.length === 0) {
|
||||
console.error(chalk.red("Usage: pi plugin disable <plugin> ..."));
|
||||
console.error(chalk.red(`Usage: ${APP_NAME} plugin disable <plugin> ...`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -607,7 +612,7 @@ async function handleDisable(manager: PluginManager, plugins: string[], flags: {
|
||||
// =============================================================================
|
||||
|
||||
export function printPluginHelp(): void {
|
||||
console.log(`${chalk.bold("pi plugin")} - Plugin lifecycle management
|
||||
console.log(`${chalk.bold(`${APP_NAME} plugin`)} - Plugin lifecycle management
|
||||
|
||||
${chalk.bold("Commands:")}
|
||||
install <pkg[@ver]>[features] Install plugins from npm
|
||||
@@ -641,10 +646,10 @@ ${chalk.bold("Options:")}
|
||||
-l, --local Use project-local overrides
|
||||
|
||||
${chalk.bold("Examples:")}
|
||||
pi plugin install @oh-my-pi/exa[search]
|
||||
pi plugin list --json
|
||||
pi plugin features my-plugin --enable search,web
|
||||
pi plugin config set my-plugin apiKey sk-xxx
|
||||
pi plugin doctor --fix
|
||||
${APP_NAME} plugin install @oh-my-pi/exa[search]
|
||||
${APP_NAME} plugin list --json
|
||||
${APP_NAME} plugin features my-plugin --enable search,web
|
||||
${APP_NAME} plugin config set my-plugin apiKey sk-xxx
|
||||
${APP_NAME} plugin doctor --fix
|
||||
`);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Update CLI command handler.
|
||||
*
|
||||
* Handles `pi update` to check for and install updates.
|
||||
* Handles `omp update` to check for and install updates.
|
||||
* Uses bun if available, otherwise downloads binary from GitHub releases.
|
||||
*/
|
||||
|
||||
@@ -11,7 +11,7 @@ import { dirname } from "node:path";
|
||||
import { Readable } from "node:stream";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import chalk from "chalk";
|
||||
import { VERSION } from "../config";
|
||||
import { APP_NAME, VERSION } from "../config";
|
||||
|
||||
/**
|
||||
* Detect if we're running as a Bun compiled binary.
|
||||
@@ -129,9 +129,9 @@ function getBinaryName(): string {
|
||||
}
|
||||
|
||||
if (os === "windows") {
|
||||
return `pi-${os}-${archName}.exe`;
|
||||
return `${APP_NAME}-${os}-${archName}.exe`;
|
||||
}
|
||||
return `pi-${os}-${archName}`;
|
||||
return `${APP_NAME}-${os}-${archName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,7 +193,7 @@ async function updateViaBinary(release: ReleaseInfo): Promise<void> {
|
||||
unlinkSync(backupPath);
|
||||
|
||||
console.log(chalk.green(`\n✓ Updated to ${release.version}`));
|
||||
console.log(chalk.dim("Restart pi to use the new version"));
|
||||
console.log(chalk.dim(`Restart ${APP_NAME} to use the new version`));
|
||||
} catch (err) {
|
||||
// Restore from backup if possible
|
||||
if (existsSync(backupPath) && !existsSync(execPath)) {
|
||||
@@ -256,18 +256,18 @@ export async function runUpdateCommand(opts: { force: boolean; check: boolean })
|
||||
* Print update command help.
|
||||
*/
|
||||
export function printUpdateHelp(): void {
|
||||
console.log(`${chalk.bold("pi update")} - Check for and install updates
|
||||
console.log(`${chalk.bold(`${APP_NAME} update`)} - Check for and install updates
|
||||
|
||||
${chalk.bold("Usage:")}
|
||||
pi update [options]
|
||||
${APP_NAME} update [options]
|
||||
|
||||
${chalk.bold("Options:")}
|
||||
-c, --check Check for updates without installing
|
||||
-f, --force Force reinstall even if up to date
|
||||
|
||||
${chalk.bold("Examples:")}
|
||||
pi update Update to latest version
|
||||
pi update --check Check if updates are available
|
||||
pi update --force Force reinstall
|
||||
${APP_NAME} update Update to latest version
|
||||
${APP_NAME} update --check Check if updates are available
|
||||
${APP_NAME} update --force Force reinstall
|
||||
`);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { existsSync, readFileSync, statSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
|
||||
@@ -9,12 +9,20 @@ import packageJson from "../package.json" with { type: "json" };
|
||||
// App Config (from embedded package.json)
|
||||
// =============================================================================
|
||||
|
||||
export const APP_NAME: string = (packageJson as { piConfig?: { name?: string } }).piConfig?.name || "pi";
|
||||
export const APP_NAME: string = (packageJson as { ompConfig?: { name?: string } }).ompConfig?.name || "omp";
|
||||
export const CONFIG_DIR_NAME: string =
|
||||
(packageJson as { piConfig?: { configDir?: string } }).piConfig?.configDir || ".pi";
|
||||
(packageJson as { ompConfig?: { configDir?: string } }).ompConfig?.configDir || ".omp";
|
||||
export const VERSION: string = (packageJson as { version: string }).version;
|
||||
|
||||
// e.g., PI_CODING_AGENT_DIR or TAU_CODING_AGENT_DIR
|
||||
const priorityList = [
|
||||
{ dir: ".omp", globalAgentDir: ".omp/agent" },
|
||||
{ dir: ".pi", globalAgentDir: ".pi/agent" },
|
||||
{ dir: ".claude" },
|
||||
{ dir: ".codex" },
|
||||
{ dir: ".gemini" },
|
||||
];
|
||||
|
||||
// e.g., OMP_CODING_AGENT_DIR
|
||||
export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
|
||||
|
||||
// =============================================================================
|
||||
@@ -58,10 +66,10 @@ export function getChangelogPath(): string {
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// User Config Paths (~/.pi/agent/*)
|
||||
// User Config Paths (~/.omp/agent/*)
|
||||
// =============================================================================
|
||||
|
||||
/** Get the agent config directory (e.g., ~/.pi/agent/) */
|
||||
/** Get the agent config directory (e.g., ~/.omp/agent/) */
|
||||
export function getAgentDir(): string {
|
||||
return process.env[ENV_AGENT_DIR] || join(homedir(), CONFIG_DIR_NAME, "agent");
|
||||
}
|
||||
@@ -105,3 +113,279 @@ export function getSessionsDir(): string {
|
||||
export function getDebugLogPath(): string {
|
||||
return join(getAgentDir(), `${APP_NAME}-debug.log`);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Multi-Config Directory Helpers
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Config directory bases in priority order (highest first).
|
||||
* User-level: ~/.omp/agent, ~/.pi/agent, ~/.claude, ~/.codex, ~/.gemini
|
||||
* Project-level: .omp, .pi, .claude, .codex, .gemini
|
||||
*/
|
||||
const USER_CONFIG_BASES = priorityList.map(({ dir, globalAgentDir }) => ({
|
||||
base: () => join(homedir(), globalAgentDir ?? dir),
|
||||
name: dir,
|
||||
}));
|
||||
|
||||
const PROJECT_CONFIG_BASES = priorityList.map(({ dir }) => ({
|
||||
base: dir,
|
||||
name: dir,
|
||||
}));
|
||||
|
||||
export interface ConfigDirEntry {
|
||||
path: string;
|
||||
source: string; // e.g., ".omp", ".pi", ".claude"
|
||||
level: "user" | "project";
|
||||
}
|
||||
|
||||
export interface GetConfigDirsOptions {
|
||||
/** Include user-level directories (~/.omp/agent/...). Default: true */
|
||||
user?: boolean;
|
||||
/** Include project-level directories (.omp/...). Default: true */
|
||||
project?: boolean;
|
||||
/** Current working directory for project paths. Default: process.cwd() */
|
||||
cwd?: string;
|
||||
/** Only return directories that exist. Default: false */
|
||||
existingOnly?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all config directories for a subpath, ordered by priority (highest first).
|
||||
*
|
||||
* @param subpath - Subpath within config dirs (e.g., "commands", "hooks", "agents")
|
||||
* @param options - Options for filtering
|
||||
* @returns Array of directory entries, highest priority first
|
||||
*
|
||||
* @example
|
||||
* // Get all command directories
|
||||
* getConfigDirs("commands")
|
||||
* // → [{ path: "~/.omp/agent/commands", source: ".omp", level: "user" }, ...]
|
||||
*
|
||||
* @example
|
||||
* // Get only existing project skill directories
|
||||
* getConfigDirs("skills", { user: false, existingOnly: true })
|
||||
*/
|
||||
export function getConfigDirs(subpath: string, options: GetConfigDirsOptions = {}): ConfigDirEntry[] {
|
||||
const { user = true, project = true, cwd = process.cwd(), existingOnly = false } = options;
|
||||
const results: ConfigDirEntry[] = [];
|
||||
|
||||
// User-level directories (highest priority)
|
||||
if (user) {
|
||||
for (const { base, name } of USER_CONFIG_BASES) {
|
||||
const path = join(base(), subpath);
|
||||
if (!existingOnly || existsSync(path)) {
|
||||
results.push({ path, source: name, level: "user" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Project-level directories
|
||||
if (project) {
|
||||
for (const { base, name } of PROJECT_CONFIG_BASES) {
|
||||
const path = resolve(cwd, base, subpath);
|
||||
if (!existingOnly || existsSync(path)) {
|
||||
results.push({ path, source: name, level: "project" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all config directory paths for a subpath (convenience wrapper).
|
||||
* Returns just the paths, highest priority first.
|
||||
*/
|
||||
export function getConfigDirPaths(subpath: string, options: GetConfigDirsOptions = {}): string[] {
|
||||
return getConfigDirs(subpath, options).map((e) => e.path);
|
||||
}
|
||||
|
||||
export interface ConfigFileResult<T> {
|
||||
path: string;
|
||||
source: string;
|
||||
level: "user" | "project";
|
||||
content: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the first existing config file from priority-ordered locations.
|
||||
*
|
||||
* @param subpath - Subpath within config dirs (e.g., "settings.json", "models.json")
|
||||
* @param options - Options for filtering (same as getConfigDirs)
|
||||
* @returns The parsed content and metadata, or undefined if not found
|
||||
*
|
||||
* @example
|
||||
* const result = readConfigFile<Settings>("settings.json", { project: false });
|
||||
* if (result) {
|
||||
* console.log(`Loaded from ${result.path}`);
|
||||
* console.log(result.content);
|
||||
* }
|
||||
*/
|
||||
export function readConfigFile<T = unknown>(
|
||||
subpath: string,
|
||||
options: GetConfigDirsOptions = {},
|
||||
): ConfigFileResult<T> | undefined {
|
||||
const dirs = getConfigDirs("", { ...options, existingOnly: false });
|
||||
|
||||
for (const { path: base, source, level } of dirs) {
|
||||
const filePath = join(base, subpath);
|
||||
if (existsSync(filePath)) {
|
||||
try {
|
||||
const content = readFileSync(filePath, "utf-8");
|
||||
return {
|
||||
path: filePath,
|
||||
source,
|
||||
level,
|
||||
content: JSON.parse(content) as T,
|
||||
};
|
||||
} catch {
|
||||
// Continue to next file on parse error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all existing config files for a subpath (for merging scenarios).
|
||||
* Returns in priority order (highest first).
|
||||
*/
|
||||
export function readAllConfigFiles<T = unknown>(
|
||||
subpath: string,
|
||||
options: GetConfigDirsOptions = {},
|
||||
): ConfigFileResult<T>[] {
|
||||
const dirs = getConfigDirs("", { ...options, existingOnly: false });
|
||||
const results: ConfigFileResult<T>[] = [];
|
||||
|
||||
for (const { path: base, source, level } of dirs) {
|
||||
const filePath = join(base, subpath);
|
||||
if (existsSync(filePath)) {
|
||||
try {
|
||||
const content = readFileSync(filePath, "utf-8");
|
||||
results.push({
|
||||
path: filePath,
|
||||
source,
|
||||
level,
|
||||
content: JSON.parse(content) as T,
|
||||
});
|
||||
} catch {
|
||||
// Skip files that fail to parse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first existing config file (for non-JSON files like SYSTEM.md).
|
||||
* Returns just the path, or undefined if not found.
|
||||
*/
|
||||
export function findConfigFile(subpath: string, options: GetConfigDirsOptions = {}): string | undefined {
|
||||
const dirs = getConfigDirs("", { ...options, existingOnly: false });
|
||||
|
||||
for (const { path: base } of dirs) {
|
||||
const filePath = join(base, subpath);
|
||||
if (existsSync(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first existing config file with metadata.
|
||||
*/
|
||||
export function findConfigFileWithMeta(
|
||||
subpath: string,
|
||||
options: GetConfigDirsOptions = {},
|
||||
): Omit<ConfigFileResult<never>, "content"> | undefined {
|
||||
const dirs = getConfigDirs("", { ...options, existingOnly: false });
|
||||
|
||||
for (const { path: base, source, level } of dirs) {
|
||||
const filePath = join(base, subpath);
|
||||
if (existsSync(filePath)) {
|
||||
return { path: filePath, source, level };
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Walk-Up Config Discovery (for monorepo scenarios)
|
||||
// =============================================================================
|
||||
|
||||
function isDirectory(p: string): boolean {
|
||||
try {
|
||||
return statSync(p).isDirectory();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find nearest config directory by walking up from cwd.
|
||||
* Checks all config bases (.omp, .pi, .claude) at each level.
|
||||
*
|
||||
* @param subpath - Subpath within config dirs (e.g., "commands", "agents")
|
||||
* @param cwd - Starting directory
|
||||
* @returns First existing directory found, or undefined
|
||||
*/
|
||||
export function findNearestProjectConfigDir(subpath: string, cwd: string = process.cwd()): ConfigDirEntry | undefined {
|
||||
let currentDir = cwd;
|
||||
|
||||
while (true) {
|
||||
// Check all config bases at this level, in priority order
|
||||
for (const { base, name } of PROJECT_CONFIG_BASES) {
|
||||
const candidate = join(currentDir, base, subpath);
|
||||
if (isDirectory(candidate)) {
|
||||
return { path: candidate, source: name, level: "project" };
|
||||
}
|
||||
}
|
||||
|
||||
// Move up one directory
|
||||
const parentDir = dirname(currentDir);
|
||||
if (parentDir === currentDir) break; // Reached root
|
||||
currentDir = parentDir;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all nearest config directories by walking up from cwd.
|
||||
* Returns one entry per config base (.omp, .pi, .claude) - the nearest one found.
|
||||
* Results are in priority order (highest first).
|
||||
*/
|
||||
export function findAllNearestProjectConfigDirs(subpath: string, cwd: string = process.cwd()): ConfigDirEntry[] {
|
||||
const results: ConfigDirEntry[] = [];
|
||||
const foundBases = new Set<string>();
|
||||
|
||||
let currentDir = cwd;
|
||||
|
||||
while (foundBases.size < PROJECT_CONFIG_BASES.length) {
|
||||
for (const { base, name } of PROJECT_CONFIG_BASES) {
|
||||
if (foundBases.has(name)) continue;
|
||||
|
||||
const candidate = join(currentDir, base, subpath);
|
||||
if (isDirectory(candidate)) {
|
||||
results.push({ path: candidate, source: name, level: "project" });
|
||||
foundBases.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
const parentDir = dirname(currentDir);
|
||||
if (parentDir === currentDir) break;
|
||||
currentDir = parentDir;
|
||||
}
|
||||
|
||||
// Sort by priority order
|
||||
const order = PROJECT_CONFIG_BASES.map((b) => b.name);
|
||||
results.sort((a, b) => order.indexOf(a.source) - order.indexOf(b.source));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
type OAuthCredentials,
|
||||
type OAuthProvider,
|
||||
} from "@oh-my-pi/pi-ai";
|
||||
import { logger } from "./logger";
|
||||
|
||||
export type ApiKeyCredential = {
|
||||
type: "api_key";
|
||||
@@ -31,13 +32,21 @@ export type AuthStorageData = Record<string, AuthCredential>;
|
||||
|
||||
/**
|
||||
* Credential storage backed by a JSON file.
|
||||
* Reads from multiple fallback paths, writes to primary path.
|
||||
*/
|
||||
export class AuthStorage {
|
||||
private data: AuthStorageData = {};
|
||||
private runtimeOverrides: Map<string, string> = new Map();
|
||||
private fallbackResolver?: (provider: string) => string | undefined;
|
||||
|
||||
constructor(private authPath: string) {
|
||||
/**
|
||||
* @param authPath - Primary path for reading/writing auth.json
|
||||
* @param fallbackPaths - Additional paths to check when reading (legacy support)
|
||||
*/
|
||||
constructor(
|
||||
private authPath: string,
|
||||
private fallbackPaths: string[] = [],
|
||||
) {
|
||||
this.reload();
|
||||
}
|
||||
|
||||
@@ -66,17 +75,31 @@ export class AuthStorage {
|
||||
|
||||
/**
|
||||
* Reload credentials from disk.
|
||||
* Checks primary path first, then fallback paths.
|
||||
*/
|
||||
reload(): void {
|
||||
if (!existsSync(this.authPath)) {
|
||||
this.data = {};
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.data = JSON.parse(readFileSync(this.authPath, "utf-8"));
|
||||
} catch {
|
||||
this.data = {};
|
||||
const pathsToCheck = [this.authPath, ...this.fallbackPaths];
|
||||
|
||||
logger.debug("AuthStorage.reload checking paths", { paths: pathsToCheck });
|
||||
|
||||
for (const authPath of pathsToCheck) {
|
||||
const exists = existsSync(authPath);
|
||||
logger.debug("AuthStorage.reload path check", { path: authPath, exists });
|
||||
|
||||
if (exists) {
|
||||
try {
|
||||
this.data = JSON.parse(readFileSync(authPath, "utf-8"));
|
||||
logger.debug("AuthStorage.reload loaded", { path: authPath, providers: Object.keys(this.data) });
|
||||
return;
|
||||
} catch (e) {
|
||||
logger.error("AuthStorage failed to parse auth file", { path: authPath, error: String(e) });
|
||||
// Continue to next path on parse error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn("AuthStorage no auth file found", { checkedPaths: pathsToCheck });
|
||||
this.data = {};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -132,7 +132,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
||||
if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
|
||||
const randomId = crypto.getRandomValues(new Uint8Array(8));
|
||||
const id = Array.from(randomId, (b) => b.toString(16).padStart(2, "0")).join("");
|
||||
tempFilePath = join(tmpdir(), `pi-bash-${id}.log`);
|
||||
tempFilePath = join(tmpdir(), `omp-bash-${id}.log`);
|
||||
tempFileStream = createWriteStream(tempFilePath);
|
||||
// Write already-buffered chunks to temp file
|
||||
for (const chunk of outputChunks) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as typebox from "@sinclair/typebox";
|
||||
import { CONFIG_DIR_NAME, getAgentDir } from "../../config";
|
||||
import { getAgentDir, getConfigDirPaths } from "../../config";
|
||||
import * as piCodingAgent from "../../index";
|
||||
import { execCommand } from "../exec";
|
||||
import { createReviewCommand } from "./bundled/review";
|
||||
@@ -105,7 +105,7 @@ export interface DiscoverCustomCommandsResult {
|
||||
/**
|
||||
* Discover custom command modules from standard locations:
|
||||
* - agentDir/commands/[name]/index.ts (user)
|
||||
* - cwd/.pi/commands/[name]/index.ts (project)
|
||||
* - cwd/.omp/commands/[name]/index.ts (project)
|
||||
*/
|
||||
export function discoverCustomCommands(options: DiscoverCustomCommandsOptions = {}): DiscoverCustomCommandsResult {
|
||||
const cwd = options.cwd ?? process.cwd();
|
||||
@@ -128,9 +128,10 @@ export function discoverCustomCommands(options: DiscoverCustomCommandsOptions =
|
||||
const userCommandsDir = path.join(agentDir, "commands");
|
||||
addPaths(discoverCommandsInDir(userCommandsDir), "user");
|
||||
|
||||
// 2. Project commands: cwd/.pi/commands/
|
||||
const projectCommandsDir = path.join(cwd, CONFIG_DIR_NAME, "commands");
|
||||
addPaths(discoverCommandsInDir(projectCommandsDir), "project");
|
||||
// 2. Project commands: cwd/{.omp,.pi,.claude}/commands/
|
||||
for (const projectCommandsDir of getConfigDirPaths("commands", { user: false, cwd })) {
|
||||
addPaths(discoverCommandsInDir(projectCommandsDir), "project");
|
||||
}
|
||||
|
||||
return { paths };
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import * as typebox from "@sinclair/typebox";
|
||||
import { getAgentDir } from "../../config";
|
||||
import { getAgentDir, getConfigDirPaths } from "../../config";
|
||||
import * as piCodingAgent from "../../index";
|
||||
import { theme } from "../../modes/interactive/theme/theme";
|
||||
import type { ExecOptions } from "../exec";
|
||||
@@ -202,8 +202,8 @@ function discoverToolsInDir(dir: string): string[] {
|
||||
/**
|
||||
* Discover and load tools from standard locations:
|
||||
* 1. agentDir/tools/*.ts (global)
|
||||
* 2. cwd/.pi/tools/*.ts (project-local)
|
||||
* 3. Installed plugins (~/.pi/plugins/node_modules/*)
|
||||
* 2. cwd/{.omp,.pi,.claude}/tools/*.ts (project-local, with fallbacks)
|
||||
* 3. Installed plugins (~/.omp/plugins/node_modules/*)
|
||||
*
|
||||
* Plus any explicitly configured paths from settings or CLI.
|
||||
*
|
||||
@@ -236,11 +236,12 @@ export async function discoverAndLoadCustomTools(
|
||||
const globalToolsDir = path.join(agentDir, "tools");
|
||||
addPaths(discoverToolsInDir(globalToolsDir));
|
||||
|
||||
// 2. Project-local tools: cwd/.pi/tools/
|
||||
const localToolsDir = path.join(cwd, ".pi", "tools");
|
||||
addPaths(discoverToolsInDir(localToolsDir));
|
||||
// 2. Project-local tools: cwd/{.omp,.pi,.claude}/tools/
|
||||
for (const localToolsDir of getConfigDirPaths("tools", { user: false, cwd })) {
|
||||
addPaths(discoverToolsInDir(localToolsDir));
|
||||
}
|
||||
|
||||
// 3. Plugin tools: ~/.pi/plugins/node_modules/*/
|
||||
// 3. Plugin tools: ~/.omp/plugins/node_modules/*/
|
||||
addPaths(getAllPluginToolPaths(cwd));
|
||||
|
||||
// 4. Explicitly configured paths (can override/add)
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import * as typebox from "@sinclair/typebox";
|
||||
import { getAgentDir } from "../../config";
|
||||
import { getAgentDir, getConfigDirPaths } from "../../config";
|
||||
import * as piCodingAgent from "../../index";
|
||||
import { logger } from "../logger";
|
||||
import type { HookMessage } from "../messages";
|
||||
@@ -275,8 +275,8 @@ function discoverHooksInDir(dir: string): string[] {
|
||||
/**
|
||||
* Discover and load hooks from standard locations:
|
||||
* 1. agentDir/hooks/*.ts (global)
|
||||
* 2. cwd/.pi/hooks/*.ts (project-local)
|
||||
* 3. Installed plugins (~/.pi/plugins/node_modules/*)
|
||||
* 2. cwd/{.omp,.pi,.claude}/hooks/*.ts (project-local, with fallbacks)
|
||||
* 3. Installed plugins (~/.omp/plugins/node_modules/*)
|
||||
*
|
||||
* Plus any explicitly configured paths from settings.
|
||||
*/
|
||||
@@ -303,11 +303,12 @@ export async function discoverAndLoadHooks(
|
||||
const globalHooksDir = path.join(agentDir, "hooks");
|
||||
addPaths(discoverHooksInDir(globalHooksDir));
|
||||
|
||||
// 2. Project-local hooks: cwd/.pi/hooks/
|
||||
const localHooksDir = path.join(cwd, ".pi", "hooks");
|
||||
addPaths(discoverHooksInDir(localHooksDir));
|
||||
// 2. Project-local hooks: cwd/{.omp,.pi,.claude}/hooks/
|
||||
for (const localHooksDir of getConfigDirPaths("hooks", { user: false, cwd })) {
|
||||
addPaths(discoverHooksInDir(localHooksDir));
|
||||
}
|
||||
|
||||
// 3. Plugin hooks: ~/.pi/plugins/node_modules/*/
|
||||
// 3. Plugin hooks: ~/.omp/plugins/node_modules/*/
|
||||
addPaths(getAllPluginHookPaths(cwd));
|
||||
|
||||
// 4. Explicitly configured paths (can override/add)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Centralized file logger for pi.
|
||||
* Centralized file logger for omp.
|
||||
*
|
||||
* Logs to ~/.pi/logs/ with size-based rotation, supporting concurrent pi instances.
|
||||
* Logs to ~/.omp/logs/ with size-based rotation, supporting concurrent omp instances.
|
||||
* Each log entry includes process.pid for traceability.
|
||||
*/
|
||||
|
||||
@@ -12,7 +12,7 @@ import winston from "winston";
|
||||
import DailyRotateFile from "winston-daily-rotate-file";
|
||||
import { CONFIG_DIR_NAME } from "../config";
|
||||
|
||||
/** Get the logs directory (~/.pi/logs/) */
|
||||
/** Get the logs directory (~/.omp/logs/) */
|
||||
function getLogsDir(): string {
|
||||
return join(homedir(), CONFIG_DIR_NAME, "logs");
|
||||
}
|
||||
@@ -49,7 +49,7 @@ const logFormat = winston.format.combine(
|
||||
/** Size-based rotating file transport */
|
||||
const fileTransport = new DailyRotateFile({
|
||||
dirname: ensureLogsDir(),
|
||||
filename: "pi.%DATE%.log",
|
||||
filename: "omp.%DATE%.log",
|
||||
datePattern: "YYYY-MM-DD",
|
||||
maxSize: "10m",
|
||||
maxFiles: 5,
|
||||
@@ -73,10 +73,10 @@ export interface Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Centralized logger for pi.
|
||||
* Centralized logger for omp.
|
||||
*
|
||||
* Logs to ~/.pi/logs/pi.YYYY-MM-DD.log with size-based rotation.
|
||||
* Safe for concurrent access from multiple pi instances.
|
||||
* Logs to ~/.omp/logs/omp.YYYY-MM-DD.log with size-based rotation.
|
||||
* Safe for concurrent access from multiple omp instances.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
|
||||
@@ -30,7 +30,7 @@ const CONNECTION_TIMEOUT_MS = 30_000;
|
||||
|
||||
/** Client info sent during initialization */
|
||||
const CLIENT_INFO = {
|
||||
name: "pi-coding-agent",
|
||||
name: "omp-coding-agent",
|
||||
version: "1.0.0",
|
||||
};
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { getConfigDirPaths } from "../../config";
|
||||
import type { MCPConfigFile, MCPServerConfig } from "./types";
|
||||
|
||||
/** Environment variable expansion pattern: ${VAR} or ${VAR:-default} */
|
||||
@@ -80,17 +80,22 @@ export function loadMCPConfigFile(filePath: string, extraEnv?: Record<string, st
|
||||
* Configuration locations (in order of priority, later overrides earlier).
|
||||
*/
|
||||
export interface MCPConfigLocations {
|
||||
/** User-level config: ~/.pi/mcp.json or ~/.claude.json */
|
||||
user?: string;
|
||||
/** Project-level config: <cwd>/.mcp.json */
|
||||
/** User-level config paths: ~/.omp/mcp.json, ~/.pi/mcp.json, ~/.claude/mcp.json */
|
||||
userPaths: string[];
|
||||
/** Project-level config: <cwd>/mcp.json or <cwd>/.mcp.json */
|
||||
project?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get standard MCP config file paths.
|
||||
* User-level checks .omp, .pi, .claude directories for fallback support.
|
||||
*/
|
||||
export function getMCPConfigPaths(cwd: string): MCPConfigLocations {
|
||||
const home = homedir();
|
||||
// User-level: check all config dirs (not under agent/)
|
||||
// mcp.json lives at ~/.omp/mcp.json, not ~/.omp/agent/mcp.json
|
||||
const userPaths = getConfigDirPaths("mcp.json", { project: false }).map((p) =>
|
||||
p.replace("/agent/mcp.json", "/mcp.json"),
|
||||
);
|
||||
|
||||
// Project-level: check both mcp.json and .mcp.json (prefer mcp.json if both exist)
|
||||
const mcpJson = join(cwd, "mcp.json");
|
||||
@@ -98,9 +103,7 @@ export function getMCPConfigPaths(cwd: string): MCPConfigLocations {
|
||||
const projectPath = existsSync(mcpJson) ? mcpJson : dotMcpJson;
|
||||
|
||||
return {
|
||||
// User-level: ~/.pi/mcp.json (our standard)
|
||||
user: join(home, ".pi", "mcp.json"),
|
||||
// Project-level: mcp.json or .mcp.json at project root
|
||||
userPaths,
|
||||
project: projectPath,
|
||||
};
|
||||
}
|
||||
@@ -161,7 +164,13 @@ export function loadAllMCPConfigs(
|
||||
|
||||
const paths = getMCPConfigPaths(cwd);
|
||||
|
||||
const userConfig = paths.user ? loadMCPConfigFile(paths.user, opts.extraEnv) : null;
|
||||
// Load first existing user config (fallback support)
|
||||
let userConfig: MCPConfigFile | null = null;
|
||||
for (const userPath of paths.userPaths) {
|
||||
userConfig = loadMCPConfigFile(userPath, opts.extraEnv);
|
||||
if (userConfig) break;
|
||||
}
|
||||
|
||||
const projectConfig = enableProjectConfig && paths.project ? loadMCPConfigFile(paths.project, opts.extraEnv) : null;
|
||||
|
||||
let configs = mergeMCPConfigs(userConfig, projectConfig);
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import AjvModule from "ajv";
|
||||
import type { AuthStorage } from "./auth-storage";
|
||||
import { logger } from "./logger";
|
||||
|
||||
const Ajv = (AjvModule as any).default || AjvModule;
|
||||
|
||||
@@ -92,9 +93,15 @@ export class ModelRegistry {
|
||||
private customProviderApiKeys: Map<string, string> = new Map();
|
||||
private loadError: string | undefined = undefined;
|
||||
|
||||
/**
|
||||
* @param authStorage - Auth storage for API key resolution
|
||||
* @param modelsJsonPath - Primary path for models.json
|
||||
* @param fallbackPaths - Additional paths to check (legacy support)
|
||||
*/
|
||||
constructor(
|
||||
readonly authStorage: AuthStorage,
|
||||
private modelsJsonPath: string | undefined = undefined,
|
||||
private fallbackPaths: string[] = [],
|
||||
) {
|
||||
// Set up fallback resolver for custom provider API keys
|
||||
this.authStorage.setFallbackResolver((provider) => {
|
||||
@@ -133,15 +140,25 @@ export class ModelRegistry {
|
||||
builtInModels.push(...(providerModels as Model<Api>[]));
|
||||
}
|
||||
|
||||
// Load custom models from models.json (if path provided)
|
||||
// Load custom models from models.json (check primary path, then fallbacks)
|
||||
let customModels: Model<Api>[] = [];
|
||||
if (this.modelsJsonPath) {
|
||||
const result = this.loadCustomModels(this.modelsJsonPath);
|
||||
if (result.error) {
|
||||
this.loadError = result.error;
|
||||
// Keep built-in models even if custom models failed to load
|
||||
} else {
|
||||
customModels = result.models;
|
||||
const pathsToCheck = this.modelsJsonPath ? [this.modelsJsonPath, ...this.fallbackPaths] : this.fallbackPaths;
|
||||
|
||||
if (pathsToCheck.length > 0) {
|
||||
logger.debug("ModelRegistry.loadModels checking paths", { paths: pathsToCheck });
|
||||
}
|
||||
|
||||
for (const modelsPath of pathsToCheck) {
|
||||
if (existsSync(modelsPath)) {
|
||||
logger.debug("ModelRegistry.loadModels loading", { path: modelsPath });
|
||||
const result = this.loadCustomModels(modelsPath);
|
||||
if (result.error) {
|
||||
this.loadError = result.error;
|
||||
// Keep built-in models even if custom models failed to load
|
||||
} else {
|
||||
customModels = result.models;
|
||||
}
|
||||
break; // Use first existing file
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export async function installPlugin(packageName: string): Promise<InstalledPlugi
|
||||
// Initialize package.json if it doesn't exist
|
||||
const pkgJsonPath = join(PLUGINS_DIR, "package.json");
|
||||
if (!(await Bun.file(pkgJsonPath).exists())) {
|
||||
await Bun.write(pkgJsonPath, JSON.stringify({ name: "pi-plugins", private: true, dependencies: {} }, null, 2));
|
||||
await Bun.write(pkgJsonPath, JSON.stringify({ name: "omp-plugins", private: true, dependencies: {} }, null, 2));
|
||||
}
|
||||
|
||||
// Run npm install in plugins directory
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { getPluginsLockfile, getPluginsNodeModules, getPluginsPackageJson, getProjectPluginOverrides } from "./paths";
|
||||
import {
|
||||
getAllProjectPluginOverridePaths,
|
||||
getPluginsLockfile,
|
||||
getPluginsNodeModules,
|
||||
getPluginsPackageJson,
|
||||
} from "./paths";
|
||||
import type { InstalledPlugin, PluginManifest, PluginRuntimeConfig, ProjectPluginOverrides } from "./types";
|
||||
|
||||
// =============================================================================
|
||||
@@ -30,18 +35,19 @@ function loadRuntimeConfig(): PluginRuntimeConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load project-local plugin overrides.
|
||||
* Load project-local plugin overrides (checks .omp and .pi directories).
|
||||
*/
|
||||
function loadProjectOverrides(cwd: string): ProjectPluginOverrides {
|
||||
const overridesPath = getProjectPluginOverrides(cwd);
|
||||
if (!existsSync(overridesPath)) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
return JSON.parse(readFileSync(overridesPath, "utf-8"));
|
||||
} catch {
|
||||
return {};
|
||||
for (const overridesPath of getAllProjectPluginOverridePaths(cwd)) {
|
||||
if (existsSync(overridesPath)) {
|
||||
try {
|
||||
return JSON.parse(readFileSync(overridesPath, "utf-8"));
|
||||
} catch {
|
||||
// Continue to next path
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -79,7 +85,7 @@ export function getEnabledPlugins(cwd: string): InstalledPlugin[] {
|
||||
const manifest: PluginManifest | undefined = pluginPkg.omp || pluginPkg.pi;
|
||||
|
||||
if (!manifest) {
|
||||
// Not a pi plugin, skip
|
||||
// Not an omp plugin, skip
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ export class PluginManager {
|
||||
pkgJsonPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "pi-plugins",
|
||||
name: "omp-plugins",
|
||||
private: true,
|
||||
dependencies: {},
|
||||
},
|
||||
@@ -516,7 +516,7 @@ export class PluginManager {
|
||||
status: hasManifest ? "ok" : "warning",
|
||||
message: hasManifest
|
||||
? `v${pluginPkg.version}${pluginPkg.description ? ` - ${pluginPkg.description}` : ""}`
|
||||
: `v${pluginPkg.version} - No omp/pi manifest (not a pi plugin)`,
|
||||
: `v${pluginPkg.version} - No omp/pi manifest (not an omp plugin)`,
|
||||
});
|
||||
|
||||
// Check tools path exists if specified
|
||||
|
||||
@@ -1,32 +1,37 @@
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { CONFIG_DIR_NAME } from "../../config";
|
||||
import { CONFIG_DIR_NAME, getConfigDirPaths } from "../../config";
|
||||
|
||||
// =============================================================================
|
||||
// Plugin Directory Paths
|
||||
// =============================================================================
|
||||
|
||||
/** Root plugin directory: ~/.pi/plugins (not under agent/) */
|
||||
/** Root plugin directory: ~/.omp/plugins (not under agent/) */
|
||||
export function getPluginsDir(): string {
|
||||
return join(homedir(), CONFIG_DIR_NAME, "plugins");
|
||||
}
|
||||
|
||||
/** Where npm installs packages: ~/.pi/plugins/node_modules */
|
||||
/** Where npm installs packages: ~/.omp/plugins/node_modules */
|
||||
export function getPluginsNodeModules(): string {
|
||||
return join(getPluginsDir(), "node_modules");
|
||||
}
|
||||
|
||||
/** Plugin manifest: ~/.pi/plugins/package.json */
|
||||
/** Plugin manifest: ~/.omp/plugins/package.json */
|
||||
export function getPluginsPackageJson(): string {
|
||||
return join(getPluginsDir(), "package.json");
|
||||
}
|
||||
|
||||
/** Plugin lock file: ~/.pi/plugins/pi-plugins.lock.json */
|
||||
/** Plugin lock file: ~/.omp/plugins/omp-plugins.lock.json */
|
||||
export function getPluginsLockfile(): string {
|
||||
return join(getPluginsDir(), "pi-plugins.lock.json");
|
||||
return join(getPluginsDir(), "omp-plugins.lock.json");
|
||||
}
|
||||
|
||||
/** Project-local plugin overrides: .pi/plugin-overrides.json */
|
||||
/** Project-local plugin overrides: .omp/plugin-overrides.json (primary) */
|
||||
export function getProjectPluginOverrides(cwd: string): string {
|
||||
return 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 });
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ export interface InstalledPlugin {
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Runtime Config Types (stored in pi-plugins.lock.json)
|
||||
// Runtime Config Types (stored in omp-plugins.lock.json)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
@@ -131,7 +131,7 @@ export interface PluginRuntimeState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime configuration persisted to pi-plugins.lock.json.
|
||||
* Runtime configuration persisted to omp-plugins.lock.json.
|
||||
* Tracks plugin states and settings across sessions.
|
||||
*/
|
||||
export interface PluginRuntimeConfig {
|
||||
@@ -146,7 +146,7 @@ export interface PluginRuntimeConfig {
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Project-local plugin overrides (stored in .pi/plugin-overrides.json).
|
||||
* Project-local plugin overrides (stored in .omp/plugin-overrides.json).
|
||||
* Allows per-project plugin configuration without modifying global state.
|
||||
*/
|
||||
export interface ProjectPluginOverrides {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
import { join } from "node:path";
|
||||
import { Agent, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Model } from "@oh-my-pi/pi-ai";
|
||||
import { getAgentDir } from "../config";
|
||||
import { getAgentDir, getConfigDirPaths } from "../config";
|
||||
import { AgentSession } from "./agent-session";
|
||||
import { AuthStorage } from "./auth-storage";
|
||||
import {
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
import type { CustomTool } from "./custom-tools/types";
|
||||
import { discoverAndLoadHooks, HookRunner, type LoadedHook, wrapToolsWithHooks } from "./hooks/index";
|
||||
import type { HookFactory } from "./hooks/types";
|
||||
import { logger } from "./logger";
|
||||
import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp/index";
|
||||
import { convertToLlm } from "./messages";
|
||||
import { ModelRegistry } from "./model-registry";
|
||||
@@ -91,7 +92,7 @@ import {
|
||||
export interface CreateAgentSessionOptions {
|
||||
/** Working directory for project-local discovery. Default: process.cwd() */
|
||||
cwd?: string;
|
||||
/** Global config directory. Default: ~/.pi/agent */
|
||||
/** Global config directory. Default: ~/.omp/agent */
|
||||
agentDir?: string;
|
||||
|
||||
/** Auth storage for credentials. Default: discoverAuthStorage(agentDir) */
|
||||
@@ -125,7 +126,7 @@ export interface CreateAgentSessionOptions {
|
||||
skills?: Skill[];
|
||||
/** Context files (AGENTS.md content). Default: discovered walking up from cwd */
|
||||
contextFiles?: Array<{ path: string; content: string }>;
|
||||
/** Slash commands. Default: discovered from cwd/.pi/commands/ + agentDir/commands/ */
|
||||
/** Slash commands. Default: discovered from cwd/.omp/commands/ + agentDir/commands/ */
|
||||
slashCommands?: FileSlashCommand[];
|
||||
|
||||
/** Enable MCP server discovery from .mcp.json files. Default: true */
|
||||
@@ -202,17 +203,33 @@ function getDefaultAgentDir(): string {
|
||||
// Discovery Functions
|
||||
|
||||
/**
|
||||
* Create an AuthStorage instance for the given agent directory.
|
||||
* Create an AuthStorage instance with fallback support.
|
||||
* Reads from primary path first, then falls back to legacy paths (.pi, .claude).
|
||||
*/
|
||||
export function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): AuthStorage {
|
||||
return new AuthStorage(join(agentDir, "auth.json"));
|
||||
const primaryPath = join(agentDir, "auth.json");
|
||||
// Get all auth.json paths (user-level only), excluding the primary
|
||||
const allPaths = getConfigDirPaths("auth.json", { project: false });
|
||||
const fallbackPaths = allPaths.filter((p) => p !== primaryPath);
|
||||
|
||||
logger.debug("discoverAuthStorage", { agentDir, primaryPath, allPaths, fallbackPaths });
|
||||
|
||||
return new AuthStorage(primaryPath, fallbackPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ModelRegistry for the given agent directory.
|
||||
* Create a ModelRegistry with fallback support.
|
||||
* Reads from primary path first, then falls back to legacy paths (.pi, .claude).
|
||||
*/
|
||||
export function discoverModels(authStorage: AuthStorage, agentDir: string = getDefaultAgentDir()): ModelRegistry {
|
||||
return new ModelRegistry(authStorage, join(agentDir, "models.json"));
|
||||
const primaryPath = join(agentDir, "models.json");
|
||||
// Get all models.json paths (user-level only), excluding the primary
|
||||
const allPaths = getConfigDirPaths("models.json", { project: false });
|
||||
const fallbackPaths = allPaths.filter((p) => p !== primaryPath);
|
||||
|
||||
logger.debug("discoverModels", { primaryPath, fallbackPaths });
|
||||
|
||||
return new ModelRegistry(authStorage, primaryPath, fallbackPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,7 +365,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
||||
// Settings
|
||||
|
||||
/**
|
||||
* Load settings from agentDir/settings.json merged with cwd/.pi/settings.json.
|
||||
* Load settings from agentDir/settings.json merged with cwd/.omp/settings.json.
|
||||
*/
|
||||
export function loadSettings(cwd?: string, agentDir?: string): Settings {
|
||||
const manager = SettingsManager.create(cwd ?? process.cwd(), agentDir ?? getDefaultAgentDir());
|
||||
|
||||
@@ -394,7 +394,7 @@ export function buildSessionContext(
|
||||
|
||||
/**
|
||||
* Compute the default session directory for a cwd.
|
||||
* Encodes cwd into a safe directory name under ~/.pi/agent/sessions/.
|
||||
* Encodes cwd into a safe directory name under ~/.omp/agent/sessions/.
|
||||
*/
|
||||
function getDefaultSessionDir(cwd: string): string {
|
||||
const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
||||
@@ -1101,7 +1101,7 @@ export class SessionManager {
|
||||
/**
|
||||
* Create a new session.
|
||||
* @param cwd Working directory (stored in session header)
|
||||
* @param sessionDir Optional session directory. If omitted, uses default (~/.pi/agent/sessions/<encoded-cwd>/).
|
||||
* @param sessionDir Optional session directory. If omitted, uses default (~/.omp/agent/sessions/<encoded-cwd>/).
|
||||
*/
|
||||
static create(cwd: string, sessionDir?: string): SessionManager {
|
||||
const dir = sessionDir ?? getDefaultSessionDir(cwd);
|
||||
@@ -1126,7 +1126,7 @@ export class SessionManager {
|
||||
/**
|
||||
* Continue the most recent session, or create new if none.
|
||||
* @param cwd Working directory
|
||||
* @param sessionDir Optional session directory. If omitted, uses default (~/.pi/agent/sessions/<encoded-cwd>/).
|
||||
* @param sessionDir Optional session directory. If omitted, uses default (~/.omp/agent/sessions/<encoded-cwd>/).
|
||||
*/
|
||||
static continueRecent(cwd: string, sessionDir?: string): SessionManager {
|
||||
const dir = sessionDir ?? getDefaultSessionDir(cwd);
|
||||
@@ -1145,7 +1145,7 @@ export class SessionManager {
|
||||
/**
|
||||
* List all sessions.
|
||||
* @param cwd Working directory (used to compute default session directory)
|
||||
* @param sessionDir Optional session directory. If omitted, uses default (~/.pi/agent/sessions/<encoded-cwd>/).
|
||||
* @param sessionDir Optional session directory. If omitted, uses default (~/.omp/agent/sessions/<encoded-cwd>/).
|
||||
*/
|
||||
static list(cwd: string, sessionDir?: string): SessionInfo[] {
|
||||
const dir = sessionDir ?? getDefaultSessionDir(cwd);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { CONFIG_DIR_NAME, getAgentDir } from "../config";
|
||||
import { getAgentDir, readConfigFile } from "../config";
|
||||
|
||||
export interface CompactionSettings {
|
||||
enabled?: boolean; // default: true
|
||||
@@ -126,19 +126,14 @@ function deepMergeSettings(base: Settings, overrides: Settings): Settings {
|
||||
|
||||
export class SettingsManager {
|
||||
private settingsPath: string | null;
|
||||
private projectSettingsPath: string | null;
|
||||
private cwd: string | null;
|
||||
private globalSettings: Settings;
|
||||
private settings: Settings;
|
||||
private persist: boolean;
|
||||
|
||||
private constructor(
|
||||
settingsPath: string | null,
|
||||
projectSettingsPath: string | null,
|
||||
initialSettings: Settings,
|
||||
persist: boolean,
|
||||
) {
|
||||
private constructor(settingsPath: string | null, cwd: string | null, initialSettings: Settings, persist: boolean) {
|
||||
this.settingsPath = settingsPath;
|
||||
this.projectSettingsPath = projectSettingsPath;
|
||||
this.cwd = cwd;
|
||||
this.persist = persist;
|
||||
this.globalSettings = initialSettings;
|
||||
const projectSettings = this.loadProjectSettings();
|
||||
@@ -148,9 +143,8 @@ export class SettingsManager {
|
||||
/** Create a SettingsManager that loads from files */
|
||||
static create(cwd: string = process.cwd(), agentDir: string = getAgentDir()): SettingsManager {
|
||||
const settingsPath = join(agentDir, "settings.json");
|
||||
const projectSettingsPath = join(cwd, CONFIG_DIR_NAME, "settings.json");
|
||||
const globalSettings = SettingsManager.loadFromFile(settingsPath);
|
||||
return new SettingsManager(settingsPath, projectSettingsPath, globalSettings, true);
|
||||
return new SettingsManager(settingsPath, cwd, globalSettings, true);
|
||||
}
|
||||
|
||||
/** Create an in-memory SettingsManager (no file I/O) */
|
||||
@@ -172,17 +166,11 @@ export class SettingsManager {
|
||||
}
|
||||
|
||||
private loadProjectSettings(): Settings {
|
||||
if (!this.projectSettingsPath || !existsSync(this.projectSettingsPath)) {
|
||||
return {};
|
||||
}
|
||||
if (!this.cwd) return {};
|
||||
|
||||
try {
|
||||
const content = readFileSync(this.projectSettingsPath, "utf-8");
|
||||
return JSON.parse(content);
|
||||
} catch (error) {
|
||||
console.error(`Warning: Could not read project settings file: ${error}`);
|
||||
return {};
|
||||
}
|
||||
// Use readConfigFile to search .omp, .pi, .claude in priority order
|
||||
const result = readConfigFile<Settings>("settings.json", { user: false, cwd: this.cwd });
|
||||
return result?.content ?? {};
|
||||
}
|
||||
|
||||
/** Apply additional overrides on top of current settings */
|
||||
|
||||
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "n
|
||||
import { homedir } from "node:os";
|
||||
import { basename, dirname, join, resolve } from "node:path";
|
||||
import { minimatch } from "minimatch";
|
||||
import { CONFIG_DIR_NAME, getAgentDir } from "../config";
|
||||
import { getAgentDir, getConfigDirPaths } from "../config";
|
||||
import type { SkillsSettings } from "./settings-manager";
|
||||
|
||||
/**
|
||||
@@ -331,7 +331,7 @@ function escapeXml(str: string): string {
|
||||
export interface LoadSkillsOptions extends SkillsSettings {
|
||||
/** Working directory for project-local skills. Default: process.cwd() */
|
||||
cwd?: string;
|
||||
/** Agent config directory for global skills. Default: ~/.pi/agent */
|
||||
/** Agent config directory for global skills. Default: ~/.omp/agent */
|
||||
agentDir?: string;
|
||||
}
|
||||
|
||||
@@ -424,7 +424,9 @@ export function loadSkills(options: LoadSkillsOptions = {}): LoadSkillsResult {
|
||||
addSkills(loadSkillsFromDirInternal(join(resolvedAgentDir, "skills"), "user", "recursive"));
|
||||
}
|
||||
if (enablePiProject) {
|
||||
addSkills(loadSkillsFromDirInternal(resolve(cwd, CONFIG_DIR_NAME, "skills"), "project", "recursive"));
|
||||
for (const dir of getConfigDirPaths("skills", { user: false, cwd, existingOnly: true })) {
|
||||
addSkills(loadSkillsFromDirInternal(dir, "project", "recursive"));
|
||||
}
|
||||
}
|
||||
for (const customDir of customDirectories) {
|
||||
addSkills(loadSkillsFromDirInternal(customDir.replace(/^~(?=$|[\\/])/, homedir()), "custom", "recursive"));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { CONFIG_DIR_NAME, getCommandsDir } from "../config";
|
||||
import { getCommandsDir, getConfigDirPaths } from "../config";
|
||||
import { logger } from "./logger";
|
||||
|
||||
/**
|
||||
@@ -190,8 +190,8 @@ export interface LoadSlashCommandsOptions {
|
||||
* 1. Builtin: package commands/
|
||||
* 2. Claude user: ~/.claude/commands/
|
||||
* 3. Claude project: .claude/commands/
|
||||
* 4. Pi user: agentDir/commands/
|
||||
* 5. Pi project: cwd/{CONFIG_DIR_NAME}/commands/
|
||||
* 4. OMP user: agentDir/commands/
|
||||
* 5. OMP project: cwd/.omp/commands/ (and legacy .pi/commands/)
|
||||
*
|
||||
* First occurrence wins (earlier sources have priority).
|
||||
*/
|
||||
@@ -231,13 +231,14 @@ export function loadSlashCommands(options: LoadSlashCommandsOptions = {}): FileS
|
||||
addCommands(loadCommandsFromDir(claudeProjectDir, "claude-project"));
|
||||
}
|
||||
|
||||
// 4. Pi user commands (agentDir/commands/)
|
||||
// 4. OMP user commands (agentDir/commands/)
|
||||
const globalCommandsDir = options.agentDir ? join(options.agentDir, "commands") : resolvedAgentDir;
|
||||
addCommands(loadCommandsFromDir(globalCommandsDir, "user"));
|
||||
|
||||
// 5. Pi project commands (cwd/{CONFIG_DIR_NAME}/commands/)
|
||||
const projectCommandsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, "commands");
|
||||
addCommands(loadCommandsFromDir(projectCommandsDir, "project"));
|
||||
// 5. OMP project commands (cwd/.omp/commands/ and legacy .pi/commands/)
|
||||
for (const dir of getConfigDirPaths("commands", { user: false, cwd: resolvedCwd })) {
|
||||
addCommands(loadCommandsFromDir(dir, "project"));
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Central timing instrumentation for startup profiling.
|
||||
* Enable with PI_TIMING=1 environment variable.
|
||||
* Enable with OMP_TIMING=1 or PI_TIMING=1 environment variable.
|
||||
*/
|
||||
|
||||
const ENABLED = process.env.PI_TIMING === "1";
|
||||
const ENABLED = process.env.OMP_TIMING === "1" || process.env.PI_TIMING === "1";
|
||||
const timings: Array<{ label: string; ms: number }> = [];
|
||||
let lastTime = Date.now();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { extname, join } from "node:path";
|
||||
import { getConfigDirPaths } from "../../../config.js";
|
||||
import type { ServerConfig } from "./types";
|
||||
|
||||
export interface LspConfig {
|
||||
@@ -94,7 +95,7 @@ export const SERVERS: Record<string, ServerConfig> = {
|
||||
fileTypes: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
||||
rootMarkers: ["package.json", "tsconfig.json", "jsconfig.json"],
|
||||
initOptions: {
|
||||
hostInfo: "pi-coding-agent",
|
||||
hostInfo: "omp-coding-agent",
|
||||
preferences: {
|
||||
includeInlayParameterNameHints: "all",
|
||||
includeInlayVariableTypeHints: true,
|
||||
@@ -673,30 +674,50 @@ export function resolveCommand(command: string, cwd: string): string | null {
|
||||
|
||||
/**
|
||||
* Configuration file search paths (in priority order).
|
||||
* Supports both visible and hidden variants, and both .pi subdirectory and root.
|
||||
* Supports both visible and hidden variants at each config location.
|
||||
*/
|
||||
function getConfigPaths(cwd: string): string[] {
|
||||
return [
|
||||
// Project-level configs (highest priority)
|
||||
join(cwd, "lsp.json"),
|
||||
join(cwd, ".lsp.json"),
|
||||
join(cwd, ".pi", "lsp.json"),
|
||||
join(cwd, ".pi", ".lsp.json"),
|
||||
// User-level configs (fallback)
|
||||
join(homedir(), ".pi", "lsp.json"),
|
||||
join(homedir(), ".pi", ".lsp.json"),
|
||||
join(homedir(), "lsp.json"),
|
||||
join(homedir(), ".lsp.json"),
|
||||
];
|
||||
const filenames = ["lsp.json", ".lsp.json"];
|
||||
const paths: string[] = [];
|
||||
|
||||
// Project root files (highest priority)
|
||||
for (const filename of filenames) {
|
||||
paths.push(join(cwd, filename));
|
||||
}
|
||||
|
||||
// Project config directories (.omp/, .pi/, .claude/)
|
||||
const projectDirs = getConfigDirPaths("", { user: false, project: true, cwd });
|
||||
for (const dir of projectDirs) {
|
||||
for (const filename of filenames) {
|
||||
paths.push(join(dir, filename));
|
||||
}
|
||||
}
|
||||
|
||||
// User config directories (~/.omp/agent/, ~/.pi/agent/, ~/.claude/)
|
||||
const userDirs = getConfigDirPaths("", { user: true, project: false });
|
||||
for (const dir of userDirs) {
|
||||
for (const filename of filenames) {
|
||||
paths.push(join(dir, filename));
|
||||
}
|
||||
}
|
||||
|
||||
// User home root files (lowest priority fallback)
|
||||
for (const filename of filenames) {
|
||||
paths.push(join(homedir(), filename));
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load LSP configuration.
|
||||
*
|
||||
* Priority:
|
||||
* 1. Project-level config: lsp.json, .lsp.json, .pi/lsp.json, .pi/.lsp.json
|
||||
* 2. User-level config: ~/.pi/lsp.json, ~/.pi/.lsp.json, ~/lsp.json, ~/.lsp.json
|
||||
* 3. Auto-detect from project markers + available binaries
|
||||
* 1. Project root: lsp.json, .lsp.json
|
||||
* 2. Project config dirs: .omp/lsp.json, .pi/lsp.json, .claude/lsp.json (+ hidden variants)
|
||||
* 3. User config dirs: ~/.omp/agent/lsp.json, ~/.pi/agent/lsp.json, ~/.claude/lsp.json (+ hidden variants)
|
||||
* 4. User home root: ~/lsp.json, ~/.lsp.json
|
||||
* 5. Auto-detect from project markers + available binaries
|
||||
*
|
||||
* Config file format:
|
||||
* ```json
|
||||
|
||||
@@ -84,7 +84,7 @@ export async function writeArtifacts(
|
||||
*/
|
||||
export function createTempArtifactsDir(runId?: string): string {
|
||||
const id = runId || `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
const dir = path.join(os.tmpdir(), `pi-task-${id}`);
|
||||
const dir = path.join(os.tmpdir(), `omp-task-${id}`);
|
||||
ensureArtifactsDir(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { findAllNearestProjectConfigDirs, getConfigDirs } from "../../../config";
|
||||
|
||||
// Embed command markdown files at build time
|
||||
import architectPlanMd from "./bundled-commands/architect-plan.md" with { type: "text" };
|
||||
@@ -81,7 +81,7 @@ function loadCommandsFromDir(dir: string, source: "user" | "project"): WorkflowC
|
||||
for (const entry of entries) {
|
||||
if (!entry.name.endsWith(".md")) continue;
|
||||
|
||||
const filePath = path.join(dir, entry.name);
|
||||
const filePath = path.resolve(dir, entry.name);
|
||||
|
||||
try {
|
||||
if (!fs.statSync(filePath).isFile()) continue;
|
||||
@@ -113,32 +113,6 @@ function loadCommandsFromDir(dir: string, source: "user" | "project"): WorkflowC
|
||||
return commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if path is a directory.
|
||||
*/
|
||||
function isDirectory(p: string): boolean {
|
||||
try {
|
||||
return fs.statSync(p).isDirectory();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find nearest directory by walking up from cwd.
|
||||
*/
|
||||
function findNearestDir(cwd: string, relPath: string): string | null {
|
||||
let currentDir = cwd;
|
||||
while (true) {
|
||||
const candidate = path.join(currentDir, relPath);
|
||||
if (isDirectory(candidate)) return candidate;
|
||||
|
||||
const parentDir = path.dirname(currentDir);
|
||||
if (parentDir === currentDir) return null;
|
||||
currentDir = parentDir;
|
||||
}
|
||||
}
|
||||
|
||||
/** Cache for bundled commands */
|
||||
let bundledCommandsCache: WorkflowCommand[] | null = null;
|
||||
|
||||
@@ -172,43 +146,57 @@ export function loadBundledCommands(): WorkflowCommand[] {
|
||||
/**
|
||||
* Discover all available commands.
|
||||
*
|
||||
* Precedence: project > user > bundled
|
||||
* Precedence (highest wins): .omp > .pi > .claude (project before user), then bundled
|
||||
*/
|
||||
export function discoverCommands(cwd: string): WorkflowCommand[] {
|
||||
const commandMap = new Map<string, WorkflowCommand>();
|
||||
const resolvedCwd = path.resolve(cwd);
|
||||
const commandSources = Array.from(new Set(getConfigDirs("", { project: false }).map((entry) => entry.source)));
|
||||
|
||||
const userDirs = getConfigDirs("commands", { project: false })
|
||||
.filter((entry) => commandSources.includes(entry.source))
|
||||
.map((entry) => ({
|
||||
...entry,
|
||||
path: path.resolve(entry.path),
|
||||
}));
|
||||
|
||||
const projectDirs = findAllNearestProjectConfigDirs("commands", resolvedCwd)
|
||||
.filter((entry) => commandSources.includes(entry.source))
|
||||
.map((entry) => ({
|
||||
...entry,
|
||||
path: path.resolve(entry.path),
|
||||
}));
|
||||
|
||||
const orderedSources = commandSources.filter(
|
||||
(source) =>
|
||||
userDirs.some((entry) => entry.source === source) || projectDirs.some((entry) => entry.source === source),
|
||||
);
|
||||
|
||||
const orderedDirs: Array<{ dir: string; source: "user" | "project" }> = [];
|
||||
for (const source of orderedSources) {
|
||||
const project = projectDirs.find((entry) => entry.source === source);
|
||||
if (project) orderedDirs.push({ dir: project.path, source: "project" });
|
||||
const user = userDirs.find((entry) => entry.source === source);
|
||||
if (user) orderedDirs.push({ dir: user.path, source: "user" });
|
||||
}
|
||||
|
||||
const commands: WorkflowCommand[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
for (const { dir, source } of orderedDirs) {
|
||||
for (const cmd of loadCommandsFromDir(dir, source)) {
|
||||
if (seen.has(cmd.name)) continue;
|
||||
commands.push(cmd);
|
||||
seen.add(cmd.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Bundled commands (lowest priority)
|
||||
for (const cmd of loadBundledCommands()) {
|
||||
commandMap.set(cmd.name, cmd);
|
||||
if (seen.has(cmd.name)) continue;
|
||||
commands.push(cmd);
|
||||
seen.add(cmd.name);
|
||||
}
|
||||
|
||||
// User commands
|
||||
const userPiDir = path.join(os.homedir(), ".pi", "agent", "commands");
|
||||
const userClaudeDir = path.join(os.homedir(), ".claude", "commands");
|
||||
|
||||
for (const cmd of loadCommandsFromDir(userClaudeDir, "user")) {
|
||||
commandMap.set(cmd.name, cmd);
|
||||
}
|
||||
for (const cmd of loadCommandsFromDir(userPiDir, "user")) {
|
||||
commandMap.set(cmd.name, cmd);
|
||||
}
|
||||
|
||||
// Project commands (highest priority)
|
||||
const projectPiDir = findNearestDir(cwd, ".pi/commands");
|
||||
const projectClaudeDir = findNearestDir(cwd, ".claude/commands");
|
||||
|
||||
if (projectClaudeDir) {
|
||||
for (const cmd of loadCommandsFromDir(projectClaudeDir, "project")) {
|
||||
commandMap.set(cmd.name, cmd);
|
||||
}
|
||||
}
|
||||
if (projectPiDir) {
|
||||
for (const cmd of loadCommandsFromDir(projectPiDir, "project")) {
|
||||
commandMap.set(cmd.name, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(commandMap.values());
|
||||
return commands;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,17 +2,19 @@
|
||||
* Agent discovery from filesystem.
|
||||
*
|
||||
* Discovers agent definitions from:
|
||||
* - ~/.pi/agent/agents/*.md (user-level, primary)
|
||||
* - ~/.claude/agents/*.md (user-level, fallback)
|
||||
* - .pi/agents/*.md (project-level, primary)
|
||||
* - .claude/agents/*.md (project-level, fallback)
|
||||
* - ~/.omp/agent/agents/*.md (user-level, primary)
|
||||
* - ~/.pi/agent/agents/*.md (user-level, legacy)
|
||||
* - ~/.claude/agents/*.md (user-level, legacy)
|
||||
* - .omp/agents/*.md (project-level, primary)
|
||||
* - .pi/agents/*.md (project-level, legacy)
|
||||
* - .claude/agents/*.md (project-level, legacy)
|
||||
*
|
||||
* Agent files use markdown with YAML frontmatter.
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { findAllNearestProjectConfigDirs, getConfigDirs } from "../../../config";
|
||||
import { loadBundledAgents } from "./agents";
|
||||
import type { AgentDefinition, AgentSource } from "./types";
|
||||
|
||||
@@ -76,7 +78,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentDefinition[]
|
||||
for (const entry of entries) {
|
||||
if (!entry.name.endsWith(".md")) continue;
|
||||
|
||||
const filePath = path.join(dir, entry.name);
|
||||
const filePath = path.resolve(dir, entry.name);
|
||||
|
||||
// Handle both regular files and symlinks
|
||||
try {
|
||||
@@ -124,80 +126,66 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentDefinition[]
|
||||
return agents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if path is a directory.
|
||||
*/
|
||||
function isDirectory(p: string): boolean {
|
||||
try {
|
||||
return fs.statSync(p).isDirectory();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find nearest directory by walking up from cwd.
|
||||
*/
|
||||
function findNearestDir(cwd: string, relPath: string): string | null {
|
||||
let currentDir = cwd;
|
||||
while (true) {
|
||||
const candidate = path.join(currentDir, relPath);
|
||||
if (isDirectory(candidate)) return candidate;
|
||||
|
||||
const parentDir = path.dirname(currentDir);
|
||||
if (parentDir === currentDir) return null;
|
||||
currentDir = parentDir;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover agents from filesystem and merge with bundled agents.
|
||||
*
|
||||
* Precedence (highest wins): project > user > bundled
|
||||
* Within each level: .pi > .claude
|
||||
* Precedence (highest wins): .omp > .pi > .claude (project before user), then bundled
|
||||
*
|
||||
* @param cwd - Current working directory for project agent discovery
|
||||
*/
|
||||
export function discoverAgents(cwd: string): DiscoveryResult {
|
||||
// Primary directories (.pi)
|
||||
const userPiDir = path.join(os.homedir(), ".pi", "agent", "agents");
|
||||
const projectPiDir = findNearestDir(cwd, ".pi/agents");
|
||||
const resolvedCwd = path.resolve(cwd);
|
||||
const agentSources = Array.from(new Set(getConfigDirs("", { project: false }).map((entry) => entry.source)));
|
||||
|
||||
// Fallback directories (.claude)
|
||||
const userClaudeDir = path.join(os.homedir(), ".claude", "agents");
|
||||
const projectClaudeDir = findNearestDir(cwd, ".claude/agents");
|
||||
// Get user directories (priority order: .omp, .pi, .claude, ...)
|
||||
const userDirs = getConfigDirs("agents", { project: false })
|
||||
.filter((entry) => agentSources.includes(entry.source))
|
||||
.map((entry) => ({
|
||||
...entry,
|
||||
path: path.resolve(entry.path),
|
||||
}));
|
||||
|
||||
const agentMap = new Map<string, AgentDefinition>();
|
||||
// Get project directories by walking up from cwd (priority order)
|
||||
const projectDirs = findAllNearestProjectConfigDirs("agents", resolvedCwd)
|
||||
.filter((entry) => agentSources.includes(entry.source))
|
||||
.map((entry) => ({
|
||||
...entry,
|
||||
path: path.resolve(entry.path),
|
||||
}));
|
||||
|
||||
const orderedSources = agentSources.filter(
|
||||
(source) =>
|
||||
userDirs.some((entry) => entry.source === source) || projectDirs.some((entry) => entry.source === source),
|
||||
);
|
||||
|
||||
const orderedDirs: Array<{ dir: string; source: AgentSource }> = [];
|
||||
for (const source of orderedSources) {
|
||||
const project = projectDirs.find((entry) => entry.source === source);
|
||||
if (project) orderedDirs.push({ dir: project.path, source: "project" });
|
||||
const user = userDirs.find((entry) => entry.source === source);
|
||||
if (user) orderedDirs.push({ dir: user.path, source: "user" });
|
||||
}
|
||||
|
||||
const agents: AgentDefinition[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
for (const { dir, source } of orderedDirs) {
|
||||
for (const agent of loadAgentsFromDir(dir, source)) {
|
||||
if (seen.has(agent.name)) continue;
|
||||
agents.push(agent);
|
||||
seen.add(agent.name);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Bundled agents (lowest priority)
|
||||
for (const agent of loadBundledAgents()) {
|
||||
agentMap.set(agent.name, agent);
|
||||
if (seen.has(agent.name)) continue;
|
||||
agents.push(agent);
|
||||
seen.add(agent.name);
|
||||
}
|
||||
|
||||
// 2. User agents (.claude then .pi - .pi overrides .claude)
|
||||
for (const agent of loadAgentsFromDir(userClaudeDir, "user")) {
|
||||
agentMap.set(agent.name, agent);
|
||||
}
|
||||
for (const agent of loadAgentsFromDir(userPiDir, "user")) {
|
||||
agentMap.set(agent.name, agent);
|
||||
}
|
||||
const projectAgentsDir = projectDirs.length > 0 ? projectDirs[0].path : null;
|
||||
|
||||
// 3. Project agents (highest priority - .claude then .pi)
|
||||
if (projectClaudeDir) {
|
||||
for (const agent of loadAgentsFromDir(projectClaudeDir, "project")) {
|
||||
agentMap.set(agent.name, agent);
|
||||
}
|
||||
}
|
||||
if (projectPiDir) {
|
||||
for (const agent of loadAgentsFromDir(projectPiDir, "project")) {
|
||||
agentMap.set(agent.name, agent);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
agents: Array.from(agentMap.values()),
|
||||
projectAgentsDir: projectPiDir,
|
||||
};
|
||||
return { agents, projectAgentsDir };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Subprocess execution for subagents.
|
||||
*
|
||||
* Spawns `pi` in JSON mode to execute tasks with isolated context.
|
||||
* Spawns `omp` in JSON mode to execute tasks with isolated context.
|
||||
* Parses JSON events for progress tracking.
|
||||
*/
|
||||
|
||||
@@ -18,15 +18,15 @@ import {
|
||||
type AgentProgress,
|
||||
MAX_OUTPUT_BYTES,
|
||||
MAX_OUTPUT_LINES,
|
||||
PI_BLOCKED_AGENT_ENV,
|
||||
OMP_BLOCKED_AGENT_ENV,
|
||||
type SingleResult,
|
||||
} from "./types";
|
||||
|
||||
/** pi command: 'pi.cmd' on Windows, 'pi' elsewhere */
|
||||
const PI_CMD = process.platform === "win32" ? "pi.cmd" : "pi";
|
||||
/** omp command: 'omp.cmd' on Windows, 'omp' elsewhere */
|
||||
const OMP_CMD = process.platform === "win32" ? "omp.cmd" : "omp";
|
||||
|
||||
/** Windows shell option for spawn */
|
||||
const PI_SHELL_OPT = process.platform === "win32";
|
||||
const OMP_SHELL_OPT = process.platform === "win32";
|
||||
|
||||
/** Options for subprocess execution */
|
||||
export interface ExecutorOptions {
|
||||
@@ -143,7 +143,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
const tempDir = os.tmpdir();
|
||||
const promptFile = path.join(
|
||||
tempDir,
|
||||
`pi-agent-${agent.name}-${Date.now()}-${Math.random().toString(36).slice(2)}.md`,
|
||||
`omp-agent-${agent.name}-${Date.now()}-${Math.random().toString(36).slice(2)}.md`,
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -217,14 +217,14 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
// Set up environment - block same-agent recursion unless explicitly recursive
|
||||
const env = { ...process.env };
|
||||
if (!agent.recursive) {
|
||||
env[PI_BLOCKED_AGENT_ENV] = agent.name;
|
||||
env[OMP_BLOCKED_AGENT_ENV] = agent.name;
|
||||
}
|
||||
|
||||
// Spawn subprocess
|
||||
const proc = spawn(PI_CMD, args, {
|
||||
const proc = spawn(OMP_CMD, args, {
|
||||
cwd,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
shell: PI_SHELL_OPT,
|
||||
shell: OMP_SHELL_OPT,
|
||||
env,
|
||||
});
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* Task tool - Delegate tasks to specialized agents.
|
||||
*
|
||||
* Discovers agent definitions from:
|
||||
* - Bundled agents (shipped with pi-coding-agent)
|
||||
* - ~/.pi/agent/agents/*.md (user-level)
|
||||
* - .pi/agents/*.md (project-level)
|
||||
* - Bundled agents (shipped with omp-coding-agent)
|
||||
* - ~/.omp/agent/agents/*.md (user-level)
|
||||
* - .omp/agents/*.md (project-level)
|
||||
*
|
||||
* Supports:
|
||||
* - Single agent execution
|
||||
@@ -25,8 +25,8 @@ import {
|
||||
MAX_AGENTS_IN_DESCRIPTION,
|
||||
MAX_CONCURRENCY,
|
||||
MAX_PARALLEL_TASKS,
|
||||
PI_BLOCKED_AGENT_ENV,
|
||||
PI_NO_SUBAGENTS_ENV,
|
||||
OMP_BLOCKED_AGENT_ENV,
|
||||
OMP_NO_SUBAGENTS_ENV,
|
||||
type TaskToolDetails,
|
||||
taskSchema,
|
||||
} from "./types";
|
||||
@@ -122,13 +122,13 @@ function buildDescription(cwd: string): string {
|
||||
`- tasks: Array of {agent, task, model?} - tasks to run in parallel (max ${MAX_PARALLEL_TASKS}, ${MAX_CONCURRENCY} concurrent)`,
|
||||
);
|
||||
lines.push(
|
||||
' - model: (optional) Override the agent\'s default model with fuzzy matching (e.g., "sonnet", "codex", "5.2"). Supports comma-separated fallbacks: "gpt, opus" tries gpt first, then opus. Use "default" for pi\'s default model',
|
||||
' - model: (optional) Override the agent\'s default model with fuzzy matching (e.g., "sonnet", "codex", "5.2"). Supports comma-separated fallbacks: "gpt, opus" tries gpt first, then opus. Use "default" for omp\'s default model',
|
||||
);
|
||||
lines.push(
|
||||
"- context: (optional) Shared context string prepended to all task prompts - use this to avoid repeating instructions",
|
||||
);
|
||||
lines.push("");
|
||||
lines.push("Results are always written to {tempdir}/pi-task-{runId}/task_{agent}_{index}.md");
|
||||
lines.push("Results are always written to {tempdir}/omp-task-{runId}/task_{agent}_{index}.md");
|
||||
lines.push("");
|
||||
lines.push("Example usage:");
|
||||
lines.push("");
|
||||
@@ -173,7 +173,7 @@ function buildDescription(cwd: string): string {
|
||||
lines.push(' { "agent": "explore", "task": "Search in tests/" }');
|
||||
lines.push(" ]");
|
||||
lines.push("}");
|
||||
lines.push("Results → {tempdir}/pi-task-{runId}/task_explore_*.md");
|
||||
lines.push("Results → {tempdir}/omp-task-{runId}/task_explore_*.md");
|
||||
lines.push("</example>");
|
||||
|
||||
return lines.join("\n");
|
||||
@@ -189,7 +189,7 @@ export function createTaskTool(
|
||||
): AgentTool<typeof taskSchema, TaskToolDetails, Theme> {
|
||||
const hasOutputTool = options?.availableTools?.has("output") ?? false;
|
||||
// Check if subagents are completely inhibited (legacy recursion prevention)
|
||||
if (process.env[PI_NO_SUBAGENTS_ENV]) {
|
||||
if (process.env[OMP_NO_SUBAGENTS_ENV]) {
|
||||
return {
|
||||
name: "task",
|
||||
label: "Task",
|
||||
@@ -207,7 +207,7 @@ export function createTaskTool(
|
||||
}
|
||||
|
||||
// Check for same-agent blocking (allows other agent types)
|
||||
const blockedAgent = process.env[PI_BLOCKED_AGENT_ENV];
|
||||
const blockedAgent = process.env[OMP_BLOCKED_AGENT_ENV];
|
||||
|
||||
return {
|
||||
name: "task",
|
||||
|
||||
@@ -8,19 +8,17 @@
|
||||
* - Fuzzy match: "opus" → "p-anthropic/claude-opus-4-5"
|
||||
* - Comma fallback: "gpt, opus" → tries gpt first, then opus
|
||||
* - "default" → undefined (use system default)
|
||||
* - "pi/slow" → configured slow model from settings
|
||||
* - "omp/slow" → configured slow model from settings
|
||||
*/
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { readConfigFile } from "../../../config";
|
||||
|
||||
/** pi command: 'pi.cmd' on Windows, 'pi' elsewhere */
|
||||
const PI_CMD = process.platform === "win32" ? "pi.cmd" : "pi";
|
||||
/** omp command: 'omp.cmd' on Windows, 'omp' elsewhere */
|
||||
const OMP_CMD = process.platform === "win32" ? "omp.cmd" : "omp";
|
||||
|
||||
/** Windows shell option for spawn/spawnSync */
|
||||
const PI_SHELL_OPT = process.platform === "win32";
|
||||
const OMP_SHELL_OPT = process.platform === "win32";
|
||||
|
||||
/** Cache for available models (provider/modelId format) */
|
||||
let cachedModels: string[] | null = null;
|
||||
@@ -31,7 +29,7 @@ let cacheExpiry = 0;
|
||||
const CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Get available models from `pi --list-models`.
|
||||
* Get available models from `omp --list-models`.
|
||||
* Returns models in "provider/modelId" format.
|
||||
* Caches the result for performance.
|
||||
*/
|
||||
@@ -42,10 +40,10 @@ export function getAvailableModels(): string[] {
|
||||
}
|
||||
|
||||
try {
|
||||
const result = spawnSync(PI_CMD, ["--list-models"], {
|
||||
const result = spawnSync(OMP_CMD, ["--list-models"], {
|
||||
encoding: "utf-8",
|
||||
timeout: 5000,
|
||||
shell: PI_SHELL_OPT,
|
||||
shell: OMP_SHELL_OPT,
|
||||
});
|
||||
|
||||
if (result.status !== 0 || !result.stdout) {
|
||||
@@ -82,27 +80,24 @@ export function clearModelCache(): void {
|
||||
cacheExpiry = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load model roles from settings file.
|
||||
*/
|
||||
function loadModelRoles(): Record<string, string> {
|
||||
try {
|
||||
const settingsPath = join(homedir(), ".pi", "agent", "settings.json");
|
||||
if (!existsSync(settingsPath)) return {};
|
||||
const content = readFileSync(settingsPath, "utf-8");
|
||||
const settings = JSON.parse(content);
|
||||
return settings.modelRoles ?? {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
interface SettingsWithRoles {
|
||||
modelRoles?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a pi/<role> alias to a model string.
|
||||
* Load model roles from settings file (checks .omp first, then .pi fallback).
|
||||
*/
|
||||
function loadModelRoles(): Record<string, string> {
|
||||
const result = readConfigFile<SettingsWithRoles>("settings.json", { project: false });
|
||||
return result?.content.modelRoles ?? {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an omp/<role> alias to a model string.
|
||||
* Looks up the role in settings.modelRoles and returns the configured model.
|
||||
* Returns undefined if the role isn't configured.
|
||||
*/
|
||||
function resolvePiAlias(role: string, availableModels: string[]): string | undefined {
|
||||
function resolveOmpAlias(role: string, availableModels: string[]): string | undefined {
|
||||
const roles = loadModelRoles();
|
||||
|
||||
// Look up role in settings (case-insensitive)
|
||||
@@ -148,10 +143,10 @@ export function resolveModelPattern(pattern: string | undefined, availableModels
|
||||
.filter(Boolean);
|
||||
|
||||
for (const p of patterns) {
|
||||
// Handle pi/<role> aliases - looks up role in settings.modelRoles
|
||||
if (p.toLowerCase().startsWith("pi/")) {
|
||||
const role = p.slice(3); // Remove "pi/" prefix
|
||||
const resolved = resolvePiAlias(role, models);
|
||||
// Handle omp/<role> aliases - looks up role in settings.modelRoles
|
||||
if (p.toLowerCase().startsWith("omp/")) {
|
||||
const role = p.slice(4); // Remove "omp/" prefix
|
||||
const resolved = resolveOmpAlias(role, models);
|
||||
if (resolved) return resolved;
|
||||
continue; // Role not configured, try next pattern
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ export const MAX_OUTPUT_LINES = 5000;
|
||||
export const MAX_AGENTS_IN_DESCRIPTION = 10;
|
||||
|
||||
/** Environment variable to inhibit subagent spawning (legacy, still checked for backwards compat) */
|
||||
export const PI_NO_SUBAGENTS_ENV = "PI_NO_SUBAGENTS";
|
||||
export const OMP_NO_SUBAGENTS_ENV = "OMP_NO_SUBAGENTS";
|
||||
|
||||
/** Environment variable containing blocked agent name (self-recursion prevention) */
|
||||
export const PI_BLOCKED_AGENT_ENV = "PI_BLOCKED_AGENT";
|
||||
export const OMP_BLOCKED_AGENT_ENV = "OMP_BLOCKED_AGENT";
|
||||
|
||||
/** Task tool parameters */
|
||||
export const taskSchema = Type.Object({
|
||||
|
||||
@@ -309,7 +309,7 @@ function convertWithMarkitdown(
|
||||
|
||||
// Write to temp file with extension hint
|
||||
const ext = extensionHint || ".bin";
|
||||
const tmpFile = path.join(os.tmpdir(), `pi-convert-${Date.now()}${ext}`);
|
||||
const tmpFile = path.join(os.tmpdir(), `omp-convert-${Date.now()}${ext}`);
|
||||
|
||||
try {
|
||||
fs.writeFileSync(tmpFile, content);
|
||||
@@ -531,7 +531,7 @@ function parseFeedToMarkdown(content: string, maxItems = 10): string {
|
||||
* Render HTML to text using lynx
|
||||
*/
|
||||
function renderWithLynx(html: string, timeout: number): { content: string; ok: boolean } {
|
||||
const tmpFile = path.join(os.tmpdir(), `pi-render-${Date.now()}.html`);
|
||||
const tmpFile = path.join(os.tmpdir(), `omp-render-${Date.now()}.html`);
|
||||
try {
|
||||
fs.writeFileSync(tmpFile, html);
|
||||
// Convert path to file URL (handles Windows paths correctly)
|
||||
@@ -712,7 +712,7 @@ async function fetchGitHubApi(endpoint: string, timeout: number): Promise<{ data
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
"User-Agent": "pi-web-fetch/1.0",
|
||||
"User-Agent": "omp-web-fetch/1.0",
|
||||
};
|
||||
|
||||
// Use GITHUB_TOKEN if available
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
*
|
||||
* 4-tier auth resolution:
|
||||
* 1. ANTHROPIC_SEARCH_API_KEY / ANTHROPIC_SEARCH_BASE_URL env vars
|
||||
* 2. Provider with api="anthropic-messages" in ~/.pi/agent/models.json
|
||||
* 3. OAuth credentials in ~/.pi/agent/auth.json (with expiry check)
|
||||
* 2. Provider with api="anthropic-messages" in ~/.omp/agent/models.json
|
||||
* 3. OAuth credentials in ~/.omp/agent/auth.json (with expiry check)
|
||||
* 4. ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL fallback
|
||||
*/
|
||||
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { getConfigDirPaths } from "../../../config";
|
||||
import type { AnthropicAuthConfig, AuthJson, ModelsJson } from "./types";
|
||||
|
||||
const DEFAULT_BASE_URL = "https://api.anthropic.com";
|
||||
@@ -83,7 +84,8 @@ export function isOAuthToken(apiKey: string): boolean {
|
||||
* 4. ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL fallback
|
||||
*/
|
||||
export async function findAnthropicAuth(): Promise<AnthropicAuthConfig | null> {
|
||||
const piAgentDir = path.join(os.homedir(), ".pi", "agent");
|
||||
// Get all config directories (user-level only) for fallback support
|
||||
const configDirs = getConfigDirPaths("", { project: false });
|
||||
|
||||
// 1. Explicit search-specific env vars
|
||||
const searchApiKey = await getEnv("ANTHROPIC_SEARCH_API_KEY");
|
||||
@@ -96,41 +98,45 @@ export async function findAnthropicAuth(): Promise<AnthropicAuthConfig | null> {
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Provider with api="anthropic-messages" in models.json
|
||||
const modelsJson = await readJson<ModelsJson>(path.join(piAgentDir, "models.json"));
|
||||
if (modelsJson?.providers) {
|
||||
// First pass: look for providers with actual API keys
|
||||
for (const [_name, provider] of Object.entries(modelsJson.providers)) {
|
||||
if (provider.api === "anthropic-messages" && provider.apiKey && provider.apiKey !== "none") {
|
||||
return {
|
||||
apiKey: provider.apiKey,
|
||||
baseUrl: provider.baseUrl ?? DEFAULT_BASE_URL,
|
||||
isOAuth: isOAuthToken(provider.apiKey),
|
||||
};
|
||||
// 2. Provider with api="anthropic-messages" in models.json (check all config dirs)
|
||||
for (const configDir of configDirs) {
|
||||
const modelsJson = await readJson<ModelsJson>(path.join(configDir, "models.json"));
|
||||
if (modelsJson?.providers) {
|
||||
// First pass: look for providers with actual API keys
|
||||
for (const [_name, provider] of Object.entries(modelsJson.providers)) {
|
||||
if (provider.api === "anthropic-messages" && provider.apiKey && provider.apiKey !== "none") {
|
||||
return {
|
||||
apiKey: provider.apiKey,
|
||||
baseUrl: provider.baseUrl ?? DEFAULT_BASE_URL,
|
||||
isOAuth: isOAuthToken(provider.apiKey),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Second pass: check for proxy mode (baseUrl but apiKey="none")
|
||||
for (const [_name, provider] of Object.entries(modelsJson.providers)) {
|
||||
if (provider.api === "anthropic-messages" && provider.baseUrl) {
|
||||
return {
|
||||
apiKey: provider.apiKey ?? "",
|
||||
baseUrl: provider.baseUrl,
|
||||
isOAuth: false,
|
||||
};
|
||||
// Second pass: check for proxy mode (baseUrl but apiKey="none")
|
||||
for (const [_name, provider] of Object.entries(modelsJson.providers)) {
|
||||
if (provider.api === "anthropic-messages" && provider.baseUrl) {
|
||||
return {
|
||||
apiKey: provider.apiKey ?? "",
|
||||
baseUrl: provider.baseUrl,
|
||||
isOAuth: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. OAuth credentials in auth.json (with 5-minute expiry buffer)
|
||||
const authJson = await readJson<AuthJson>(path.join(piAgentDir, "auth.json"));
|
||||
if (authJson?.anthropic?.type === "oauth" && authJson.anthropic.access) {
|
||||
const expiryBuffer = 5 * 60 * 1000; // 5 minutes
|
||||
if (authJson.anthropic.expires > Date.now() + expiryBuffer) {
|
||||
return {
|
||||
apiKey: authJson.anthropic.access,
|
||||
baseUrl: DEFAULT_BASE_URL,
|
||||
isOAuth: true,
|
||||
};
|
||||
// 3. OAuth credentials in auth.json (with 5-minute expiry buffer, check all config dirs)
|
||||
for (const configDir of configDirs) {
|
||||
const authJson = await readJson<AuthJson>(path.join(configDir, "auth.json"));
|
||||
if (authJson?.anthropic?.type === "oauth" && authJson.anthropic.access) {
|
||||
const expiryBuffer = 5 * 60 * 1000; // 5 minutes
|
||||
if (authJson.anthropic.expires > Date.now() + expiryBuffer) {
|
||||
return {
|
||||
apiKey: authJson.anthropic.access,
|
||||
baseUrl: DEFAULT_BASE_URL,
|
||||
isOAuth: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +169,7 @@ export function buildAnthropicHeaders(auth: AnthropicAuthConfig): Record<string,
|
||||
"content-type": "application/json",
|
||||
"anthropic-dangerous-direct-browser-access": "true",
|
||||
"anthropic-beta": betas.join(","),
|
||||
"user-agent": "pi-coding-agent/1.0.0 (cli)",
|
||||
"user-agent": "claude-code/2.0.20",
|
||||
"x-app": "cli",
|
||||
// Stainless SDK telemetry headers (required for OAuth)
|
||||
"x-stainless-arch": process.arch,
|
||||
|
||||
@@ -186,7 +186,7 @@ async function executeWebSearch(
|
||||
}
|
||||
}
|
||||
|
||||
const WEB_SEARCH_DESCRIPTION = `Allows Pi to search the web and use the results to inform responses
|
||||
const WEB_SEARCH_DESCRIPTION = `Allows OMP to search the web and use the results to inform responses
|
||||
- Provides up-to-date information for current events and recent data
|
||||
- Returns search result information formatted as search result blocks, including links as markdown hyperlinks
|
||||
- Use this tool for accessing information beyond Claude's knowledge cutoff
|
||||
|
||||
@@ -180,7 +180,7 @@ export async function searchAnthropic(params: AnthropicSearchParams): Promise<We
|
||||
const auth = await findAnthropicAuth();
|
||||
if (!auth) {
|
||||
throw new Error(
|
||||
"No Anthropic credentials found. Set ANTHROPIC_API_KEY or configure OAuth in ~/.pi/agent/auth.json",
|
||||
"No Anthropic credentials found. Set ANTHROPIC_API_KEY or configure OAuth in ~/.omp/agent/auth.json",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
* createAgentSession() options. The SDK does the heavy lifting.
|
||||
*/
|
||||
|
||||
import { existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { type ImageContent, supportsXhigh } from "@oh-my-pi/pi-ai";
|
||||
import chalk from "chalk";
|
||||
import { type Args, parseArgs, printHelp } from "./cli/args";
|
||||
@@ -15,7 +13,7 @@ import { listModels } from "./cli/list-models";
|
||||
import { parsePluginArgs, printPluginHelp, runPluginCommand } from "./cli/plugin-cli";
|
||||
import { selectSession } from "./cli/session-picker";
|
||||
import { parseUpdateArgs, printUpdateHelp, runUpdateCommand } from "./cli/update-cli";
|
||||
import { CONFIG_DIR_NAME, getAgentDir, getModelsPath, VERSION } from "./config";
|
||||
import { findConfigFile, getModelsPath, VERSION } from "./config";
|
||||
import type { AgentSession } from "./core/agent-session";
|
||||
import type { LoadedCustomTool } from "./core/custom-tools/index";
|
||||
import { exportFromFile } from "./core/export-html/index";
|
||||
@@ -199,18 +197,16 @@ function createSessionManager(parsed: Args, cwd: string): SessionManager | undef
|
||||
|
||||
/** Discover SYSTEM.md file if no CLI system prompt was provided */
|
||||
function discoverSystemPromptFile(): string | undefined {
|
||||
// Check project-local first: .pi/SYSTEM.md
|
||||
const projectPath = join(process.cwd(), CONFIG_DIR_NAME, "SYSTEM.md");
|
||||
if (existsSync(projectPath)) {
|
||||
// Check project-local first (.omp/SYSTEM.md, .pi/SYSTEM.md legacy)
|
||||
const projectPath = findConfigFile("SYSTEM.md", { user: false });
|
||||
if (projectPath) {
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
// Fall back to global: ~/.pi/agent/SYSTEM.md
|
||||
const globalPath = join(getAgentDir(), "SYSTEM.md");
|
||||
if (existsSync(globalPath)) {
|
||||
// If not found, check SYSTEM.md file in the global directory.
|
||||
const globalPath = findConfigFile("SYSTEM.md", { user: true });
|
||||
if (globalPath) {
|
||||
return globalPath;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -375,8 +371,8 @@ export async function main(args: string[]) {
|
||||
time("SettingsManager.create");
|
||||
|
||||
// Apply model role overrides from CLI args or env vars (ephemeral, not persisted)
|
||||
const smolModel = parsed.smol ?? process.env.PI_SMOL_MODEL;
|
||||
const slowModel = parsed.slow ?? process.env.PI_SLOW_MODEL;
|
||||
const smolModel = parsed.smol ?? process.env.OMP_SMOL_MODEL;
|
||||
const slowModel = parsed.slow ?? process.env.OMP_SLOW_MODEL;
|
||||
if (smolModel || slowModel) {
|
||||
const roleOverrides: Record<string, string> = {};
|
||||
if (smolModel) roleOverrides.smol = smolModel;
|
||||
|
||||
@@ -6,6 +6,22 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFile
|
||||
import { dirname, join } from "node:path";
|
||||
import { getAgentDir } from "./config";
|
||||
|
||||
/**
|
||||
* Migrate PI_* environment variables to OMP_* equivalents.
|
||||
* If PI_XX is set and OMP_XX is not, set OMP_XX to PI_XX's value.
|
||||
* This provides backwards compatibility for users with existing PI_* env vars.
|
||||
*/
|
||||
export function migrateEnvVars(): void {
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (key.startsWith("PI_") && value !== undefined) {
|
||||
const ompKey = `OMP_${key.slice(3)}`; // PI_FOO -> OMP_FOO
|
||||
if (process.env[ompKey] === undefined) {
|
||||
process.env[ompKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate legacy oauth.json and settings.json apiKeys to auth.json.
|
||||
*
|
||||
@@ -66,10 +82,10 @@ export function migrateAuthToAuthJson(): string[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate sessions from ~/.pi/agent/*.jsonl to proper session directories.
|
||||
* Migrate sessions from ~/.omp/agent/*.jsonl to proper session directories.
|
||||
*
|
||||
* Bug in v0.30.0: Sessions were saved to ~/.pi/agent/ instead of
|
||||
* ~/.pi/agent/sessions/<encoded-cwd>/. This migration moves them
|
||||
* Bug in v0.30.0: Sessions were saved to ~/.omp/agent/ instead of
|
||||
* ~/.omp/agent/sessions/<encoded-cwd>/. This migration moves them
|
||||
* to the correct location based on the cwd in their session header.
|
||||
*
|
||||
* See: https://github.com/badlogic/pi-mono/issues/320
|
||||
@@ -129,7 +145,12 @@ export function migrateSessionsFromAgentRoot(): void {
|
||||
* @returns Object with migration results
|
||||
*/
|
||||
export function runMigrations(): { migratedAuthProviders: string[] } {
|
||||
// First: migrate env vars (before anything else reads them)
|
||||
migrateEnvVars();
|
||||
|
||||
// Then: run data migrations
|
||||
const migratedAuthProviders = migrateAuthToAuthJson();
|
||||
migrateSessionsFromAgentRoot();
|
||||
|
||||
return { migratedAuthProviders };
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export class HookEditorComponent extends Container {
|
||||
}
|
||||
|
||||
const currentText = this.editor.getText();
|
||||
const tmpFile = path.join(os.tmpdir(), `pi-hook-editor-${Date.now()}.md`);
|
||||
const tmpFile = path.join(os.tmpdir(), `omp-hook-editor-${Date.now()}.md`);
|
||||
|
||||
try {
|
||||
fs.writeFileSync(tmpFile, currentText, "utf-8");
|
||||
|
||||
@@ -52,7 +52,7 @@ export class PluginListComponent extends Container {
|
||||
if (plugins.length === 0) {
|
||||
this.addChild(new Text(theme.fg("muted", " No plugins installed"), 0, 0));
|
||||
this.addChild(new Spacer(1));
|
||||
this.addChild(new Text(theme.fg("dim", " Install with: pi plugin install <package>"), 0, 0));
|
||||
this.addChild(new Text(theme.fg("dim", " Install with: omp plugin install <package>"), 0, 0));
|
||||
this.addChild(new Spacer(1));
|
||||
this.addChild(new DynamicBorder());
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface LspServerInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Premium welcome screen with block-based Pi logo and two-column layout.
|
||||
* Premium welcome screen with block-based OMP logo and two-column layout.
|
||||
*/
|
||||
export class WelcomeComponent implements Component {
|
||||
private version: string;
|
||||
@@ -60,7 +60,7 @@ export class WelcomeComponent implements Component {
|
||||
const leftCol = 26;
|
||||
const rightCol = boxWidth - leftCol - 3; // 3 = │ + │ + │
|
||||
|
||||
// Block-based Pi logo (gradient: magenta → cyan)
|
||||
// Block-based OMP logo (gradient: magenta → cyan)
|
||||
// biome-ignore format: preserve ASCII art layout
|
||||
const piLogo = ["▀████████████▀", " ╘███ ███ ", " ███ ███ ", " ███ ███ ", " ▄███▄ ▄███▄ "];
|
||||
|
||||
|
||||
@@ -1535,7 +1535,7 @@ export class InteractiveMode {
|
||||
}
|
||||
|
||||
const currentText = this.editor.getText();
|
||||
const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);
|
||||
const tmpFile = path.join(os.tmpdir(), `omp-editor-${Date.now()}.omp.md`);
|
||||
|
||||
try {
|
||||
// Write current content to temp file
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Pi Coding Agent Theme",
|
||||
"description": "Theme schema for Pi coding agent",
|
||||
"description": "Theme schema for OMP coding agent",
|
||||
"type": "object",
|
||||
"required": ["name", "colors"],
|
||||
"properties": {
|
||||
|
||||
@@ -199,7 +199,7 @@ async function readImageMacOS(timeout: number): Promise<ClipboardImage | null> {
|
||||
|
||||
// Read the actual image data using a temp file approach
|
||||
// osascript can't output binary directly, so we write to a temp file
|
||||
const tempFile = `/tmp/pi-clipboard-${Date.now()}.${imageType === "png" ? "png" : "jpg"}`;
|
||||
const tempFile = `/tmp/omp-clipboard-${Date.now()}.${imageType === "png" ? "png" : "jpg"}`;
|
||||
const clipboardClass = imageType === "png" ? "«class PNGf»" : "«class JPEG»";
|
||||
|
||||
const readScript = `
|
||||
|
||||
@@ -77,7 +77,7 @@ SNAPSHOT_FILE='${escapedPath}'
|
||||
${hasRcFile ? `source "${rcFile}" < /dev/null 2>/dev/null` : "# No user config file to source"}
|
||||
|
||||
# Create/clear the snapshot file
|
||||
echo "# Shell snapshot - generated by pi agent" >| "$SNAPSHOT_FILE"
|
||||
echo "# Shell snapshot - generated by omp agent" >| "$SNAPSHOT_FILE"
|
||||
|
||||
# Unalias everything first to avoid conflicts when sourced
|
||||
echo "unalias -a 2>/dev/null || true" >> "$SNAPSHOT_FILE"
|
||||
@@ -127,7 +127,7 @@ export async function getOrCreateSnapshot(
|
||||
const rcFile = getShellConfigFile(shell);
|
||||
|
||||
// Create snapshot directory
|
||||
const snapshotDir = join(tmpdir(), "pi-shell-snapshots");
|
||||
const snapshotDir = join(tmpdir(), "omp-shell-snapshots");
|
||||
try {
|
||||
mkdirSync(snapshotDir, { recursive: true });
|
||||
} catch {
|
||||
|
||||
@@ -26,13 +26,13 @@ function isExecutable(path: string): boolean {
|
||||
* Build the spawn environment (cached).
|
||||
*/
|
||||
function buildSpawnEnv(shell: string): Record<string, string | undefined> {
|
||||
const noCI = process.env.PI_BASH_NO_CI || process.env.CLAUDE_BASH_NO_CI;
|
||||
const noCI = process.env.OMP_BASH_NO_CI || process.env.CLAUDE_BASH_NO_CI;
|
||||
return {
|
||||
...process.env,
|
||||
SHELL: shell,
|
||||
GIT_EDITOR: "true",
|
||||
GPG_TTY: "not a tty",
|
||||
PICODE: "1",
|
||||
OMPCODE: "1",
|
||||
CLAUDECODE: "1",
|
||||
...(noCI ? {} : { CI: "true" }),
|
||||
};
|
||||
@@ -40,10 +40,10 @@ function buildSpawnEnv(shell: string): Record<string, string | undefined> {
|
||||
|
||||
/**
|
||||
* Get shell args, optionally including login shell flag.
|
||||
* Supports PI_BASH_NO_LOGIN and CLAUDE_BASH_NO_LOGIN to skip -l.
|
||||
* Supports OMP_BASH_NO_LOGIN and CLAUDE_BASH_NO_LOGIN to skip -l.
|
||||
*/
|
||||
function getShellArgs(): string[] {
|
||||
const noLogin = process.env.PI_BASH_NO_LOGIN || process.env.CLAUDE_BASH_NO_LOGIN;
|
||||
const noLogin = process.env.OMP_BASH_NO_LOGIN || process.env.CLAUDE_BASH_NO_LOGIN;
|
||||
return noLogin ? ["-c"] : ["-l", "-c"];
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ function getShellArgs(): string[] {
|
||||
* Get shell prefix for wrapping commands (profilers, strace, etc.).
|
||||
*/
|
||||
function getShellPrefix(): string | undefined {
|
||||
return process.env.PI_SHELL_PREFIX || process.env.CLAUDE_CODE_SHELL_PREFIX;
|
||||
return process.env.OMP_SHELL_PREFIX || process.env.CLAUDE_CODE_SHELL_PREFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +107,7 @@ export function getShellConfig(): ShellConfig {
|
||||
return cachedShellConfig;
|
||||
}
|
||||
throw new Error(
|
||||
`Custom shell path not found: ${customShellPath}\nPlease update shellPath in ~/.pi/agent/settings.json`,
|
||||
`Custom shell path not found: ${customShellPath}\nPlease update shellPath in ~/.omp/agent/settings.json`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ export function getShellConfig(): ShellConfig {
|
||||
`No bash shell found. Options:\n` +
|
||||
` 1. Install Git for Windows: https://git-scm.com/download/win\n` +
|
||||
` 2. Add your bash to PATH (Cygwin, MSYS2, etc.)\n` +
|
||||
` 3. Set shellPath in ~/.pi/agent/settings.json\n\n` +
|
||||
` 3. Set shellPath in ~/.omp/agent/settings.json\n\n` +
|
||||
`Searched Git Bash in:\n${paths.map((p) => ` ${p}`).join("\n")}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ describe.skipIf(!API_KEY)("AgentSession compaction e2e", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// Create temp directory for session files
|
||||
tempDir = join(tmpdir(), `pi-compaction-test-${Date.now()}`);
|
||||
tempDir = join(tmpdir(), `omp-compaction-test-${Date.now()}`);
|
||||
mkdirSync(tempDir, { recursive: true });
|
||||
|
||||
// Track events
|
||||
|
||||
@@ -32,7 +32,7 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
|
||||
let capturedEvents: SessionEvent[];
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = join(tmpdir(), `pi-compaction-hooks-test-${Date.now()}`);
|
||||
tempDir = join(tmpdir(), `omp-compaction-hooks-test-${Date.now()}`);
|
||||
mkdirSync(tempDir, { recursive: true });
|
||||
capturedEvents = [];
|
||||
});
|
||||
|
||||
@@ -13,11 +13,11 @@ describe.skipIf(!process.env.ANTHROPIC_API_KEY && !process.env.ANTHROPIC_OAUTH_T
|
||||
let sessionDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
sessionDir = join(tmpdir(), `pi-rpc-test-${Date.now()}`);
|
||||
sessionDir = join(tmpdir(), `omp-rpc-test-${Date.now()}`);
|
||||
client = new RpcClient({
|
||||
cliPath: join(import.meta.dir, "..", "dist", "cli.js"),
|
||||
cwd: join(import.meta.dir, ".."),
|
||||
env: { PI_CODING_AGENT_DIR: sessionDir },
|
||||
env: { OMP_CODING_AGENT_DIR: sessionDir },
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet-4-5",
|
||||
});
|
||||
|
||||
@@ -287,14 +287,14 @@ describe("skills", () => {
|
||||
});
|
||||
|
||||
it("should expand ~ in customDirectories", () => {
|
||||
const homeSkillsDir = join(homedir(), ".pi/agent/skills");
|
||||
const homeSkillsDir = join(homedir(), ".omp/agent/skills");
|
||||
const { skills: withTilde } = loadSkills({
|
||||
enableCodexUser: false,
|
||||
enableClaudeUser: false,
|
||||
enableClaudeProject: false,
|
||||
enablePiUser: false,
|
||||
enablePiProject: false,
|
||||
customDirectories: ["~/.pi/agent/skills"],
|
||||
customDirectories: ["~/.omp/agent/skills"],
|
||||
});
|
||||
const { skills: withoutTilde } = loadSkills({
|
||||
enableCodexUser: false,
|
||||
|
||||
@@ -77,7 +77,7 @@ export interface TestSessionContext {
|
||||
* Use this for e2e tests that need real LLM calls.
|
||||
*/
|
||||
export function createTestSession(options: TestSessionOptions = {}): TestSessionContext {
|
||||
const tempDir = join(tmpdir(), `pi-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
||||
const tempDir = join(tmpdir(), `omp-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
||||
mkdirSync(tempDir, { recursive: true });
|
||||
|
||||
const model = getModel("anthropic", "claude-sonnet-4-5")!;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
### Changed
|
||||
|
||||
- Updated configuration directory from `~/.pi/mom/` to `~/.omp/mom/` for auth and custom tools
|
||||
|
||||
## [2.3.1337] - 2026-01-03
|
||||
|
||||
@@ -60,7 +63,7 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
|
||||
|
||||
### Added
|
||||
|
||||
- Support for OAuth login via coding agent's `/login` command (link `~/.pi/agent/auth.json` to `~/.pi/mom/auth.json`)
|
||||
- Support for OAuth login via coding agent's `/login` command (link `~/.omp/agent/auth.json` to `~/.omp/mom/auth.json`)
|
||||
|
||||
## [0.20.2] - 2025-12-13
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ export MOM_SLACK_APP_TOKEN=xapp-...
|
||||
export MOM_SLACK_BOT_TOKEN=xoxb-...
|
||||
# Option 1: Anthropic API key
|
||||
export ANTHROPIC_API_KEY=sk-ant-...
|
||||
# Option 2: use /login command in pi agent, then copy/link auth.json to ~/.pi/mom/
|
||||
# Option 2: use /login command in omp agent, then copy/link auth.json to ~/.omp/mom/
|
||||
|
||||
# Create Docker sandbox (recommended)
|
||||
docker run -d \
|
||||
@@ -113,7 +113,7 @@ export ANTHROPIC_API_KEY=sk-ant-...
|
||||
- enter `/login` command
|
||||
- choose "Anthropic" provider
|
||||
- follow instructions in the browser
|
||||
- link `auth.json` to mom: `ln -s ~/.pi/agent/auth.json ~/.pi/mom/auth.json`
|
||||
- link `auth.json` to mom: `ln -s ~/.omp/agent/auth.json ~/.omp/mom/auth.json`
|
||||
|
||||
## How Mom Works
|
||||
|
||||
|
||||
@@ -754,7 +754,7 @@ export interface ToolAPI {
|
||||
Tools are discovered from:
|
||||
|
||||
1. `data/tools/**/index.ts` (workspace-local, recursive)
|
||||
2. `~/.pi/mom/tools/**/index.ts` (global, recursive)
|
||||
2. `~/.omp/mom/tools/**/index.ts` (global, recursive)
|
||||
|
||||
```typescript
|
||||
// loader.ts
|
||||
@@ -770,7 +770,7 @@ async function loadCustomTools(dataDir: string): Promise<LoadedTool[]> {
|
||||
const jiti = createJiti(import.meta.url, { alias: getAliases() });
|
||||
|
||||
// Discover tool directories
|
||||
const toolDirs = [path.join(dataDir, "tools"), path.join(os.homedir(), ".pi", "mom", "tools")];
|
||||
const toolDirs = [path.join(dataDir, "tools"), path.join(os.homedir(), ".omp", "mom", "tools")];
|
||||
|
||||
for (const dir of toolDirs) {
|
||||
if (!fs.existsSync(dir)) continue;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@oh-my-pi/pi-mom",
|
||||
"version": "2.3.1337",
|
||||
"description": "Slack bot that delegates messages to the pi coding agent",
|
||||
"description": "Slack bot that delegates messages to the omp coding agent",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"mom": "src/main.ts"
|
||||
|
||||
@@ -46,7 +46,7 @@ async function getAnthropicApiKey(authStorage: AuthStorage): Promise<string> {
|
||||
throw new Error(
|
||||
"No API key found for anthropic.\n\n" +
|
||||
"Set an API key environment variable, or use /login with Anthropic and link to auth.json from " +
|
||||
join(homedir(), ".pi", "mom", "auth.json"),
|
||||
join(homedir(), ".omp", "mom", "auth.json"),
|
||||
);
|
||||
}
|
||||
return key;
|
||||
@@ -446,7 +446,7 @@ async function createRunner(sandboxConfig: SandboxConfig, channelId: string, cha
|
||||
|
||||
// Create AuthStorage and ModelRegistry
|
||||
// Auth stored outside workspace so agent can't access it
|
||||
const authStorage = new AuthStorage(join(homedir(), ".pi", "mom", "auth.json"));
|
||||
const authStorage = new AuthStorage(join(homedir(), ".omp", "mom", "auth.json"));
|
||||
const modelRegistry = new ModelRegistry(authStorage);
|
||||
|
||||
// Create agent
|
||||
|
||||
@@ -330,7 +330,7 @@ export class TUI extends Container {
|
||||
const isImageLine = this.containsImage(line);
|
||||
if (!isImageLine && 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}`,
|
||||
|
||||
+10
-10
@@ -1,4 +1,4 @@
|
||||
# Pi Coding Agent Installer for Windows
|
||||
# OMP Coding Agent Installer for Windows
|
||||
# Usage: irm https://raw.githubusercontent.com/can1357/oh-my-pi/main/scripts/install.ps1 | iex
|
||||
#
|
||||
# Or with options:
|
||||
@@ -13,9 +13,9 @@ param(
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$Repo = "can1357/oh-my-pi"
|
||||
$Package = "@oh-my-pi/pi-coding-agent"
|
||||
$InstallDir = if ($env:PI_INSTALL_DIR) { $env:PI_INSTALL_DIR } else { "$env:LOCALAPPDATA\pi" }
|
||||
$BinaryName = "pi-windows-x64.exe"
|
||||
$Package = "@oh-my-pi/omp-coding-agent"
|
||||
$InstallDir = if ($env:OMP_INSTALL_DIR) { $env:OMP_INSTALL_DIR } else { "$env:LOCALAPPDATA\omp" }
|
||||
$BinaryName = "omp-windows-x64.exe"
|
||||
|
||||
function Test-BunInstalled {
|
||||
try {
|
||||
@@ -37,8 +37,8 @@ function Install-ViaBun {
|
||||
Write-Host "Installing via bun..."
|
||||
bun install -g $Package
|
||||
Write-Host ""
|
||||
Write-Host "✓ Installed pi via bun" -ForegroundColor Green
|
||||
Write-Host "Run 'pi' to get started!"
|
||||
Write-Host "✓ Installed omp via bun" -ForegroundColor Green
|
||||
Write-Host "Run 'omp' to get started!"
|
||||
}
|
||||
|
||||
function Install-Binary {
|
||||
@@ -53,20 +53,20 @@ function Install-Binary {
|
||||
Write-Host "Downloading $BinaryName..."
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
|
||||
$OutPath = Join-Path $InstallDir "pi.exe"
|
||||
$OutPath = Join-Path $InstallDir "omp.exe"
|
||||
Invoke-WebRequest -Uri $Url -OutFile $OutPath
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "✓ Installed pi to $OutPath" -ForegroundColor Green
|
||||
Write-Host "✓ Installed omp to $OutPath" -ForegroundColor Green
|
||||
|
||||
# Add to PATH if not already there
|
||||
$UserPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
||||
if ($UserPath -notlike "*$InstallDir*") {
|
||||
Write-Host "Adding $InstallDir to PATH..."
|
||||
[Environment]::SetEnvironmentVariable("Path", "$UserPath;$InstallDir", "User")
|
||||
Write-Host "Restart your terminal, then run 'pi' to get started!"
|
||||
Write-Host "Restart your terminal, then run 'omp' to get started!"
|
||||
} else {
|
||||
Write-Host "Run 'pi' to get started!"
|
||||
Write-Host "Run 'omp' to get started!"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user