@oh-my-pi/pi-coding-agent 13.16.5 → 13.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/package.json +7 -7
- package/src/cli/args.ts +7 -0
- package/src/cli/classify-install-target.ts +50 -0
- package/src/cli/plugin-cli.ts +245 -31
- package/src/commands/plugin.ts +3 -0
- package/src/config/settings-schema.ts +12 -13
- package/src/cursor.ts +66 -1
- package/src/discovery/claude-plugins.ts +95 -5
- package/src/discovery/helpers.ts +168 -41
- package/src/discovery/plugin-dir-roots.ts +28 -0
- package/src/discovery/substitute-plugin-root.ts +29 -0
- package/src/extensibility/plugins/index.ts +1 -0
- package/src/extensibility/plugins/marketplace/cache.ts +136 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +354 -0
- package/src/extensibility/plugins/marketplace/index.ts +6 -0
- package/src/extensibility/plugins/marketplace/manager.ts +528 -0
- package/src/extensibility/plugins/marketplace/registry.ts +181 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
- package/src/extensibility/plugins/marketplace/types.ts +177 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/local-protocol.ts +2 -19
- package/src/internal-urls/parse.ts +72 -0
- package/src/internal-urls/router.ts +2 -18
- package/src/lsp/config.ts +9 -0
- package/src/main.ts +50 -1
- package/src/modes/components/plugin-selector.ts +86 -0
- package/src/modes/components/settings-defs.ts +0 -4
- package/src/modes/controllers/mcp-command-controller.ts +14 -0
- package/src/modes/controllers/selector-controller.ts +104 -13
- package/src/modes/interactive-mode.ts +4 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/agents/reviewer.md +3 -4
- package/src/sdk.ts +0 -7
- package/src/slash-commands/builtin-registry.ts +273 -0
- package/src/tools/bash-skill-urls.ts +48 -5
- package/src/tools/read.ts +15 -9
- package/src/web/search/code-search.ts +2 -179
- package/src/web/search/index.ts +2 -3
- package/src/web/search/types.ts +1 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.17.0] - 2026-03-30
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `marketplace.autoUpdate` setting (`off`/`notify`/`auto`, default `notify`) for automatic plugin update checking on startup
|
|
10
|
+
- Added background marketplace catalog refresh on startup when catalogs are stale (>24h)
|
|
11
|
+
- Added `/marketplace upgrade [name@marketplace]` slash command to upgrade outdated plugins
|
|
12
|
+
- Added `omp plugin upgrade [name@marketplace]` CLI command for plugin upgrades
|
|
13
|
+
- Added `checkForUpdates()`, `upgradePlugin()`, `upgradeAllPlugins()`, and `refreshStaleMarketplaces()` to MarketplaceManager
|
|
14
|
+
- Added marketplace plugin system: registry types, ID helpers, atomic read/write for `marketplaces.json` and `installed_plugins.json` (Claude Code-compatible format)
|
|
15
|
+
- Added `MarketplaceManager` orchestrator for marketplace and plugin lifecycle (add/remove/update marketplaces, install/uninstall/enable plugins)
|
|
16
|
+
- Added marketplace fetcher with source classification (GitHub, git, URL, local) and catalog validation
|
|
17
|
+
- Added plugin source resolver with `pathIsWithin` containment checks and versioned cache manager
|
|
18
|
+
- Added CLI commands: `omp plugin marketplace add|remove|update|list`, `omp plugin discover [marketplace]`
|
|
19
|
+
- Added `classifyInstallTarget()` to distinguish `name@marketplace` from npm install targets
|
|
20
|
+
- Extended `listClaudePluginRoots()` to read OMP's installed plugins registry alongside Claude Code's, with OMP as authoritative for duplicate plugin IDs
|
|
21
|
+
- Added `--plugin-dir <path>` repeatable CLI flag for loading plugins from local directories
|
|
22
|
+
- Added `/reload-plugins` slash command that invalidates fs content cache and plugin roots cache
|
|
23
|
+
- Added `printPluginHelp()` entries for marketplace and discover commands
|
|
24
|
+
- Added MCP server loading from marketplace plugin `.mcp.json` files with `${CLAUDE_PLUGIN_ROOT}` variable substitution
|
|
25
|
+
- Added skill and command namespacing for marketplace plugins (`plugin-name:skill-name`)
|
|
26
|
+
- Added LSP config loading from marketplace plugin roots via `getPreloadedPluginRoots()`
|
|
27
|
+
- Wired `--plugin-dir` runtime injection into plugin roots at session startup with highest precedence
|
|
28
|
+
- Added git (GitHub, SSH, HTTPS) and HTTP URL marketplace source fetching
|
|
29
|
+
- Added `/marketplace` TUI slash command with subcommands: add, remove, update, list, discover, install, uninstall, installed
|
|
30
|
+
- Added `/plugins` TUI slash command to view all installed plugins (npm + marketplace) and enable/disable marketplace plugins
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Changed marketplace clone promotion to occur after duplicate and drift checks, improving safety of concurrent marketplace operations
|
|
35
|
+
|
|
36
|
+
### Removed
|
|
37
|
+
|
|
38
|
+
- Removed grep.app code search provider support; code search now uses Exa exclusively
|
|
39
|
+
- Removed `providers.codeSearch` setting and related configuration options
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- Fixed git-subdir plugin source resolution to properly clean up temporary clone directories on path validation errors
|
|
44
|
+
- Fixed LSP config loading to use correct filenames variable when scanning plugin roots
|
|
45
|
+
- Fixed plugin selector UI to request render on cancel, preventing stale display state
|
|
46
|
+
- Fixed marketplace install command error handling to display user-friendly error messages instead of crashing
|
|
47
|
+
- Fixed MCP tools from newly added servers not being activated after `/mcp add` — `refreshMCPTools` preserves prior MCP tool selections, so brand-new servers had their tools registered in the registry but never passed to the agent; tools are now explicitly activated on successful connection
|
|
48
|
+
- Fixed `skill://` URI resolver to handle namespaced skills via longest-prefix matching against registered skill names
|
|
49
|
+
|
|
5
50
|
## [13.16.5] - 2026-03-29
|
|
6
51
|
|
|
7
52
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "13.
|
|
4
|
+
"version": "13.17.0",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
44
44
|
"@mozilla/readability": "^0.6",
|
|
45
|
-
"@oh-my-pi/omp-stats": "13.
|
|
46
|
-
"@oh-my-pi/pi-agent-core": "13.
|
|
47
|
-
"@oh-my-pi/pi-ai": "13.
|
|
48
|
-
"@oh-my-pi/pi-natives": "13.
|
|
49
|
-
"@oh-my-pi/pi-tui": "13.
|
|
50
|
-
"@oh-my-pi/pi-utils": "13.
|
|
45
|
+
"@oh-my-pi/omp-stats": "13.17.0",
|
|
46
|
+
"@oh-my-pi/pi-agent-core": "13.17.0",
|
|
47
|
+
"@oh-my-pi/pi-ai": "13.17.0",
|
|
48
|
+
"@oh-my-pi/pi-natives": "13.17.0",
|
|
49
|
+
"@oh-my-pi/pi-tui": "13.17.0",
|
|
50
|
+
"@oh-my-pi/pi-utils": "13.17.0",
|
|
51
51
|
"@sinclair/typebox": "^0.34",
|
|
52
52
|
"@xterm/headless": "^6.0",
|
|
53
53
|
"ajv": "^8.18",
|
package/src/cli/args.ts
CHANGED
|
@@ -38,6 +38,7 @@ export interface Args {
|
|
|
38
38
|
hooks?: string[];
|
|
39
39
|
extensions?: string[];
|
|
40
40
|
noExtensions?: boolean;
|
|
41
|
+
pluginDirs?: string[];
|
|
41
42
|
print?: boolean;
|
|
42
43
|
export?: string;
|
|
43
44
|
noSkills?: boolean;
|
|
@@ -151,6 +152,9 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
|
|
|
151
152
|
} else if ((arg === "--extension" || arg === "-e") && i + 1 < args.length) {
|
|
152
153
|
result.extensions = result.extensions ?? [];
|
|
153
154
|
result.extensions.push(args[++i]);
|
|
155
|
+
} else if (arg === "--plugin-dir" && i + 1 < args.length) {
|
|
156
|
+
result.pluginDirs = result.pluginDirs ?? [];
|
|
157
|
+
result.pluginDirs.push(args[++i]);
|
|
154
158
|
} else if (arg === "--no-extensions") {
|
|
155
159
|
result.noExtensions = true;
|
|
156
160
|
} else if (arg === "--no-skills") {
|
|
@@ -262,6 +266,9 @@ ${chalk.bold("Available Tools (default-enabled unless noted):")}
|
|
|
262
266
|
web_search - Search the web
|
|
263
267
|
ask - Ask user questions (interactive mode only)
|
|
264
268
|
|
|
269
|
+
${chalk.bold("Plugin Options:")}
|
|
270
|
+
--plugin-dir <path> Load plugin from directory (repeatable)
|
|
271
|
+
|
|
265
272
|
${chalk.bold("Useful Commands:")}
|
|
266
273
|
omp agents unpack - Export bundled subagents to ~/.omp/agent/agents (default)
|
|
267
274
|
omp agents unpack --project - Export bundled subagents to ./.omp/agents`;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify an install spec as a marketplace plugin reference or a plain npm package.
|
|
3
|
+
*
|
|
4
|
+
* Rules (applied in order):
|
|
5
|
+
* 1. Starts with `@` (scoped npm) -> always npm.
|
|
6
|
+
* 2. Contains `@` after the first character -> split on the LAST `@`.
|
|
7
|
+
* If the right-hand side is a known marketplace name, it's a marketplace ref.
|
|
8
|
+
* Otherwise it's an npm spec (e.g. `pkg@1.2.3`).
|
|
9
|
+
* 3. No `@` -> npm.
|
|
10
|
+
*/
|
|
11
|
+
// Common npm dist-tags that should never be interpreted as marketplace names
|
|
12
|
+
const NPM_DIST_TAGS = new Set([
|
|
13
|
+
"latest",
|
|
14
|
+
"next",
|
|
15
|
+
"beta",
|
|
16
|
+
"alpha",
|
|
17
|
+
"canary",
|
|
18
|
+
"rc",
|
|
19
|
+
"dev",
|
|
20
|
+
"stable",
|
|
21
|
+
"nightly",
|
|
22
|
+
"experimental",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
// Semver-like: starts with digit, or contains version range prefixes
|
|
26
|
+
const LOOKS_LIKE_VERSION = /^[\d~^>=<]/;
|
|
27
|
+
|
|
28
|
+
export function classifyInstallTarget(
|
|
29
|
+
spec: string,
|
|
30
|
+
knownMarketplaces: Set<string>,
|
|
31
|
+
): { type: "marketplace"; name: string; marketplace: string } | { type: "npm"; spec: string } {
|
|
32
|
+
// Rule 1: scoped npm package — @ at position 0 is never a marketplace separator.
|
|
33
|
+
if (spec.startsWith("@")) return { type: "npm", spec };
|
|
34
|
+
// Rule 2: @ somewhere after the first character.
|
|
35
|
+
const atIdx = spec.lastIndexOf("@");
|
|
36
|
+
if (atIdx > 0) {
|
|
37
|
+
const rhs = spec.slice(atIdx + 1);
|
|
38
|
+
// Dist-tags and version specifiers are never marketplace names.
|
|
39
|
+
if (NPM_DIST_TAGS.has(rhs) || LOOKS_LIKE_VERSION.test(rhs)) {
|
|
40
|
+
return { type: "npm", spec };
|
|
41
|
+
}
|
|
42
|
+
if (knownMarketplaces.has(rhs)) {
|
|
43
|
+
return { type: "marketplace", name: spec.slice(0, atIdx), marketplace: rhs };
|
|
44
|
+
}
|
|
45
|
+
// Not a known marketplace — treat as npm version specifier.
|
|
46
|
+
return { type: "npm", spec };
|
|
47
|
+
}
|
|
48
|
+
// Rule 3: no @ at all.
|
|
49
|
+
return { type: "npm", spec };
|
|
50
|
+
}
|
package/src/cli/plugin-cli.ts
CHANGED
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
import { APP_NAME } from "@oh-my-pi/pi-utils";
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import { PluginManager, parseSettingValue, validateSetting } from "../extensibility/plugins";
|
|
10
|
+
import {
|
|
11
|
+
getInstalledPluginsRegistryPath,
|
|
12
|
+
getMarketplacesCacheDir,
|
|
13
|
+
getMarketplacesRegistryPath,
|
|
14
|
+
getPluginsCacheDir,
|
|
15
|
+
MarketplaceManager,
|
|
16
|
+
} from "../extensibility/plugins/marketplace/index.js";
|
|
10
17
|
import { theme } from "../modes/theme/theme";
|
|
11
18
|
|
|
12
19
|
// =============================================================================
|
|
@@ -22,7 +29,10 @@ export type PluginAction =
|
|
|
22
29
|
| "features"
|
|
23
30
|
| "config"
|
|
24
31
|
| "enable"
|
|
25
|
-
| "disable"
|
|
32
|
+
| "disable"
|
|
33
|
+
| "marketplace"
|
|
34
|
+
| "discover"
|
|
35
|
+
| "upgrade";
|
|
26
36
|
|
|
27
37
|
export interface PluginCommandArgs {
|
|
28
38
|
action: PluginAction;
|
|
@@ -53,6 +63,9 @@ const VALID_ACTIONS: PluginAction[] = [
|
|
|
53
63
|
"config",
|
|
54
64
|
"enable",
|
|
55
65
|
"disable",
|
|
66
|
+
"marketplace",
|
|
67
|
+
"discover",
|
|
68
|
+
"upgrade",
|
|
56
69
|
];
|
|
57
70
|
|
|
58
71
|
/**
|
|
@@ -108,6 +121,10 @@ export function parsePluginArgs(args: string[]): PluginCommandArgs | undefined {
|
|
|
108
121
|
return result;
|
|
109
122
|
}
|
|
110
123
|
|
|
124
|
+
import { classifyInstallTarget } from "./classify-install-target";
|
|
125
|
+
|
|
126
|
+
export { classifyInstallTarget } from "./classify-install-target";
|
|
127
|
+
|
|
111
128
|
// =============================================================================
|
|
112
129
|
// Command Handlers
|
|
113
130
|
// =============================================================================
|
|
@@ -146,6 +163,153 @@ export async function runPluginCommand(cmd: PluginCommandArgs): Promise<void> {
|
|
|
146
163
|
case "disable":
|
|
147
164
|
await handleDisable(manager, cmd.args, cmd.flags);
|
|
148
165
|
break;
|
|
166
|
+
case "marketplace":
|
|
167
|
+
await handleMarketplace(cmd.args, cmd.flags);
|
|
168
|
+
break;
|
|
169
|
+
case "discover":
|
|
170
|
+
await handleDiscover(cmd.args, cmd.flags);
|
|
171
|
+
break;
|
|
172
|
+
case "upgrade":
|
|
173
|
+
await handleUpgrade(cmd.args, cmd.flags);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// =============================================================================
|
|
179
|
+
// Marketplace Handlers
|
|
180
|
+
// =============================================================================
|
|
181
|
+
|
|
182
|
+
function makeMarketplaceManager(): MarketplaceManager {
|
|
183
|
+
return new MarketplaceManager({
|
|
184
|
+
marketplacesRegistryPath: getMarketplacesRegistryPath(),
|
|
185
|
+
installedRegistryPath: getInstalledPluginsRegistryPath(),
|
|
186
|
+
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
187
|
+
pluginsCacheDir: getPluginsCacheDir(),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function handleMarketplace(args: string[], _flags: PluginCommandArgs["flags"]): Promise<void> {
|
|
192
|
+
const subcommand = args[0] ?? "list";
|
|
193
|
+
const manager = makeMarketplaceManager();
|
|
194
|
+
|
|
195
|
+
switch (subcommand) {
|
|
196
|
+
case "add": {
|
|
197
|
+
const source = args[1];
|
|
198
|
+
if (!source) {
|
|
199
|
+
console.error(chalk.red(`Usage: ${APP_NAME} plugin marketplace add <source>`));
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
await manager.addMarketplace(source);
|
|
204
|
+
console.log(chalk.green(`${theme.status.success} Added marketplace: ${source}`));
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error(chalk.red(`${theme.status.error} Failed to add marketplace: ${err}`));
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case "remove":
|
|
212
|
+
case "rm": {
|
|
213
|
+
const name = args[1];
|
|
214
|
+
if (!name) {
|
|
215
|
+
console.error(chalk.red(`Usage: ${APP_NAME} plugin marketplace remove <name>`));
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
await manager.removeMarketplace(name);
|
|
220
|
+
console.log(chalk.green(`${theme.status.success} Removed marketplace: ${name}`));
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.error(chalk.red(`${theme.status.error} Failed to remove marketplace: ${err}`));
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case "update": {
|
|
228
|
+
try {
|
|
229
|
+
const name = args[1];
|
|
230
|
+
if (name) {
|
|
231
|
+
await manager.updateMarketplace(name);
|
|
232
|
+
console.log(chalk.green(`${theme.status.success} Updated marketplace: ${name}`));
|
|
233
|
+
} else {
|
|
234
|
+
const results = await manager.updateAllMarketplaces();
|
|
235
|
+
console.log(chalk.green(`${theme.status.success} Updated ${results.length} marketplace(s)`));
|
|
236
|
+
}
|
|
237
|
+
} catch (err) {
|
|
238
|
+
console.error(chalk.red(`${theme.status.error} Failed to update marketplace: ${err}`));
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
default: {
|
|
244
|
+
if (subcommand !== "list") {
|
|
245
|
+
console.error(chalk.red(`Unknown marketplace subcommand: ${subcommand}`));
|
|
246
|
+
console.error(chalk.dim("Valid subcommands: add, remove, update, list"));
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const marketplaces = await manager.listMarketplaces();
|
|
251
|
+
if (marketplaces.length === 0) {
|
|
252
|
+
console.log(chalk.dim("No marketplaces configured"));
|
|
253
|
+
console.log(chalk.dim(`\nAdd one with: ${APP_NAME} plugin marketplace add <source>`));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
console.log(chalk.bold("Configured Marketplaces:\n"));
|
|
257
|
+
for (const mp of marketplaces) {
|
|
258
|
+
console.log(` ${chalk.cyan(mp.name)} ${chalk.dim(mp.sourceUri)}`);
|
|
259
|
+
}
|
|
260
|
+
} catch (err) {
|
|
261
|
+
console.error(chalk.red(`${theme.status.error} Failed to list marketplaces: ${err}`));
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function handleDiscover(args: string[], _flags: PluginCommandArgs["flags"]): Promise<void> {
|
|
270
|
+
const marketplace = args[0];
|
|
271
|
+
const manager = makeMarketplaceManager();
|
|
272
|
+
try {
|
|
273
|
+
const plugins = await manager.listAvailablePlugins(marketplace);
|
|
274
|
+
|
|
275
|
+
if (plugins.length === 0) {
|
|
276
|
+
console.log(chalk.dim(marketplace ? `No plugins found in ${marketplace}` : "No plugins available"));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
console.log(chalk.bold(`Available Plugins${marketplace ? ` (${marketplace})` : ""}:\n`));
|
|
281
|
+
for (const plugin of plugins) {
|
|
282
|
+
console.log(` ${chalk.cyan(plugin.name)}${plugin.version ? `@${plugin.version}` : ""}`);
|
|
283
|
+
if (plugin.description) {
|
|
284
|
+
console.log(chalk.dim(` ${plugin.description}`));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
} catch (err) {
|
|
288
|
+
console.error(chalk.red(`${theme.status.error} Failed to discover plugins: ${err}`));
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function handleUpgrade(args: string[], _flags: PluginCommandArgs["flags"]): Promise<void> {
|
|
294
|
+
const manager = makeMarketplaceManager();
|
|
295
|
+
const pluginId = args[0];
|
|
296
|
+
try {
|
|
297
|
+
if (pluginId) {
|
|
298
|
+
const result = await manager.upgradePlugin(pluginId);
|
|
299
|
+
console.log(chalk.green(`Upgraded ${pluginId} to ${result.version}`));
|
|
300
|
+
} else {
|
|
301
|
+
const results = await manager.upgradeAllPlugins();
|
|
302
|
+
if (results.length === 0) {
|
|
303
|
+
console.log("All marketplace plugins are up to date.");
|
|
304
|
+
} else {
|
|
305
|
+
for (const r of results) {
|
|
306
|
+
console.log(chalk.green(` ${r.pluginId}: ${r.from} -> ${r.to}`));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
console.error(chalk.red(`Failed to upgrade: ${err}`));
|
|
312
|
+
process.exit(1);
|
|
149
313
|
}
|
|
150
314
|
}
|
|
151
315
|
|
|
@@ -158,13 +322,35 @@ async function handleInstall(
|
|
|
158
322
|
console.error(chalk.red(`Usage: ${APP_NAME} plugin install <package[@version]>[features] ...`));
|
|
159
323
|
console.error(chalk.dim("Examples:"));
|
|
160
324
|
console.error(chalk.dim(` ${APP_NAME} plugin install @oh-my-pi/exa`));
|
|
161
|
-
console.error(chalk.dim(` ${APP_NAME} plugin install @
|
|
162
|
-
console.error(chalk.dim(` ${APP_NAME} plugin install @oh-my-pi/exa[*] # all features`));
|
|
163
|
-
console.error(chalk.dim(` ${APP_NAME} plugin install @oh-my-pi/exa[] # no optional features`));
|
|
325
|
+
console.error(chalk.dim(` ${APP_NAME} plugin install name@marketplace`));
|
|
164
326
|
process.exit(1);
|
|
165
327
|
}
|
|
166
328
|
|
|
329
|
+
// Build known marketplace set for classification
|
|
330
|
+
const mktMgr = makeMarketplaceManager();
|
|
331
|
+
const knownMarketplaces = new Set((await mktMgr.listMarketplaces()).map(m => m.name));
|
|
332
|
+
|
|
167
333
|
for (const spec of packages) {
|
|
334
|
+
const target = classifyInstallTarget(spec, knownMarketplaces);
|
|
335
|
+
|
|
336
|
+
if (target.type === "marketplace") {
|
|
337
|
+
try {
|
|
338
|
+
const entry = await mktMgr.installPlugin(target.name, target.marketplace, {
|
|
339
|
+
force: flags.force,
|
|
340
|
+
});
|
|
341
|
+
console.log(
|
|
342
|
+
chalk.green(
|
|
343
|
+
`${theme.status.success} Installed ${target.name} from ${target.marketplace} (${entry.version})`,
|
|
344
|
+
),
|
|
345
|
+
);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
console.error(chalk.red(`${theme.status.error} Failed to install ${spec}: ${err}`));
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// npm path
|
|
168
354
|
try {
|
|
169
355
|
const result = await manager.install(spec, { force: flags.force, dryRun: flags.dryRun });
|
|
170
356
|
|
|
@@ -196,10 +382,27 @@ async function handleUninstall(manager: PluginManager, packages: string[], flags
|
|
|
196
382
|
process.exit(1);
|
|
197
383
|
}
|
|
198
384
|
|
|
385
|
+
// For uninstall, check the installed plugins registry directly.
|
|
386
|
+
// This works even if the marketplace entry was later removed from marketplaces.json.
|
|
387
|
+
const mktMgr = makeMarketplaceManager();
|
|
388
|
+
const installedPlugins = new Set((await mktMgr.listInstalledPlugins()).map(p => p.id));
|
|
389
|
+
|
|
199
390
|
for (const name of packages) {
|
|
391
|
+
if (installedPlugins.has(name)) {
|
|
392
|
+
// Exact match against installed marketplace plugin IDs (name@marketplace)
|
|
393
|
+
try {
|
|
394
|
+
await mktMgr.uninstallPlugin(name);
|
|
395
|
+
console.log(chalk.green(`${theme.status.success} Uninstalled ${name}`));
|
|
396
|
+
} catch (err) {
|
|
397
|
+
console.error(chalk.red(`${theme.status.error} Failed to uninstall ${name}: ${err}`));
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// npm path
|
|
200
404
|
try {
|
|
201
405
|
await manager.uninstall(name);
|
|
202
|
-
|
|
203
406
|
if (flags.json) {
|
|
204
407
|
console.log(JSON.stringify({ uninstalled: name }));
|
|
205
408
|
} else {
|
|
@@ -213,44 +416,53 @@ async function handleUninstall(manager: PluginManager, packages: string[], flags
|
|
|
213
416
|
}
|
|
214
417
|
|
|
215
418
|
async function handleList(manager: PluginManager, flags: { json?: boolean }): Promise<void> {
|
|
216
|
-
const
|
|
419
|
+
const npmPlugins = await manager.list();
|
|
420
|
+
const mktMgr = makeMarketplaceManager();
|
|
421
|
+
const mktPlugins = await mktMgr.listInstalledPlugins();
|
|
217
422
|
|
|
218
423
|
if (flags.json) {
|
|
219
|
-
console.log(JSON.stringify(
|
|
424
|
+
console.log(JSON.stringify({ npm: npmPlugins, marketplace: mktPlugins }, null, 2));
|
|
220
425
|
return;
|
|
221
426
|
}
|
|
222
427
|
|
|
223
|
-
if (
|
|
428
|
+
if (npmPlugins.length === 0 && mktPlugins.length === 0) {
|
|
224
429
|
console.log(chalk.dim("No plugins installed"));
|
|
225
430
|
console.log(chalk.dim(`\nInstall plugins with: ${APP_NAME} plugin install <package>`));
|
|
226
431
|
return;
|
|
227
432
|
}
|
|
228
433
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
434
|
+
if (npmPlugins.length > 0) {
|
|
435
|
+
console.log(chalk.bold("npm Plugins:\n"));
|
|
436
|
+
for (const plugin of npmPlugins) {
|
|
437
|
+
const status = plugin.enabled ? chalk.green(theme.status.enabled) : chalk.dim(theme.status.disabled);
|
|
438
|
+
const nameVersion = `${plugin.name}@${plugin.version}`;
|
|
439
|
+
console.log(`${status} ${nameVersion}`);
|
|
440
|
+
if (plugin.manifest.description) {
|
|
441
|
+
console.log(chalk.dim(` ${plugin.manifest.description}`));
|
|
442
|
+
}
|
|
443
|
+
if (plugin.enabledFeatures && plugin.enabledFeatures.length > 0) {
|
|
444
|
+
console.log(chalk.dim(` Features: ${plugin.enabledFeatures.join(", ")}`));
|
|
445
|
+
}
|
|
446
|
+
if (plugin.manifest.features) {
|
|
447
|
+
const availableFeatures = Object.keys(plugin.manifest.features);
|
|
448
|
+
if (availableFeatures.length > 0) {
|
|
449
|
+
const enabledSet = new Set(plugin.enabledFeatures ?? []);
|
|
450
|
+
const featureDisplay = availableFeatures
|
|
451
|
+
.map(f => (enabledSet.has(f) ? chalk.green(f) : chalk.dim(f)))
|
|
452
|
+
.join(", ");
|
|
453
|
+
console.log(chalk.dim(` Available: [${featureDisplay}]`));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
242
456
|
}
|
|
457
|
+
}
|
|
243
458
|
|
|
244
|
-
|
|
245
|
-
if (
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
.join(", ");
|
|
252
|
-
console.log(chalk.dim(` Available: [${featureDisplay}]`));
|
|
253
|
-
}
|
|
459
|
+
if (mktPlugins.length > 0) {
|
|
460
|
+
if (npmPlugins.length > 0) console.log();
|
|
461
|
+
console.log(chalk.bold("Marketplace Plugins:\n"));
|
|
462
|
+
for (const plugin of mktPlugins) {
|
|
463
|
+
const entry = plugin.entries[0];
|
|
464
|
+
const version = entry?.version ?? "unknown";
|
|
465
|
+
console.log(` ${plugin.id} (${version})`);
|
|
254
466
|
}
|
|
255
467
|
}
|
|
256
468
|
}
|
|
@@ -630,6 +842,8 @@ ${chalk.bold("Commands:")}
|
|
|
630
842
|
config <cmd> <pkg> [key] [val] Manage plugin settings
|
|
631
843
|
enable <pkg> Enable a disabled plugin
|
|
632
844
|
disable <pkg> Disable plugin without uninstalling
|
|
845
|
+
marketplace <cmd> Manage marketplace sources (add, remove, update, list)
|
|
846
|
+
discover [marketplace] Browse available marketplace plugins
|
|
633
847
|
|
|
634
848
|
${chalk.bold("Feature Syntax:")}
|
|
635
849
|
pkg Install with default features
|
package/src/commands/plugin.ts
CHANGED
|
@@ -198,6 +198,18 @@ export const SETTINGS_SCHEMA = {
|
|
|
198
198
|
|
|
199
199
|
extensions: { type: "array", default: EMPTY_STRING_ARRAY },
|
|
200
200
|
|
|
201
|
+
"marketplace.autoUpdate": {
|
|
202
|
+
type: "enum",
|
|
203
|
+
values: ["off", "notify", "auto"] as const,
|
|
204
|
+
default: "notify",
|
|
205
|
+
ui: {
|
|
206
|
+
tab: "tools",
|
|
207
|
+
label: "Marketplace Auto-Update",
|
|
208
|
+
description: "Check for plugin updates on startup (off/notify/auto)",
|
|
209
|
+
submenu: true,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
|
|
201
213
|
enabledModels: { type: "array", default: EMPTY_STRING_ARRAY },
|
|
202
214
|
|
|
203
215
|
disabledProviders: { type: "array", default: EMPTY_STRING_ARRAY },
|
|
@@ -1490,19 +1502,6 @@ export const SETTINGS_SCHEMA = {
|
|
|
1490
1502
|
submenu: true,
|
|
1491
1503
|
},
|
|
1492
1504
|
},
|
|
1493
|
-
|
|
1494
|
-
"providers.codeSearch": {
|
|
1495
|
-
type: "enum",
|
|
1496
|
-
values: ["grep", "exa"] as const,
|
|
1497
|
-
default: "grep",
|
|
1498
|
-
ui: {
|
|
1499
|
-
tab: "providers",
|
|
1500
|
-
label: "Code Search Provider",
|
|
1501
|
-
description: "Provider for code search tool",
|
|
1502
|
-
submenu: true,
|
|
1503
|
-
},
|
|
1504
|
-
},
|
|
1505
|
-
|
|
1506
1505
|
"providers.image": {
|
|
1507
1506
|
type: "enum",
|
|
1508
1507
|
values: ["auto", "gemini", "openrouter"] as const,
|
package/src/cursor.ts
CHANGED
|
@@ -7,7 +7,12 @@ import type {
|
|
|
7
7
|
AgentToolResult,
|
|
8
8
|
AgentToolUpdateCallback,
|
|
9
9
|
} from "@oh-my-pi/pi-agent-core";
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
CursorMcpCall,
|
|
12
|
+
CursorShellStreamCallbacks,
|
|
13
|
+
CursorExecHandlers as ICursorExecHandlers,
|
|
14
|
+
ToolResultMessage,
|
|
15
|
+
} from "@oh-my-pi/pi-ai";
|
|
11
16
|
import { resolveToCwd } from "./tools/path-utils";
|
|
12
17
|
|
|
13
18
|
interface CursorExecBridgeOptions {
|
|
@@ -204,6 +209,66 @@ export class CursorExecHandlers implements ICursorExecHandlers {
|
|
|
204
209
|
return toolResultMessage;
|
|
205
210
|
}
|
|
206
211
|
|
|
212
|
+
async shellStream(
|
|
213
|
+
args: Parameters<NonNullable<ICursorExecHandlers["shellStream"]>>[0],
|
|
214
|
+
callbacks: CursorShellStreamCallbacks,
|
|
215
|
+
) {
|
|
216
|
+
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
217
|
+
const toolName = "bash";
|
|
218
|
+
const tool = this.options.tools.get(toolName);
|
|
219
|
+
if (!tool) {
|
|
220
|
+
const result = buildToolErrorResult(`Tool "${toolName}" not available`);
|
|
221
|
+
return createToolResultMessage(toolCallId, toolName, result, true);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const timeoutSeconds = args.timeout && args.timeout > 0 ? args.timeout : undefined;
|
|
225
|
+
const toolArgs: Record<string, unknown> = {
|
|
226
|
+
command: args.command,
|
|
227
|
+
cwd: args.workingDirectory || undefined,
|
|
228
|
+
timeout: timeoutSeconds,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
this.options.emitEvent?.({ type: "tool_execution_start", toolCallId, toolName, args: toolArgs });
|
|
232
|
+
|
|
233
|
+
let result: AgentToolResult<unknown>;
|
|
234
|
+
let isError = false;
|
|
235
|
+
|
|
236
|
+
// Track previously streamed text so we only forward deltas.
|
|
237
|
+
let streamedLen = 0;
|
|
238
|
+
const onUpdate: AgentToolUpdateCallback<unknown> = partialResult => {
|
|
239
|
+
this.options.emitEvent?.({
|
|
240
|
+
type: "tool_execution_update",
|
|
241
|
+
toolCallId,
|
|
242
|
+
toolName,
|
|
243
|
+
args: toolArgs,
|
|
244
|
+
partialResult,
|
|
245
|
+
});
|
|
246
|
+
const text = partialResult.content.map(c => (c.type === "text" ? c.text : "")).join("");
|
|
247
|
+
if (text.length > streamedLen) {
|
|
248
|
+
callbacks.onStdout(text.slice(streamedLen));
|
|
249
|
+
streamedLen = text.length;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
result = await tool.execute(toolCallId, toolArgs, undefined, onUpdate, this.options.getToolContext?.());
|
|
255
|
+
} catch (error) {
|
|
256
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
257
|
+
result = buildToolErrorResult(message);
|
|
258
|
+
isError = true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// onUpdate may not fire for every chunk — flush any remaining output
|
|
262
|
+
// from the final result that wasn't already streamed.
|
|
263
|
+
const finalText = result.content.map(c => (c.type === "text" ? c.text : "")).join("");
|
|
264
|
+
if (finalText.length > streamedLen) {
|
|
265
|
+
callbacks.onStdout(finalText.slice(streamedLen));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
this.options.emitEvent?.({ type: "tool_execution_end", toolCallId, toolName, result, isError });
|
|
269
|
+
return createToolResultMessage(toolCallId, toolName, result, isError);
|
|
270
|
+
}
|
|
271
|
+
|
|
207
272
|
async diagnostics(args: Parameters<NonNullable<ICursorExecHandlers["diagnostics"]>>[0]) {
|
|
208
273
|
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
209
274
|
const toolResultMessage = await executeTool(this.options, "lsp", toolCallId, {
|