@oh-my-pi/pi-coding-agent 13.17.0 → 13.17.5

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.
Files changed (159) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/package.json +10 -8
  3. package/scripts/format-prompts.ts +3 -3
  4. package/src/cli/plugin-cli.ts +114 -25
  5. package/src/commands/plugin.ts +5 -0
  6. package/src/commit/agentic/prompts/analyze-file.md +1 -1
  7. package/src/commit/agentic/prompts/session-user.md +1 -1
  8. package/src/commit/agentic/prompts/split-confirm.md +1 -1
  9. package/src/commit/agentic/prompts/system.md +1 -1
  10. package/src/commit/git/index.ts +3 -4
  11. package/src/commit/model-selection.ts +1 -19
  12. package/src/commit/prompts/analysis-system.md +1 -1
  13. package/src/commit/prompts/analysis-user.md +1 -1
  14. package/src/commit/prompts/changelog-system.md +1 -1
  15. package/src/commit/prompts/changelog-user.md +1 -1
  16. package/src/commit/prompts/file-observer-system.md +1 -1
  17. package/src/commit/prompts/file-observer-user.md +1 -1
  18. package/src/commit/prompts/reduce-system.md +1 -1
  19. package/src/commit/prompts/reduce-user.md +1 -1
  20. package/src/commit/prompts/summary-retry.md +1 -1
  21. package/src/commit/prompts/summary-system.md +1 -1
  22. package/src/commit/prompts/summary-user.md +1 -1
  23. package/src/commit/prompts/types-description.md +1 -1
  24. package/src/config/model-registry.ts +19 -3
  25. package/src/config/model-resolver.ts +21 -0
  26. package/src/config/settings-schema.ts +16 -0
  27. package/src/discovery/claude-plugins.ts +5 -5
  28. package/src/discovery/helpers.ts +144 -24
  29. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +37 -0
  30. package/src/extensibility/custom-commands/loader.ts +7 -0
  31. package/src/extensibility/plugins/marketplace/fetcher.ts +55 -37
  32. package/src/extensibility/plugins/marketplace/manager.ts +296 -73
  33. package/src/extensibility/plugins/marketplace/registry.ts +15 -0
  34. package/src/extensibility/plugins/marketplace/types.ts +17 -3
  35. package/src/internal-urls/docs-index.generated.ts +2 -1
  36. package/src/main.ts +14 -4
  37. package/src/modes/components/assistant-message.ts +2 -1
  38. package/src/modes/components/plugin-selector.ts +20 -11
  39. package/src/modes/components/tool-execution.ts +2 -3
  40. package/src/modes/controllers/command-controller.ts +19 -7
  41. package/src/modes/controllers/selector-controller.ts +7 -4
  42. package/src/modes/interactive-mode.ts +4 -0
  43. package/src/modes/types.ts +1 -0
  44. package/src/modes/utils/tools-markdown.ts +27 -0
  45. package/src/patch/shared.ts +28 -3
  46. package/src/prompts/agents/designer.md +1 -1
  47. package/src/prompts/agents/explore.md +1 -1
  48. package/src/prompts/agents/frontmatter.md +1 -1
  49. package/src/prompts/agents/init.md +1 -1
  50. package/src/prompts/agents/librarian.md +1 -1
  51. package/src/prompts/agents/oracle.md +1 -1
  52. package/src/prompts/agents/plan.md +1 -1
  53. package/src/prompts/agents/reviewer.md +1 -1
  54. package/src/prompts/agents/task.md +1 -1
  55. package/src/prompts/ci-green-request.md +36 -0
  56. package/src/prompts/compaction/branch-summary-context.md +1 -1
  57. package/src/prompts/compaction/branch-summary-preamble.md +1 -1
  58. package/src/prompts/compaction/branch-summary.md +1 -1
  59. package/src/prompts/compaction/compaction-short-summary.md +1 -1
  60. package/src/prompts/compaction/compaction-summary-context.md +1 -1
  61. package/src/prompts/compaction/compaction-summary.md +1 -1
  62. package/src/prompts/compaction/compaction-turn-prefix.md +1 -1
  63. package/src/prompts/compaction/compaction-update-summary.md +1 -1
  64. package/src/prompts/memories/consolidation.md +1 -1
  65. package/src/prompts/memories/read-path.md +1 -1
  66. package/src/prompts/memories/stage_one_input.md +1 -1
  67. package/src/prompts/memories/stage_one_system.md +1 -1
  68. package/src/prompts/review-request.md +1 -1
  69. package/src/prompts/system/agent-creation-architect.md +1 -1
  70. package/src/prompts/system/agent-creation-user.md +1 -1
  71. package/src/prompts/system/auto-handoff-threshold-focus.md +1 -1
  72. package/src/prompts/system/btw-user.md +1 -1
  73. package/src/prompts/system/commit-message-system.md +1 -1
  74. package/src/prompts/system/custom-system-prompt.md +1 -1
  75. package/src/prompts/system/eager-todo.md +1 -1
  76. package/src/prompts/system/file-operations.md +1 -1
  77. package/src/prompts/system/handoff-document.md +1 -1
  78. package/src/prompts/system/plan-mode-active.md +1 -1
  79. package/src/prompts/system/plan-mode-approved.md +1 -1
  80. package/src/prompts/system/plan-mode-reference.md +1 -1
  81. package/src/prompts/system/plan-mode-subagent.md +1 -1
  82. package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
  83. package/src/prompts/system/subagent-submit-reminder.md +1 -1
  84. package/src/prompts/system/subagent-system-prompt.md +1 -1
  85. package/src/prompts/system/subagent-user-prompt.md +1 -1
  86. package/src/prompts/system/summarization-system.md +1 -1
  87. package/src/prompts/system/system-prompt.md +1 -1
  88. package/src/prompts/system/title-system.md +1 -1
  89. package/src/prompts/system/ttsr-interrupt.md +1 -1
  90. package/src/prompts/system/web-search.md +1 -1
  91. package/src/prompts/tools/ask.md +1 -1
  92. package/src/prompts/tools/ast-edit.md +1 -1
  93. package/src/prompts/tools/ast-grep.md +1 -1
  94. package/src/prompts/tools/async-result.md +1 -1
  95. package/src/prompts/tools/await.md +1 -1
  96. package/src/prompts/tools/bash.md +1 -1
  97. package/src/prompts/tools/browser.md +1 -1
  98. package/src/prompts/tools/calculator.md +1 -1
  99. package/src/prompts/tools/cancel-job.md +1 -1
  100. package/src/prompts/tools/checkpoint.md +1 -1
  101. package/src/prompts/tools/exit-plan-mode.md +1 -1
  102. package/src/prompts/tools/fetch.md +1 -1
  103. package/src/prompts/tools/find.md +1 -1
  104. package/src/prompts/tools/gemini-image.md +1 -1
  105. package/src/prompts/tools/gh-issue-view.md +11 -0
  106. package/src/prompts/tools/gh-pr-checkout.md +12 -0
  107. package/src/prompts/tools/gh-pr-diff.md +12 -0
  108. package/src/prompts/tools/gh-pr-push.md +11 -0
  109. package/src/prompts/tools/gh-pr-view.md +11 -0
  110. package/src/prompts/tools/gh-repo-view.md +11 -0
  111. package/src/prompts/tools/gh-run-watch.md +12 -0
  112. package/src/prompts/tools/gh-search-issues.md +11 -0
  113. package/src/prompts/tools/gh-search-prs.md +11 -0
  114. package/src/prompts/tools/grep.md +1 -1
  115. package/src/prompts/tools/hashline.md +1 -1
  116. package/src/prompts/tools/inspect-image-system.md +1 -1
  117. package/src/prompts/tools/inspect-image.md +1 -1
  118. package/src/prompts/tools/lsp.md +1 -1
  119. package/src/prompts/tools/patch.md +1 -1
  120. package/src/prompts/tools/python.md +1 -1
  121. package/src/prompts/tools/read.md +6 -3
  122. package/src/prompts/tools/render-mermaid.md +1 -1
  123. package/src/prompts/tools/replace.md +1 -1
  124. package/src/prompts/tools/resolve.md +1 -1
  125. package/src/prompts/tools/rewind.md +1 -1
  126. package/src/prompts/tools/search-tool-bm25.md +1 -1
  127. package/src/prompts/tools/ssh.md +1 -1
  128. package/src/prompts/tools/task-summary.md +1 -1
  129. package/src/prompts/tools/task.md +1 -1
  130. package/src/prompts/tools/todo-write.md +1 -1
  131. package/src/prompts/tools/web-search.md +1 -1
  132. package/src/prompts/tools/write.md +2 -1
  133. package/src/sdk.ts +3 -1
  134. package/src/session/messages.ts +11 -7
  135. package/src/session/session-manager.ts +13 -3
  136. package/src/slash-commands/builtin-registry.ts +109 -37
  137. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  138. package/src/task/discovery.ts +1 -1
  139. package/src/tools/archive-reader.ts +315 -0
  140. package/src/tools/auto-generated-guard.ts +1 -1
  141. package/src/tools/fetch.ts +21 -19
  142. package/src/tools/gh-cli.ts +125 -0
  143. package/src/tools/gh-renderer.ts +305 -0
  144. package/src/tools/gh.ts +2719 -0
  145. package/src/tools/index.ts +22 -0
  146. package/src/tools/read.ts +286 -34
  147. package/src/tools/render-utils.ts +22 -2
  148. package/src/tools/renderers.ts +2 -0
  149. package/src/tools/write.ts +175 -4
  150. package/src/utils/markit.ts +81 -0
  151. package/src/utils/title-generator.ts +4 -8
  152. package/src/utils/tools-manager.ts +1 -6
  153. package/src/web/scrapers/arxiv.ts +3 -3
  154. package/src/web/scrapers/iacr.ts +3 -3
  155. package/src/web/scrapers/utils.ts +6 -34
  156. package/src/web/search/index.ts +1 -36
  157. package/src/web/search/types.ts +0 -2
  158. package/src/prompts/tools/code-search.md +0 -45
  159. package/src/web/search/code-search.ts +0 -208
