Commit Graph

26 Commits

Author SHA1 Message Date
can1357 2ad61c7b92 feat(discovery): added Agent Plugins 1.0.0 standard support
- New agent-plugins provider discovers packages with a root plugin.json
  targeting the canonical schema (agent-plugins.org) from marketplace
  installs, --plugin-dir, and configured extension roots; skills/ and
  mcp.json load per spec with closed-schema validation,
  ${PLUGIN_ROOT}/${PLUGIN_DATA} expansion, reserved subprocess
  environment, instance-keyed data dirs, and per-component isolation.
- Package-boundary containment (spec §4.1) is enforced before every
  read via the new contained-path helpers, including skill:// resource
  access from the read tool and bash; plugin skill files must
  realpath-resolve inside the plugin root (skills carry containRoot).
- Legacy claude-plugins/omp-plugins providers yield skills and MCP
  surfaces of standard-targeting roots to the new provider and skip
  fatally invalid packages.
2026-08-07 05:59:52 +02:00
can1357 b778617178 chore: reformat + rewrite changelogs 2026-07-28 11:19:34 +02:00
can1357 d1de4658aa fix(discovery): warn when a plugin mcpServers pointer names a missing file 2026-07-28 10:59:35 +02:00
roboomp 4f63e4e521 fix(discovery): honored inline plugin MCP manifest maps
Treated an object-valued mcpServers manifest field as the server map, rooting relative stdio paths at the plugin root, so plugins declaring servers inline no longer fell through to .mcp.json.

Fixes #6871
2026-07-28 07:24:49 +00:00
roboomp 8f31a123d6 fix(discovery): honored plugin MCP manifest pointers
Resolved mcpServers file pointers from OMP and Claude plugin manifests before the conventional root config fallback.

Updated marketplace installer documentation for runtime symlink and lockfile registration.

Fixes #6871
2026-07-28 07:12:46 +00:00
roboomp 6467a3119e fix(discovery): rooted relative plugin mcp command and cwd at config dir
Discovered plugin .mcp.json stdio servers launched relative command/cwd
values against the session cwd instead of the plugin's config directory,
breaking bundled ChatGPT/Codex plugins (e.g. Computer Use) with ENOENT
when spawning ./... from an unrelated cwd.

The claude-plugins and omp-plugins providers now resolve relative cwd and
path-like command (./ or ../) against the .mcp.json directory via a shared
resolvePluginStdioPaths helper; bare executables such as npx are left
untouched so PATH lookup still works.

Fixes #5330
2026-07-14 18:36:21 +00:00
can1357 7e4360d317 fix(discovery): preserve marketplace-root skill selection 2026-07-08 15:19:38 +02:00
roboomp c92cf0a905 fix(discovery): load Claude plugin skill paths that point at a SKILL.md directory
Claude plugin manifests may declare a `skills` path that resolves directly to a
directory whose `SKILL.md` IS the skill (e.g. `"skills": ["./"]` or a
subdirectory containing only `SKILL.md`). `scanSkillsFromDir` only scanned
`<dir>/<name>/SKILL.md` children, so the single-skill directory layout — the
common shape the Claude plugins reference documents for plugins shipping one
skill — silently dropped every array-form manifest entry that pointed at it.

Add an opt-in `includeSelf` flag to `ScanSkillsFromDirOptions`: when set,
`<dir>/SKILL.md` (if present) is loaded as a skill in addition to the existing
child scan. The Claude plugin skills loader opts in; every other provider
(agents, builtin, claude.ts, codex, github, omp-plugins, opencode) keeps the
strict child-scan semantic they rely on. Frontmatter `name` still wins over
the directory basename fallback.

Regression test: `skills: ["./single"]` where `./single/SKILL.md` is the only
skill file loads the skill under its frontmatter name.
2026-07-05 12:23:18 +00:00
roboomp c42d2b2907 fix(discovery): load file-form Claude plugin commands
Claude plugin manifests allow command path entries to name either directories
or flat `.md` command files. The array resolver was now preserving those file
paths, but `loadSlashCommands` still sent every resolved entry through
`loadFilesFromDir`, which only globs inside directories. A manifest such as
`{"commands":["./custom/deploy.md"]}` therefore replaced the default scan
and then loaded nothing.

Teach the command loader to stat each resolved entry: `.md` files are read as
single slash commands with the same plugin namespace and source metadata as
directory-loaded files; directories continue through `loadFilesFromDir`.
Missing entries keep the existing silent-empty behavior.

