The earlier dry-run change moved scope into an options object, silently
ignoring the legacy uninstallPlugin(id, "user") runtime shape still reachable
from compiled or plain-JS callers. Restore scope as the positional second
argument and carry dryRun in a trailing options bag, preserving the existing
call shape while keeping the non-mutating dry-run path.
Fixes#8178
Route marketplace dry-runs through MarketplaceManager's normal pre-mutation
validation so ambiguous or mismatched scopes fail exactly as real uninstalls
do. Move the manager's scope argument into an options object and add a dry-run
option that returns only after all removal planning has succeeded.
Fixes#8178
handleUninstall dropped the parsed dryRun flag and unconditionally called
mktMgr.uninstallPlugin / manager.uninstall, so `omp plugin uninstall
<plugin> --dry-run` removed the plugin on both the marketplace and npm
routes. Propagate dryRun into handleUninstall and short-circuit before
both removal calls, reporting the resolved removal (with its source)
instead of mutating state.
Fixes#8178
- Implemented in-house, zero-dependency utility modules in `pi-utils` covering DOM manipulation, markdown parsing, templating, browser automation helpers, and terminal buffers.
- Migrated packages across the repository to consume the new internal utilities and `omptype` schema validators instead of external dependencies.
- Removed multiple external runtime and development dependencies including Zod, Marked, LRU cache, Turndown, and Puppeteer browser packages.
`omp plugin install .` (and any cwd-relative, absolute, or tilde-prefixed
spec) failed with `Invalid package name: .` because
`classifyInstallTarget` only emitted `marketplace` / `npm`, so local paths
fell through to `validatePackageName`, which rejects every non-npm name.
The classifier now emits a third `local` arm for `.`, `..`, `./…`,
`..\…`, `~`, `~/…`, `~\…`, `/…`, `C:\…`, `C:/…`, and `\\unc` specs.
`handleInstall` dispatches that arm to `PluginManager.link()` — the same
code path as `omp plugin link <path>` — so the two verbs are
interchangeable for local plugin directories. `--dry-run` short-circuits
before any filesystem work, `--scope`/`--force` surface a warning since
they are no-ops here (link is idempotent, and scope only governs
marketplace installs).
Coverage: thirteen-case classifier matrix in `marketplace/cli.test.ts`
plus a new `plugin-install-local.test.ts` with spy-based routing checks
for `./`, `../`, `/`, `~/`, plus a real-filesystem test that stages a
plugin folder, invokes `runPluginCommand`, and verifies the resulting
symlink + lockfile entry. The new test calls `mock.restore()` in
`afterEach` so `piUtils` / `MarketplaceManager.prototype` spies do not
leak into sibling suites.
Fixes#1945
Extends `omp plugin install` to accept git sources alongside npm specs and
marketplace refs. Bun's installer already understands git URLs; the blocker
was `PluginManager.install`'s strict npm-name validator and the assumption
that the actual package name could be derived from the spec.
- `git-url.ts`: `parseGitUrl` now recognizes npm-style namespaced shorthand
(`github:user/repo`, `gitlab:`, `bitbucket:`, `codeberg:`, `sourcehut:` /
`srht:`), with optional `#ref` and `.git` suffix. Exposes `isGitSpec` as
`parseGitUrl(s) !== null`. Existing protocol-URL and `git:` shorthand paths
are untouched.
- `manager.ts`: `install()` branches on `isGitSpec`. Git specs go through a
separate `validateGitSpec` (shell-metachar rejection only — `/`, `:`, `@`,
`#`, `+` are legal) and the real package name is discovered by snapshotting
`plugins/package.json` deps before `bun install` and diffing afterwards.
Falls back to value-match on force-reinstall where the key already exists.
- Help text in `plugin-cli` documents the new sources and adds a github:
example.
Smoke tested end-to-end on Windows with both forms against the test repo:
PluginManager.install('github:oldschoola/omp-insights')
PluginManager.install('https://github.com/oldschoola/omp-insights')
both resolve `@oldschoola/omp-insights@1.2.3` and write a correct lock entry.
Shell-injection probe (`github:foo/bar; rm -rf /`) is rejected.
- Standardized missing-file read errors and now return `File not found: <path>` for absent edit targets.
- Centralized AI provider, usage, and OAuth helpers into shared modules to remove duplicated logic.
- Migrated OAuth/API-key login flows to shared factory helpers and removed inline prompt/token-exchange code.
- Reused shared tools and formatter utilities for discovery, stream tails, LSP batching, and source formatting.
- Consolidated repeated test helpers and fixtures into shared modules, replacing inline helper duplicates.
Plugins can now be installed at user scope (global) or project scope
(per-project, higher capability priority). Scope is encoded in registry
file location, not a metadata field:
user: ~/.omp/plugins/installed_plugins.json
project: <nearest-project>/.omp/plugins/installed_plugins.json
cache: ~/.omp/plugins/cache/plugins/ (shared, path-referenced)
Project root discovery: resolveActiveProjectRegistryPath(cwd) walks up
from cwd looking for the nearest .omp/ directory, falling back to the
nearest .git root. This is the single resolver used by install, uninstall,
list, upgrade, discovery, and doctor.
Discovery: listClaudePluginRoots(home, cwd?) reads both registries when
cwd is provided. Project entries shadow user entries for the same plugin
ID. Cache key is canonical ("${home}:${resolvedProjectPath}") so nested
cwds within the same project share a cache entry.
Manager changes:
- installPlugin({ scope? }): routes registry reads/writes by scope;
checks collectReferencedPaths() across both registries before deleting
any cached plugin dir to prevent cross-scope data loss
- uninstallPlugin(id, scope?), setPluginEnabled(id, enabled, scope?),
upgradePlugin(id, scope?): throw a disambiguation error when the plugin
exists in both scopes and no scope is specified
- upgradePluginAcrossScopes(id): new; upgrades all scopes the plugin is
installed in; returns InstalledPluginEntry[]
- upgradeAllPlugins(): uses upgradePluginAcrossScopes; result includes scope
- listInstalledPlugins(): returns InstalledPluginSummary[] merged from both
registries; user entries marked shadowedBy: "project" when overridden
CLI: omp plugin install|uninstall|upgrade|enable|disable --scope user|project
Slash: /marketplace install [--scope user|project] name@marketplace
MarketplaceManager constructed with projectInstalledRegistryPath in all
CLI handlers and builtin-registry.ts via resolveActiveProjectRegistryPath.
.gitignore: .omp/plugins/ added (local runtime state, not committed).
preloadPluginRoots/clearClaudePluginRootsCache carry cwd through for LSP.
main.ts passes getProjectDir() at startup.
Tests: 226 pass across 13 files. New: project-scope.test.ts (resolver
walk-up, .git fallback, null return, canonical path, shadow precedence);
manager scope tests (registry isolation, disambiguation errors, cross-scope
cache-ref protection, upgradePluginAcrossScopes, shadowedBy marking).
fixes#581
Check installed_plugins.json directly instead of relying on the
marketplace being registered. Handles the case where a marketplace
entry is removed but its plugins are still installed.
- handleUninstall now classifies targets and routes marketplace
specs to MarketplaceManager.uninstallPlugin()
- handleList shows both npm and marketplace plugins grouped by type
Route marketplace install targets (name@marketplace) to
MarketplaceManager.installPlugin() instead of npm install.
classifyInstallTarget() was implemented but never called from
the install handler.
- Consolidated @oh-my-pi/pi-utils subpath imports into single package root import across 100+ files.
- Moved tryParseJson utility from local web scrapers module to @oh-my-pi/pi-utils package for centralized JSON parsing.
- Renamed loadSkillsFromDir to scanSkillsFromDir and refactored skill discovery to use fs.promises.readdir instead of glob-based approach.
- Replaced custom parseJSON with tryParseJson across discovery modules for consistent error handling.
- Removed emitCustomToolSessionEvent method and cleanupSshResources function, consolidating shutdown logic into dispose method.
- Updated glob pattern construction to use GlobBuilder with literal_separator(true) for improved path handling.
- Extracted directory path utilities from multiple packages into a centralized '@oh-my-pi/pi-utils/dirs' module.
- Moved 30+ path helper functions (getAgentDir, getConfigRootDir, getPluginsDir, getMCPConfigPath, etc.) from scattered locations into a single shared utility module.
- Consolidated APP_NAME, CONFIG_DIR_NAME, and VERSION constants into the centralized dirs module for reuse across packages.
- Updated 70+ import statements across packages/ai, packages/coding-agent, packages/stats, and packages/tui to use the new centralized module.
- Removed local path construction logic and replaced with utility function calls for improved maintainability and consistency.
- Deleted packages/coding-agent/src/extensibility/plugins/paths.ts as its functions were moved to the centralized dirs module.
- Removed Prettier configuration files (.prettierignore and .prettierrc) and migrated formatting to Biome.
- Updated Biome configuration from version 2.3.11 to 2.3.12 and changed arrowParentheses rule from 'always' to 'asNeeded'.
- Pinned @biomejs/biome dependency to exact version 2.3.12 in package.json and bun.lock.
- Applied consistent arrow function formatting across 489 files by removing unnecessary parentheses around single parameters.
- Removed blank lines after comment blocks and reorganized imports for consistency across the codebase.
- Converted readdirSync, readFileSync, and statSync to async readdir, readFile, stat across skills and agent discovery.
- Made scanDirectoryForSkills async and refactored custom directory scanning to use Promise.all for concurrent processing.
- Updated agent discovery to use fs/promises for async file reading and refactored helper patterns.
- Added AgentParsingError exception class for better error handling during agent parsing.
- Added filesystem error type guards (isEnoent, isEacces, isPerm, etc.) to pi-utils for safe error checking.
- Added color manipulation utilities to pi-utils for accessibility features.
- Added color-blind mode setting to settings manager.
- Migrated plugins, settings, and config modules from sync to async file operations.
- Updated error handling to use new pi-utils type guards for type-safe checking.
- Removed WASM generation script; use Bun `wasm?raw` loader for imports.
- Added bunfig.toml with loaders for `.md`, `.py`, and `.wasm?raw` text imports.
- Added types/assets/index.d.ts for global TypeScript module declarations.
- Unified TypeScript configuration with tsgo-based checking across monorepo.
- Removed build and WASM steps from install and publish pipelines.
- Added tsconfig.publish.json files to all packages with optimized publish-time configuration.
- Updated all package.json scripts with prepublishOnly hooks for correct type checking during publish.
- Added @oh-my-pi/omp-stats path mappings to root tsconfig.json for consistent imports.
- Added WASM generation script for photon module and integrated into install:dev script.
- converted relative imports to path aliases ($c/*, $ai/*, $tui/*, etc.) across all packages
- added per-package tsconfig.json with complete path mappings for runtime resolution
- set importModuleSpecifier to non-relative for IDE auto-import preferences
- updated dev script to run from monorepo root for consistent path resolution
- Added 30+ built-in color themes and comprehensive symbol customization via theme presets and overrides.
- Added status line segment editor with presets, separators, and customizable left/right segments.
- Added Biome CLI-based linter client as alternative to LSP for diagnostics and formatting.
- Added 20MB file size limit for image files to prevent memory issues during serialization.
- Replaced hardcoded Unicode symbols throughout codebase with theme-configurable symbol system.
- 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.
- Added MCP (Model Context Protocol) support with stdio and HTTP transports for external tool integration.
- Implemented plugin system with install, enable, configure, and doctor commands for extensibility.
- Added 22 Exa MCP tools for web search, LinkedIn, company research, and websets operations.
- Introduced LSP tool with rust-analyzer support for code intelligence (diagnostics, hover, references, code actions).
- Added task tool for delegating work to specialized subagents with parallel execution support.
- Implemented web_search and web_fetch tools with Anthropic and Perplexity provider support.