package/CHANGELOG.md CHANGED
@@ -2,6 +2,108 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.17.5] - 2026-04-01
6
+ ### Added
7
+
8
+ - Added support for writing to ZIP archives using fflate library for cross-platform compatibility
9
+
10
+ ### Changed
11
+
12
+ - Modified archive writing to detect and handle ZIP files separately from Bun archives
13
+
14
+ ### Removed
15
+
16
+ - Removed GhPrPushTool test case
17
+
18
+ ## [13.17.4] - 2026-04-01
19
+ ### Added
20
+
21
+ - Support for writing to archive entries in `.tar`, `.tar.gz`, `.tgz`, and `.zip` files using `archive.ext:path/inside/archive` syntax
22
+ - Ability to create new archives when writing to archive subpaths that don't yet exist
23
+
24
+ ## [13.17.3] - 2026-04-01
25
+
26
+ ### Added
27
+
28
+ - Added support for converting Jupyter notebooks (`.ipynb`) to markdown via markit
29
+ - Added `markit-ai` npm package for native document and notebook conversion
30
+ - Added support for reading files from `.tar`, `.tar.gz`, `.tgz`, and `.zip` archives using virtual subpaths like `archive.ext:path/to/file`
31
+ - Added ability to list archive contents and navigate subdirectories within supported archive formats
32
+ - Added archive-aware `read` support for `.tar`, `.tar.gz`, `.tgz`, and `.zip`, including virtual subpaths like `archive.ext:path/to/file`
33
+ - Added `/tools` slash command to show the tools currently visible to the agent in the interactive session
34
+
35
+ ### Changed
36
+
37
+ - Replaced Python-based markitdown CLI tool with native `markit-ai` library for document conversion
38
+ - Updated document conversion to use markit library instead of external markitdown command
39
+ - Removed markitdown from Python tools manager (no longer needed as external dependency)
40
+ - Updated `read` tool documentation to reflect archive support and usage patterns
41
+
42
+ ## [13.17.2] - 2026-04-01
43
+ ### Added
44
+
45
+ - Added `/marketplace help` command to display usage guide for all marketplace operations
46
+ - Added dedicated `gh-renderer.ts` module for rich terminal rendering of GitHub Actions workflow runs with live status snapshots and job details
47
+ - Added `gh_pr_checkout` tool to check out GitHub pull requests into dedicated git worktrees with contributor push metadata
48
+ - Added `gh_pr_push` tool to push checked-out pull request branches back to their source branches
49
+ - Added `gh_repo_view` tool to read GitHub repository metadata using the local GitHub CLI
50
+ - Added `gh_issue_view` tool to read GitHub issues with optional comment context
51
+ - Added `gh_pr_view` tool to read GitHub pull requests with optional comment context
52
+ - Added `gh_pr_diff` tool to read GitHub pull request diffs with optional file filtering
53
+ - Added `gh_search_issues` tool to search GitHub issues with repository scoping
54
+ - Added `gh_search_prs` tool to search GitHub pull requests with repository scoping
55
+ - Added `gh_run_watch` tool to watch GitHub Actions workflow runs, fast-fail on job failures, and stream tailed logs for failed jobs
56
+ - Added `github.enabled` setting to enable read-only `gh_*` GitHub CLI tools for repository, issue, pull request, diff, and search workflows
57
+ - Added bundled `/green` command to generate iterative CI fix prompts with optional tag instructions when HEAD is tagged
58
+ - Added `github.enabled` setting to enable read-only `gh_*` GitHub CLI tools for repository, issue, pull request, diff, and search workflows
59
+ - Added `gh_repo_view` tool to read GitHub repository metadata using the local GitHub CLI
60
+ - Added `gh_issue_view` tool to read GitHub issues with optional comment context
61
+ - Added `gh_pr_view` tool to read GitHub pull requests with optional comment context
62
+ - Added `gh_pr_diff` tool to read GitHub pull request diffs with optional file filtering
63
+ - Added `gh_search_issues` tool to search GitHub issues with repository scoping
64
+ - Added `gh_search_prs` tool to search GitHub pull requests with repository scoping
65
+ - Added `gh_run_watch` tool to watch GitHub Actions workflow runs, fast-fail on job failures, and stream tailed logs for failed jobs
66
+ - Added bundled `/green` command to generate iterative CI fix prompts with optional tag instructions when HEAD is tagged
67
+ - Project-scoped marketplace plugin installs: `omp plugin install --scope project name@marketplace` and `/marketplace install --scope project name@marketplace` install plugins into the nearest `.omp/` or `.git`-rooted project directory instead of the user directory ([#581](https://github.com/can1357/oh-my-pi/issues/581))
68
+ - `--scope user|project` flag added to `/marketplace uninstall`, `/marketplace upgrade`, `/plugins enable`, and `/plugins disable` to disambiguate when a plugin is installed in both scopes
69
+ - `omp plugin upgrade --scope project` with no plugin ID warns that `--scope` is ignored for bulk upgrades
70
+ - Added opt-in `gh_*` GitHub CLI tools behind the `github.enabled` setting for repository, issue, pull request, diff, and search workflows
71
+ - Added opt-in `gh_run_watch` to fast-fail GitHub Actions runs, stream job status snapshots, and return tailed logs for failed jobs
72
+ - Added bundled `/green` command to generate the iterative “fix CI until green” prompt, with final tag instructions included only when `HEAD` already has a tag
73
+
74
+ ### Changed
75
+
76
+ - Improved marketplace catalog parsing to skip invalid plugin entries with warnings instead of failing the entire catalog load
77
+ - Enhanced `/marketplace discover` command to suggest adding the official marketplace when no plugins are available
78
+ - Improved `/marketplace` command messaging with clearer guidance for first-time setup and available commands
79
+ - Enhanced `gh_run_watch` tool call rendering to display animated spinner status and target description (run ID, branch, or current HEAD) with improved visual hierarchy
80
+ - Enhanced `gh_pr_view` tool to include inline review comments alongside pull request reviews for improved discussion context
81
+ - Improved `gh_run_watch` tool output rendering with dedicated visual component for streaming run snapshots and job status updates
82
+
83
+ ### Fixed
84
+
85
+ - Fixed marketplace error messages to display error details instead of object stringification
86
+ - Fixed artifact storage for non-persistent sessions to use in-memory fallback instead of returning undefined, enabling proper spill truncation for all session types
87
+ - Fixed prompt file formatting to include trailing newlines at EOF for consistency across all prompt markdown files
88
+ - Fixed `gh_pr_diff` to preserve raw patch content instead of normalizing tabs and whitespace
89
+ - Fixed `gh_pr_view` to include inline review comments alongside pull request reviews and issue-style comments for discussion context
90
+ - Fixed `gh_run_watch` to resolve explicit branch watches against the selected branch head instead of local `HEAD`
91
+ - Fixed `gh_run_watch` to hide repo and polling internals from the tool schema and save full failed-job logs as session artifacts alongside the inline tailed output
92
+ - Fixed `gh_*` tool outputs to spill full large results to artifacts instead of pre-truncating the head with unusable `offset=` guidance
93
+ - Fixed bundled `/green` to watch the workflow runs for the current `HEAD` commit instead of whichever branch run was newest
94
+ - Fixed OpenAI Responses session rehydration to strip stale assistant replay payloads before resumed requests ([#594](https://github.com/can1357/oh-my-pi/pull/594) by [@daandden](https://github.com/daandden))
95
+ - Fixed inline image rendering to cap image height and preserve multiplexer scrollback during terminal resizes ([#587](https://github.com/can1357/oh-my-pi/pull/587) by [@smileynet](https://github.com/smileynet))
96
+
97
+ ## [13.17.1] - 2026-04-01
98
+ ### Removed
99
+
100
+ - Removed `code_search` tool for code snippet and documentation search
101
+
102
+ ### Fixed
103
+
104
+ - 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))
105
+ - Fixed `--list-models` and `/model` provider filtering to hide models from disabled providers ([#588](https://github.com/can1357/oh-my-pi/issues/588))
106
+ - 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))
5
107
  ## [13.17.0] - 2026-03-30
6
108
 
7
109
  ### Added
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.17.0",
4
+ "version": "13.17.5",
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",
@@ -35,26 +35,28 @@
35
35
  "format-prompts": "bun scripts/format-prompts.ts",
36
36
  "generate-docs-index": "bun scripts/generate-docs-index.ts",
37
37
  "prepack": "bun scripts/generate-docs-index.ts",
38
- "build:binary": "cd ../.. && bun --cwd=packages/stats scripts/generate-client-bundle.ts && bun --cwd=packages/natives run embed:native && bun build --compile --define PI_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp && bun --cwd=packages/natives run embed:native --reset && bun --cwd=packages/stats scripts/generate-client-bundle.ts --reset",
38
+ "build:binary": "cd ../.. && bun --cwd=packages/stats scripts/generate-client-bundle.ts && bun --cwd=packages/natives run embed:native && bun build --compile --define PI_COMPILED=true --external mupdf --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp && bun --cwd=packages/natives run embed:native --reset && bun --cwd=packages/stats scripts/generate-client-bundle.ts --reset",
39
39
  "generate-template": "bun scripts/generate-template.ts",
40
40
  "test": "bun test"
41
41
  },
42
42
  "dependencies": {
43
43
  "@agentclientprotocol/sdk": "0.16.1",
44
44
  "@mozilla/readability": "^0.6",
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",
45
+ "@oh-my-pi/omp-stats": "13.17.5",
46
+ "@oh-my-pi/pi-agent-core": "13.17.5",
47
+ "@oh-my-pi/pi-ai": "13.17.5",
48
+ "@oh-my-pi/pi-natives": "13.17.5",
49
+ "@oh-my-pi/pi-tui": "13.17.5",
50
+ "@oh-my-pi/pi-utils": "13.17.5",
51
51
  "@sinclair/typebox": "^0.34",
52
52
  "@xterm/headless": "^6.0",
53
53
  "ajv": "^8.18",
54
54
  "chalk": "^5.6",
55
55
  "diff": "^8.0",
56
+ "fflate": "0.8.2",
56
57
  "handlebars": "^4.7",
57
58
  "linkedom": "^0.18",
59
+ "markit-ai": "0.5.0",
58
60
  "puppeteer": "^24.37",
59
61
  "zod": "4.3.6"
60
62
  },
@@ -10,7 +10,7 @@
10
10
  * 5. Compact markdown tables (remove padding)
11
11
  * 6. Collapse 2+ blank lines to single blank line
12
12
  * 7. Trim trailing whitespace (preserve indentation)
13
- * 8. No trailing newline at EOF
13
+ * 8. Trailing newline at EOF (disk only; runtime omits it)
14
14
  * 9. Bold RFC 2119 keywords (MUST, SHOULD, MAY, etc.) in prompt content
15
15
  */
16
16
  import { Glob } from "bun";
@@ -44,11 +44,11 @@ async function main() {
44
44
  const original = await Bun.file(fullPath).text();
45
45
  const formatted = formatPromptContent(original, PROMPT_FORMAT_OPTIONS);
46
46
 
47
- if (original !== formatted) {
47
+ if (original !== `${formatted}\n`) {
48
48
  if (check) {
49
49
  console.log(`Would format: ${fullPath}`);
50
50
  } else {
51
- await Bun.write(fullPath, formatted);
51
+ await Bun.write(fullPath, `${formatted}\n`);
52
52
  console.log(`Formatted: ${fullPath}`);
53
53
  }
54
54
  changed++;
@@ -4,8 +4,9 @@
4
4
  * Handles `omp plugin <command>` subcommands for plugin lifecycle management.
5
5
  */
6
6
 
7
- import { APP_NAME } from "@oh-my-pi/pi-utils";
7
+ import { APP_NAME, getProjectDir } from "@oh-my-pi/pi-utils";
8
8
  import chalk from "chalk";
9
+ import { resolveOrDefaultProjectRegistryPath } from "../discovery/helpers";
9
10
  import { PluginManager, parseSettingValue, validateSetting } from "../extensibility/plugins";
10
11
  import {
11
12
  getInstalledPluginsRegistryPath,
@@ -46,6 +47,7 @@ export interface PluginCommandArgs {
46
47
  enable?: string;
47
48
  disable?: string;
48
49
  set?: string;
50
+ scope?: "user" | "project";
49
51
  };
50
52
  }
51
53
 
@@ -113,6 +115,18 @@ export function parsePluginArgs(args: string[]): PluginCommandArgs | undefined {
113
115
  result.flags.disable = args[++i];
114
116
  } else if (arg === "--set" && i + 1 < args.length) {
115
117
  result.flags.set = args[++i];
118
+ } else if (arg === "--scope" && i + 1 < args.length && !args[i + 1].startsWith("-")) {
119
+ const s = args[++i];
120
+ if (s === "user" || s === "project") {
121
+ result.flags.scope = s;
122
+ } else {
123
+ console.error(chalk.red(`Invalid --scope value: "${s}". Must be "user" or "project".`));
124
+ process.exit(1);
125
+ }
126
+ } else if (arg === "--scope") {
127
+ // --scope with no value following
128
+ console.error(chalk.red(`--scope requires a value: "user" or "project".`));
129
+ process.exit(1);
116
130
  } else if (!arg.startsWith("-")) {
117
131
  result.args.push(arg);
118
132
  }
@@ -179,10 +193,11 @@ export async function runPluginCommand(cmd: PluginCommandArgs): Promise<void> {
179
193
  // Marketplace Handlers
180
194
  // =============================================================================
181
195
 
182
- function makeMarketplaceManager(): MarketplaceManager {
196
+ async function makeMarketplaceManager(): Promise<MarketplaceManager> {
183
197
  return new MarketplaceManager({
184
198
  marketplacesRegistryPath: getMarketplacesRegistryPath(),
185
199
  installedRegistryPath: getInstalledPluginsRegistryPath(),
200
+ projectInstalledRegistryPath: await resolveOrDefaultProjectRegistryPath(getProjectDir()),
186
201
  marketplacesCacheDir: getMarketplacesCacheDir(),
187
202
  pluginsCacheDir: getPluginsCacheDir(),
188
203
  });
@@ -190,7 +205,7 @@ function makeMarketplaceManager(): MarketplaceManager {
190
205
 
191
206
  async function handleMarketplace(args: string[], _flags: PluginCommandArgs["flags"]): Promise<void> {
192
207
  const subcommand = args[0] ?? "list";
193
- const manager = makeMarketplaceManager();
208
+ const manager = await makeMarketplaceManager();
194
209
 
195
210
  switch (subcommand) {
196
211
  case "add": {
@@ -268,7 +283,7 @@ async function handleMarketplace(args: string[], _flags: PluginCommandArgs["flag
268
283
 
269
284
  async function handleDiscover(args: string[], _flags: PluginCommandArgs["flags"]): Promise<void> {
270
285
  const marketplace = args[0];
271
- const manager = makeMarketplaceManager();
286
+ const manager = await makeMarketplaceManager();
272
287
  try {
273
288
  const plugins = await manager.listAvailablePlugins(marketplace);
274
289
 
@@ -290,20 +305,34 @@ async function handleDiscover(args: string[], _flags: PluginCommandArgs["flags"]
290
305
  }
291
306
  }
292
307
 
293
- async function handleUpgrade(args: string[], _flags: PluginCommandArgs["flags"]): Promise<void> {
294
- const manager = makeMarketplaceManager();
308
+ async function handleUpgrade(args: string[], flags: PluginCommandArgs["flags"]): Promise<void> {
309
+ const manager = await makeMarketplaceManager();
295
310
  const pluginId = args[0];
296
311
  try {
297
312
  if (pluginId) {
298
- const result = await manager.upgradePlugin(pluginId);
299
- console.log(chalk.green(`Upgraded ${pluginId} to ${result.version}`));
313
+ if (flags.scope) {
314
+ const result = await manager.upgradePlugin(pluginId, flags.scope);
315
+ console.log(chalk.green(`Upgraded ${pluginId} (${flags.scope}) to ${result.version}`));
316
+ } else {
317
+ const entries = await manager.upgradePluginAcrossScopes(pluginId);
318
+ for (const entry of entries) {
319
+ console.log(chalk.green(`Upgraded ${pluginId} (${entry.scope}) to ${entry.version}`));
320
+ }
321
+ }
300
322
  } else {
323
+ if (flags.scope) {
324
+ console.error(
325
+ chalk.yellow(
326
+ `Warning: --scope is ignored when upgrading all plugins. Use 'omp plugin upgrade <id> --scope ${flags.scope}' to target a specific plugin and scope.`,
327
+ ),
328
+ );
329
+ }
301
330
  const results = await manager.upgradeAllPlugins();
302
331
  if (results.length === 0) {
303
332
  console.log("All marketplace plugins are up to date.");
304
333
  } else {
305
334
  for (const r of results) {
306
- console.log(chalk.green(` ${r.pluginId}: ${r.from} -> ${r.to}`));
335
+ console.log(chalk.green(` ${r.pluginId} (${r.scope}): ${r.from} -> ${r.to}`));
307
336
  }
308
337
  }
309
338
  }
@@ -316,7 +345,7 @@ async function handleUpgrade(args: string[], _flags: PluginCommandArgs["flags"])
316
345
  async function handleInstall(
317
346
  manager: PluginManager,
318
347
  packages: string[],
319
- flags: { json?: boolean; force?: boolean; dryRun?: boolean },
348
+ flags: { json?: boolean; force?: boolean; dryRun?: boolean; scope?: "user" | "project" },
320
349
  ): Promise<void> {
321
350
  if (packages.length === 0) {
322
351
  console.error(chalk.red(`Usage: ${APP_NAME} plugin install <package[@version]>[features] ...`));
@@ -327,7 +356,7 @@ async function handleInstall(
327
356
  }
328
357
 
329
358
  // Build known marketplace set for classification
330
- const mktMgr = makeMarketplaceManager();
359
+ const mktMgr = await makeMarketplaceManager();
331
360
  const knownMarketplaces = new Set((await mktMgr.listMarketplaces()).map(m => m.name));
332
361
 
333
362
  for (const spec of packages) {
@@ -337,6 +366,7 @@ async function handleInstall(
337
366
  try {
338
367
  const entry = await mktMgr.installPlugin(target.name, target.marketplace, {
339
368
  force: flags.force,
369
+ scope: flags.scope,
340
370
  });
341
371
  console.log(
342
372
  chalk.green(
@@ -350,6 +380,15 @@ async function handleInstall(
350
380
  continue;
351
381
  }
352
382
 
383
+ // --scope only applies to marketplace installs; warn when it would be silently no-op'd for npm.
384
+ if (flags.scope) {
385
+ console.error(
386
+ chalk.yellow(
387
+ `Warning: --scope is only supported for marketplace installs (name@marketplace). Ignoring for ${spec}.`,
388
+ ),
389
+ );
390
+ }
391
+
353
392
  // npm path
354
393
  try {
355
394
  const result = await manager.install(spec, { force: flags.force, dryRun: flags.dryRun });
@@ -376,7 +415,11 @@ async function handleInstall(
376
415
  }
377
416
  }
378
417
 
379
- async function handleUninstall(manager: PluginManager, packages: string[], flags: { json?: boolean }): Promise<void> {
418
+ async function handleUninstall(
419
+ manager: PluginManager,
420
+ packages: string[],
421
+ flags: { json?: boolean; scope?: "user" | "project" },
422
+ ): Promise<void> {
380
423
  if (packages.length === 0) {
381
424
  console.error(chalk.red(`Usage: ${APP_NAME} plugin uninstall <package> ...`));
382
425
  process.exit(1);
@@ -384,14 +427,14 @@ async function handleUninstall(manager: PluginManager, packages: string[], flags
384
427
 
385
428
  // For uninstall, check the installed plugins registry directly.
386
429
  // This works even if the marketplace entry was later removed from marketplaces.json.
387
- const mktMgr = makeMarketplaceManager();
430
+ const mktMgr = await makeMarketplaceManager();
388
431
  const installedPlugins = new Set((await mktMgr.listInstalledPlugins()).map(p => p.id));
389
432
 
390
433
  for (const name of packages) {
391
434
  if (installedPlugins.has(name)) {
392
435
  // Exact match against installed marketplace plugin IDs (name@marketplace)
393
436
  try {
394
- await mktMgr.uninstallPlugin(name);
437
+ await mktMgr.uninstallPlugin(name, flags.scope);
395
438
  console.log(chalk.green(`${theme.status.success} Uninstalled ${name}`));
396
439
  } catch (err) {
397
440
  console.error(chalk.red(`${theme.status.error} Failed to uninstall ${name}: ${err}`));
@@ -417,7 +460,7 @@ async function handleUninstall(manager: PluginManager, packages: string[], flags
417
460
 
418
461
  async function handleList(manager: PluginManager, flags: { json?: boolean }): Promise<void> {
419
462
  const npmPlugins = await manager.list();
420
- const mktMgr = makeMarketplaceManager();
463
+ const mktMgr = await makeMarketplaceManager();
421
464
  const mktPlugins = await mktMgr.listInstalledPlugins();
422
465
 
423
466
  if (flags.json) {
@@ -462,7 +505,9 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
462
505
  for (const plugin of mktPlugins) {
463
506
  const entry = plugin.entries[0];
464
507
  const version = entry?.version ?? "unknown";
465
- console.log(` ${plugin.id} (${version})`);
508
+ const shadowLabel = plugin.shadowedBy ? chalk.dim(" [shadowed]") : "";
509
+ const scopeLabel = chalk.dim(` (${plugin.scope})`);
510
+ console.log(` ${plugin.id} (${version})${scopeLabel}${shadowLabel}`);
466
511
  }
467
512
  }
468
513
  }
@@ -781,16 +826,37 @@ async function handleConfigValidate(manager: PluginManager, flags: { json?: bool
781
826
  }
782
827
  }
783
828
 
784
- async function handleEnable(manager: PluginManager, plugins: string[], flags: { json?: boolean }): Promise<void> {
829
+ async function handleEnable(
830
+ manager: PluginManager,
831
+ plugins: string[],
832
+ flags: { json?: boolean; scope?: "user" | "project" },
833
+ ): Promise<void> {
785
834
  if (plugins.length === 0) {
786
835
  console.error(chalk.red(`Usage: ${APP_NAME} plugin enable <plugin> ...`));
787
836
  process.exit(1);
788
837
  }
789
838
 
839
+ const mktMgr = await makeMarketplaceManager();
840
+ const installedPlugins = new Set((await mktMgr.listInstalledPlugins()).map(p => p.id));
841
+
790
842
  for (const name of plugins) {
843
+ if (installedPlugins.has(name)) {
844
+ try {
845
+ await mktMgr.setPluginEnabled(name, true, flags.scope);
846
+ if (flags.json) {
847
+ console.log(JSON.stringify({ enabled: name }));
848
+ } else {
849
+ console.log(chalk.green(`${theme.status.success} Enabled ${name}`));
850
+ }
851
+ } catch (err) {
852
+ console.error(chalk.red(`${theme.status.error} Failed to enable ${name}: ${err}`));
853
+ process.exit(1);
854
+ }
855
+ continue;
856
+ }
857
+
791
858
  try {
792
859
  await manager.setEnabled(name, true);
793
-
794
860
  if (flags.json) {
795
861
  console.log(JSON.stringify({ enabled: name }));
796
862
  } else {
@@ -803,16 +869,37 @@ async function handleEnable(manager: PluginManager, plugins: string[], flags: {
803
869
  }
804
870
  }
805
871
 
806
- async function handleDisable(manager: PluginManager, plugins: string[], flags: { json?: boolean }): Promise<void> {
872
+ async function handleDisable(
873
+ manager: PluginManager,
874
+ plugins: string[],
875
+ flags: { json?: boolean; scope?: "user" | "project" },
876
+ ): Promise<void> {
807
877
  if (plugins.length === 0) {
808
878
  console.error(chalk.red(`Usage: ${APP_NAME} plugin disable <plugin> ...`));
809
879
  process.exit(1);
810
880
  }
811
881
 
882
+ const mktMgr = await makeMarketplaceManager();
883
+ const installedPlugins = new Set((await mktMgr.listInstalledPlugins()).map(p => p.id));
884
+
812
885
  for (const name of plugins) {
886
+ if (installedPlugins.has(name)) {
887
+ try {
888
+ await mktMgr.setPluginEnabled(name, false, flags.scope);
889
+ if (flags.json) {
890
+ console.log(JSON.stringify({ disabled: name }));
891
+ } else {
892
+ console.log(chalk.green(`${theme.status.success} Disabled ${name}`));
893
+ }
894
+ } catch (err) {
895
+ console.error(chalk.red(`${theme.status.error} Failed to disable ${name}: ${err}`));
896
+ process.exit(1);
897
+ }
898
+ continue;
899
+ }
900
+
813
901
  try {
814
902
  await manager.setEnabled(name, false);
815
-
816
903
  if (flags.json) {
817
904
  console.log(JSON.stringify({ disabled: name }));
818
905
  } else {
@@ -859,11 +946,12 @@ ${chalk.bold("Config Subcommands:")}
859
946
  config validate Validate all plugin settings
860
947
 
861
948
  ${chalk.bold("Options:")}
862
- --json Output as JSON
863
- --fix Attempt automatic fixes (doctor)
864
- --force Overwrite without prompting (install)
865
- --dry-run Preview changes without applying (install)
866
- -l, --local Use project-local overrides
949
+ --json Output as JSON
950
+ --fix Attempt automatic fixes (doctor)
951
+ --force Overwrite without prompting (install)
952
+ --scope <scope> Install scope: user (default) or project (install name@marketplace)
953
+ --dry-run Preview changes without applying (install)
954
+ -l, --local Use project-local overrides
867
955
 
868
956
  ${chalk.bold("Examples:")}
869
957
  ${APP_NAME} plugin install @oh-my-pi/exa[search]
@@ -871,5 +959,6 @@ ${chalk.bold("Examples:")}
871
959
  ${APP_NAME} plugin features my-plugin --enable search,web
872
960
  ${APP_NAME} plugin config set my-plugin apiKey sk-xxx
873
961
  ${APP_NAME} plugin doctor --fix
962
+ ${APP_NAME} plugin install --scope project name@marketplace
874
963
  `);
875
964
  }
@@ -45,6 +45,10 @@ export default class Plugin extends Command {
45
45
  enable: Flags.string({ description: "Enable a feature" }),
46
46
  disable: Flags.string({ description: "Disable a feature" }),
47
47
  set: Flags.string({ description: "Set plugin config (key=value)" }),
48
+ scope: Flags.string({
49
+ description: 'Install scope: "user" (default) or "project"',
50
+ options: ["user", "project"],
51
+ }),
48
52
  };
49
53
 
50
54
  async run(): Promise<void> {
@@ -64,6 +68,7 @@ export default class Plugin extends Command {
64
68
  enable: flags.enable,
65
69
  disable: flags.disable,
66
70
  set: flags.set,
71
+ scope: flags.scope as "user" | "project" | undefined,
67
72
  },
68
73
  };
69
74
 
@@ -19,4 +19,4 @@ Return concise JSON object with:
19
19
  Consider how file's changes relate to above files.
20
20
  {{/if}}
21
21
 
22
- Call submit_result tool with JSON payload.
22
+ Call submit_result tool with JSON payload.
@@ -22,4 +22,4 @@ May include entries from list in propose_changelog `deletions` field for removal
22
22
  {{/each}}
23
23
  {{/if}}
24
24
 
25
- Use git_* tools to inspect changes. Call analyze_files for deeper per-file summaries. Finish with propose_commit or split_commit.
25
+ Use git_* tools to inspect changes. Call analyze_files for deeper per-file summaries. Finish with propose_commit or split_commit.
@@ -1 +1 @@
1
- Split commit plan has {{count}} commits. Proceed? (y/N):
1
+ Split commit plan has {{count}} commits. Proceed? (y/N):
@@ -35,4 +35,4 @@ Tool guidance:
35
35
  ## Changelog Requirements
36
36
 
37
37
  If changelog targets provided, you **MUST** call `propose_changelog` before finishing.
38
- If you propose split commit plan, include changelog target files in relevant commit changes.
38
+ If you propose split commit plan, include changelog target files in relevant commit changes.
@@ -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
- .trimEnd()
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,
@@ -145,4 +145,4 @@ Call create_conventional_analysis with:
145
145
  "details": [],
146
146
  "issue_refs": []
147
147
  }
148
- </example>
148
+ </example>
@@ -35,4 +35,4 @@
35
35
  {{/if}}
36
36
  <diff>
37
37
  {{ diff }}
38
- </diff>
38
+ </diff>
@@ -47,4 +47,4 @@ Return ONLY valid JSON; no markdown fences or explanation.
47
47
 
48
48
  With entries: {"entries": {"Added": ["entry 1"], "Fixed": ["entry 2"]}}
49
49
  No changelog-worthy changes: {"entries": {}}
50
- </output-format>
50
+ </output-format>
@@ -15,4 +15,4 @@ Already documented—skip these:
15
15
 
16
16
  <diff>
17
17
  {{ diff }}
18
- </diff>
18
+ </diff>
@@ -21,4 +21,4 @@ Plain list, no preamble, no summary, no markdown formatting.
21
21
  - changed 'Connection::new()' to accept '&Config' instead of individual params
22
22
  </output-format>
23
23
 
24
- Observations only. Classification in reduce phase.
24
+ Observations only. Classification in reduce phase.
@@ -5,4 +5,4 @@
5
5
  <related-files>
6
6
  {{ context_header }}
7
7
  </related-files>
8
- {{/if}}
8
+ {{/if}}
@@ -47,4 +47,4 @@ Output:
47
47
  ],
48
48
  "issue_refs": []
49
49
  }
50
- </example>
50
+ </example>
@@ -14,4 +14,4 @@
14
14
 
15
15
  <scope-candidates>
16
16
  {{ scope_candidates }}
17
- </scope-candidates>
17
+ </scope-candidates>
@@ -1,3 +1,3 @@
1
1
  {{#if base_context}}
2
2
  {{ base_context }}
3
- {{/if}}Previous summary failed validation: {{ errors }}
3
+ {{/if}}Previous summary failed validation: {{ errors }}
@@ -35,4 +35,4 @@ build | Updated serde to fix CVE-2024-1234
35
35
  </examples>
36
36
  <banned-words>
37
37
  comprehensive, various, several, improved, enhanced, quickly, simply, basically, this change, this commit, now
38
- </banned-words>
38
+ </banned-words>
@@ -10,4 +10,4 @@
10
10
 
11
11
  <diff-stat>
12
12
  {{ stat }}
13
- </diff-stat>
13
+ </diff-stat>
@@ -1,2 +1,2 @@
1
1
  Types: feat, fix, refactor, perf, docs, test, build, ci, chore, style, revert.
2
- Format: <type>(<scope>): <summary> with past-tense summary.
2
+ Format: <type>(<scope>): <summary> with past-tense summary.