Add a regression test covering a mixed array of a direct command file and a
command directory while proving default `commands/` remains replaced unless
listed explicitly.
2026-07-05 12:16:02 +00:00
roboomp f276d80fc4 fix(discovery): honour per-field Claude plugin path merge semantics
Review feedback on #4610: array-form skills was silently replacing the
default `skills/` scan when the manifest declared any explicit entries.
Per the Claude plugins reference "Path behavior rules"
(https://code.claude.com/docs/en/plugins-reference#path-behavior-rules):

- `skills` ADDS to the default `skills/` scan
- `commands` / `slash-commands` REPLACE the default `commands/` scan

`resolvePluginDir` now takes an explicit `includeFallback` flag. `loadSkills`
passes `true` (fallback + declared entries, deduped by resolved absolute
path so a manifest may still list `./skills` alongside extras without
double-load); `loadSlashCommands` passes `false` (replace semantic
preserved). Deduplication keeps the fallback first and declared entries
in manifest order.

Regression tests cover both semantics: skills-array merges with default
`skills/`; commands-array replaces default `commands/` so a stray
`commands/default.md` no longer loads once the manifest declares an
alternative — matching Claude's documented behavior.
2026-07-05 12:09:22 +00:00
roboomp d9ef874e2b fix(discovery): accept array-form Claude plugin manifest paths
The Claude plugin manifest allows `commands`, `slash-commands`, and `skills`
to be either a single string or an array of strings — documented under
https://code.claude.com/docs/en/plugins-reference#path-behavior-rules and
used by real marketplace plugins such as addyosmani/agent-skills whose
plugin.json declares `"commands": ["./.claude/commands", "./commands"]`.

`resolvePluginDir` in `packages/coding-agent/src/discovery/claude-plugins.ts`
typed those manifest fields as `string` only, so array-shaped values were
silently dropped: no items loaded, no warning surfaced. Slash commands
(`spec`, `plan`, `build`, `test`, `review`) never appeared in the picker.

Normalize `string | string[]` at the resolver, load every in-root entry,
and emit one out-of-plugin-root warning per bad entry so misconfigured
paths remain observable. Skills and slash-command loaders now fan out over
the resolved directory list and merge warnings from all sources.

Fixes #4609
2026-07-05 12:02:47 +00:00
roboomp 7289cd4baf fix(mcp): expanded plugin env headers
Expanded environment variable placeholders in Claude marketplace plugin MCP url and headers before registration. Added a regression test covering context7-style HTTP server headers.\n\nFixes #3621
2026-06-27 03:16:45 +00:00
roboomp b21385d13c fix(coding-agent): hid plugin skills from slash commands
Removed Claude marketplace plugin skill mirroring from slash-command discovery while keeping real plugin commands registered.\n\nFixes #2645
2026-06-15 12:52:12 +00:00
roboomp fde233278c fix(cli): synthesize plugin skill slash commands from directory basename
When a Claude plugin skill's SKILL.md frontmatter uses a display-style
name (e.g. `name: Understand Anything`), the synthesized slash command
inherited that text. `expandSlashCommand` splits the command at the
first whitespace, so `/understand` never resolved and the multi-word
form could not expand either.

Use `path.basename(path.dirname(skill.path))` so the slash command
always matches the documented `skills/<name>/SKILL.md` → `/<name>`
contract while the frontmatter still drives the description/body.

Fixes #2415
2026-06-14 07:12:42 +00:00
roboomp 0b444b83bf fix(cli): exposed claude plugin skills as slash commands
Loaded Claude Code marketplace plugin SKILL.md files into the slash-command capability so Claude-native plugin docs like /understand work in autocomplete and invocation.\n\nAdded a regression covering OMP installed plugin registries and bare slash-command expansion.\n\nFixes #2415
2026-06-12 19:28:35 +00:00
JacobZyy 8790736173 fix: remove plugin: prefix from marketplace skill names
The plugin:name prefix (e.g. hyperpiemia:whistle-rules) broke skill://
URL parsing because colons are ambiguous with port separators. Skills
from marketplace plugins are now registered under their plain names.
Name collisions are handled by the existing capability-layer dedup
based on provider priority ordering.
2026-06-02 15:36:22 +08:00
roboomp 8108394516 fix(discovery): honor manifest commands key for claude plugin slash commands
The ClaudePluginManifest interface was missing the `commands` field
(the standard Claude plugin format), and resolvePluginDir only checked
the legacy `slash-commands` key. Plugins declaring their command path
via `"commands": "..."` silently fell back to the hardcoded
`<plugin-root>/commands/` directory, which doesn't exist for
Claude-format plugins, so no slash commands were ever loaded.

Changes:
- Added `commands?: string` to ClaudePluginManifest.
- Changed resolvePluginDir to accept ReadonlyArray<keyof
  ClaudePluginManifest> and iterate in priority order; the first
  non-empty match wins.
- loadSlashCommands now passes ["commands", "slash-commands"] so
  the canonical Claude plugin key takes precedence over the legacy one.
- Added two regression tests: one covering the `commands` key in
  isolation, one verifying its precedence over `slash-commands` when
  both fields are present.

Fixes #1076
2026-05-15 02:10:22 +00:00
can1357 74ce4ab33e fix(coding-agent): support flat .mcp.json shape from Claude marketplace plugins
Claude marketplace plugins (e.g. context7@claude-plugins-official) ship
.mcp.json with the server map at the top level rather than under the
mcpServers key. The loader only accepted the nested shape, so install
appeared to succeed but no MCP tools were registered. Detect both shapes
and validate that each entry declares command (stdio) or url (HTTP/SSE)
before registering.

Fixes #851
2026-04-30 04:51:23 +02:00
Parsifa1 782a309f6f fix(coding-agent): fix untrusted path resolve 2026-04-23 22:51:01 +02:00
Parsifa1 e56a33c857 fix(coding-agent): honor claude plugin manifest paths 2026-04-23 22:51:01 +02:00
Miroslav Drbal 05966c8910 feat(coding-agent): marketplace plugin project scope
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
2026-03-31 17:43:09 +02:00
Miroslav Drbal 4e199f93f7 feat: add marketplace plugin system
Add Claude Code-compatible marketplace plugin infrastructure:

- Registry: types, ID helpers, atomic read/write for marketplaces.json
  and installed_plugins.json (Claude Code format with version: 2)
- Fetcher: classifySource (6 ordered rules), parseMarketplaceCatalog,
  fetchMarketplace (local sources; git/http stubs for Phase 2)
- Resolver + Cache: resolvePluginSource with pathIsWithin containment,
  cachePlugin, removeCachedPlugin, cleanOrphanedCache
- MarketplaceManager: orchestrates add/remove/update marketplaces,
  install/uninstall/enable plugins, clearPluginRootsCache on mutation
- CLI: omp plugin marketplace add|remove|update|list,
  omp plugin discover, classifyInstallTarget for name@marketplace
- Discovery: listClaudePluginRoots reads OMP registry alongside
  Claude's, OMP authoritative for duplicate plugin IDs
- Args: --plugin-dir repeatable flag (parsing only, runtime wiring TBD)
- Slash command: /reload-plugins clears fs + roots cache
- Test fixtures and 130 tests across 8 test files
2026-03-30 13:44:53 +02:00
can1357 a83175c94c refactor: migrated imports to unified package root and consolidated skill discovery logic
- 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.
2026-02-23 20:59:17 +01:00
can1357 74344eca1b fix: corrected tool description normalization to prevent undefined field errors
- Added fallback empty string for undefined tool descriptions across all AI providers to prevent runtime errors.
- Made `description` field required in CustomTool interface and normalized tool descriptions in agent-loop.
- Refactored tool normalization in agent-loop by renaming `injectIntentIntoTools()` to `normalizeTools()` with conditional intent injection.
- Added comprehensive description metadata to todo-write tool schema fields for improved clarity.
2026-02-23 17:12:59 +01:00
kylesnowschwartz 7a6b42353e fix(coding-agent): respect plugin scope and handle multiple registry entries
- P1: Use root.scope instead of hardcoded 'user' level in claude-plugins provider
- P2: Iterate all registry entries per plugin ID, not just first one
- P3: Add home parameter to discoverAgents() for test isolation
- P4: Add test coverage for claude-plugins discovery (12 tests)
- P5: Cache listClaudePluginRoots results to avoid repeated parsing

Addresses issues found in adversarial review of PR #49
2026-02-13 11:11:05 +13:00
kylesnowschwartz 842bf84a2b feat(coding-agent): add Claude Code marketplace plugin discovery
Add discovery provider that loads skills, commands, hooks, tools, and
agents from ~/.claude/plugins/cache/ based on installed_plugins.json.

- Add parseClaudePluginsRegistry() and listClaudePluginRoots() helpers
- Create claude-plugins.ts provider with priority 70 (below claude.ts)
- Register provider for skills, slash-commands, hooks, and tools
- Integrate plugin agents into task/discovery.ts

Closes #48
2026-02-13 10:23:15 +13:00