@oh-my-pi/pi-coding-agent 13.16.5 → 13.17.1
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 +58 -1
- 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/commit/git/index.ts +3 -4
- package/src/commit/model-selection.ts +1 -19
- package/src/config/model-registry.ts +19 -3
- package/src/config/model-resolver.ts +21 -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 +51 -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/patch/shared.ts +28 -3
- 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/auto-generated-guard.ts +1 -1
- package/src/tools/bash-skill-urls.ts +48 -5
- package/src/tools/read.ts +15 -9
- package/src/tools/render-utils.ts +2 -2
- package/src/utils/title-generator.ts +4 -8
- package/src/web/search/index.ts +2 -38
- package/src/web/search/types.ts +0 -6
- package/src/prompts/tools/code-search.md +0 -45
- package/src/web/search/code-search.ts +0 -385
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,63 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.17.1] - 2026-04-01
|
|
6
|
+
### Removed
|
|
7
|
+
|
|
8
|
+
- Removed `code_search` tool for code snippet and documentation search
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Fixed edit tool diff rendering to wrap long diff lines with continuation gutters instead of truncating them at terminal width ([#578](https://github.com/can1357/oh-my-pi/issues/578))
|
|
13
|
+
- Fixed `--list-models` and `/model` provider filtering to hide models from disabled providers ([#588](https://github.com/can1357/oh-my-pi/issues/588))
|
|
14
|
+
- Fixed edit tool diffstats to use diff-specific add/remove theme colors instead of success/error status colors ([#589](https://github.com/can1357/oh-my-pi/issues/589))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [13.17.0] - 2026-03-30
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Added `marketplace.autoUpdate` setting (`off`/`notify`/`auto`, default `notify`) for automatic plugin update checking on startup
|
|
22
|
+
- Added background marketplace catalog refresh on startup when catalogs are stale (>24h)
|
|
23
|
+
- Added `/marketplace upgrade [name@marketplace]` slash command to upgrade outdated plugins
|
|
24
|
+
- Added `omp plugin upgrade [name@marketplace]` CLI command for plugin upgrades
|
|
25
|
+
- Added `checkForUpdates()`, `upgradePlugin()`, `upgradeAllPlugins()`, and `refreshStaleMarketplaces()` to MarketplaceManager
|
|
26
|
+
- Added marketplace plugin system: registry types, ID helpers, atomic read/write for `marketplaces.json` and `installed_plugins.json` (Claude Code-compatible format)
|
|
27
|
+
- Added `MarketplaceManager` orchestrator for marketplace and plugin lifecycle (add/remove/update marketplaces, install/uninstall/enable plugins)
|
|
28
|
+
- Added marketplace fetcher with source classification (GitHub, git, URL, local) and catalog validation
|
|
29
|
+
- Added plugin source resolver with `pathIsWithin` containment checks and versioned cache manager
|
|
30
|
+
- Added CLI commands: `omp plugin marketplace add|remove|update|list`, `omp plugin discover [marketplace]`
|
|
31
|
+
- Added `classifyInstallTarget()` to distinguish `name@marketplace` from npm install targets
|
|
32
|
+
- Extended `listClaudePluginRoots()` to read OMP's installed plugins registry alongside Claude Code's, with OMP as authoritative for duplicate plugin IDs
|
|
33
|
+
- Added `--plugin-dir <path>` repeatable CLI flag for loading plugins from local directories
|
|
34
|
+
- Added `/reload-plugins` slash command that invalidates fs content cache and plugin roots cache
|
|
35
|
+
- Added `printPluginHelp()` entries for marketplace and discover commands
|
|
36
|
+
- Added MCP server loading from marketplace plugin `.mcp.json` files with `${CLAUDE_PLUGIN_ROOT}` variable substitution
|
|
37
|
+
- Added skill and command namespacing for marketplace plugins (`plugin-name:skill-name`)
|
|
38
|
+
- Added LSP config loading from marketplace plugin roots via `getPreloadedPluginRoots()`
|
|
39
|
+
- Wired `--plugin-dir` runtime injection into plugin roots at session startup with highest precedence
|
|
40
|
+
- Added git (GitHub, SSH, HTTPS) and HTTP URL marketplace source fetching
|
|
41
|
+
- Added `/marketplace` TUI slash command with subcommands: add, remove, update, list, discover, install, uninstall, installed
|
|
42
|
+
- Added `/plugins` TUI slash command to view all installed plugins (npm + marketplace) and enable/disable marketplace plugins
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- Changed marketplace clone promotion to occur after duplicate and drift checks, improving safety of concurrent marketplace operations
|
|
47
|
+
|
|
48
|
+
### Removed
|
|
49
|
+
|
|
50
|
+
- Removed grep.app code search provider support; code search now uses Exa exclusively
|
|
51
|
+
- Removed `providers.codeSearch` setting and related configuration options
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- Fixed git-subdir plugin source resolution to properly clean up temporary clone directories on path validation errors
|
|
56
|
+
- Fixed LSP config loading to use correct filenames variable when scanning plugin roots
|
|
57
|
+
- Fixed plugin selector UI to request render on cancel, preventing stale display state
|
|
58
|
+
- Fixed marketplace install command error handling to display user-friendly error messages instead of crashing
|
|
59
|
+
- 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
|
|
60
|
+
- Fixed `skill://` URI resolver to handle namespaced skills via longest-prefix matching against registered skill names
|
|
61
|
+
|
|
5
62
|
## [13.16.5] - 2026-03-29
|
|
6
63
|
|
|
7
64
|
### Fixed
|
|
@@ -6406,4 +6463,4 @@ Initial public release.
|
|
|
6406
6463
|
- Git branch display in footer
|
|
6407
6464
|
- Message queueing during streaming responses
|
|
6408
6465
|
- OAuth integration for Gmail and Google Calendar access
|
|
6409
|
-
- HTML export with syntax highlighting and collapsible sections
|
|
6466
|
+
- HTML export with syntax highlighting and collapsible sections
|
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.1",
|
|
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.1",
|
|
46
|
+
"@oh-my-pi/pi-agent-core": "13.17.1",
|
|
47
|
+
"@oh-my-pi/pi-ai": "13.17.1",
|
|
48
|
+
"@oh-my-pi/pi-natives": "13.17.1",
|
|
49
|
+
"@oh-my-pi/pi-tui": "13.17.1",
|
|
50
|
+
"@oh-my-pi/pi-utils": "13.17.1",
|
|
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
package/src/commit/git/index.ts
CHANGED
|
@@ -189,12 +189,11 @@ function extractFileHeader(diff: string): string {
|
|
|
189
189
|
return headerLines.join("\n");
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
function joinPatch(parts: string[]): string {
|
|
193
|
-
return parts
|
|
192
|
+
export function joinPatch(parts: string[]): string {
|
|
193
|
+
return `${parts
|
|
194
194
|
.map(part => (part.endsWith("\n") ? part : `${part}\n`))
|
|
195
195
|
.join("\n")
|
|
196
|
-
.
|
|
197
|
-
.concat("\n");
|
|
196
|
+
.replace(/\n+$/, "")}\n`;
|
|
198
197
|
}
|
|
199
198
|
|
|
200
199
|
function selectHunks(file: FileHunks, selector: HunkSelection["hunks"]): FileHunks["hunks"] {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { MODEL_ROLE_IDS } from "../config/model-registry";
|
|
4
|
-
import { parseModelPattern, resolveModelRoleValue } from "../config/model-resolver";
|
|
4
|
+
import { parseModelPattern, resolveModelRoleValue, resolveRoleSelection } from "../config/model-resolver";
|
|
5
5
|
import type { Settings } from "../config/settings";
|
|
6
6
|
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
7
7
|
|
|
@@ -11,24 +11,6 @@ export interface ResolvedCommitModel {
|
|
|
11
11
|
thinkingLevel?: ThinkingLevel;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function resolveRoleSelection(
|
|
15
|
-
roles: readonly string[],
|
|
16
|
-
settings: Settings,
|
|
17
|
-
availableModels: Model<Api>[],
|
|
18
|
-
): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
|
|
19
|
-
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
|
20
|
-
for (const role of roles) {
|
|
21
|
-
const resolved = resolveModelRoleValue(settings.getModelRole(role), availableModels, {
|
|
22
|
-
settings,
|
|
23
|
-
matchPreferences,
|
|
24
|
-
});
|
|
25
|
-
if (resolved.model) {
|
|
26
|
-
return { model: resolved.model, thinkingLevel: resolved.thinkingLevel };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
14
|
export async function resolvePrimaryModel(
|
|
33
15
|
override: string | undefined,
|
|
34
16
|
settings: Settings,
|
|
@@ -31,7 +31,7 @@ import { type ConfigError, ConfigFile } from "../config";
|
|
|
31
31
|
import { parseModelString } from "../config/model-resolver";
|
|
32
32
|
import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
|
|
33
33
|
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
|
34
|
-
import type
|
|
34
|
+
import { type Settings, settings } from "./settings";
|
|
35
35
|
|
|
36
36
|
export const kNoAuth = "N/A";
|
|
37
37
|
|
|
@@ -730,6 +730,14 @@ function normalizeSuppressedSelector(selector: string): string {
|
|
|
730
730
|
return `${parsed.provider}/${parsed.id}`;
|
|
731
731
|
}
|
|
732
732
|
|
|
733
|
+
function getDisabledProviderIdsFromSettings(): Set<string> {
|
|
734
|
+
try {
|
|
735
|
+
return new Set(settings.get("disabledProviders"));
|
|
736
|
+
} catch {
|
|
737
|
+
return new Set();
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
733
741
|
/**
|
|
734
742
|
* Model registry - loads and manages models, resolves API keys via AuthStorage.
|
|
735
743
|
*/
|
|
@@ -1670,11 +1678,19 @@ export class ModelRegistry {
|
|
|
1670
1678
|
* This is a fast check that doesn't refresh OAuth tokens.
|
|
1671
1679
|
*/
|
|
1672
1680
|
getAvailable(): Model<Api>[] {
|
|
1673
|
-
|
|
1681
|
+
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
1682
|
+
return this.#models.filter(
|
|
1683
|
+
m =>
|
|
1684
|
+
!disabledProviders.has(m.provider) &&
|
|
1685
|
+
(this.#keylessProviders.has(m.provider) || this.authStorage.hasAuth(m.provider)),
|
|
1686
|
+
);
|
|
1674
1687
|
}
|
|
1675
1688
|
|
|
1676
1689
|
getDiscoverableProviders(): string[] {
|
|
1677
|
-
|
|
1690
|
+
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
1691
|
+
return this.#discoverableProviders
|
|
1692
|
+
.filter(provider => !disabledProviders.has(provider.provider))
|
|
1693
|
+
.map(provider => provider.provider);
|
|
1678
1694
|
}
|
|
1679
1695
|
|
|
1680
1696
|
getProviderDiscoveryState(provider: string): ProviderDiscoveryState | undefined {
|
|
@@ -587,6 +587,27 @@ export function resolveModelOverride(
|
|
|
587
587
|
return { explicitThinkingLevel: false };
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
+
/**
|
|
591
|
+
* Resolve a list of role patterns to the first matching model.
|
|
592
|
+
*/
|
|
593
|
+
export function resolveRoleSelection(
|
|
594
|
+
roles: readonly string[],
|
|
595
|
+
settings: Settings,
|
|
596
|
+
availableModels: Model<Api>[],
|
|
597
|
+
): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
|
|
598
|
+
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
|
599
|
+
for (const role of roles) {
|
|
600
|
+
const resolved = resolveModelRoleValue(settings.getModelRole(role), availableModels, {
|
|
601
|
+
settings,
|
|
602
|
+
matchPreferences,
|
|
603
|
+
});
|
|
604
|
+
if (resolved.model) {
|
|
605
|
+
return { model: resolved.model, thinkingLevel: resolved.thinkingLevel };
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return undefined;
|
|
609
|
+
}
|
|
610
|
+
|
|
590
611
|
/**
|
|
591
612
|
* Resolve model patterns to actual Model objects with optional thinking levels
|
|
592
613
|
* Format: "pattern:level" where :level is optional
|