@oh-my-pi/pi-coding-agent 13.1.2 → 13.2.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.
Files changed (235) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/package.json +7 -7
  3. package/scripts/format-prompts.ts +33 -14
  4. package/src/async/job-manager.ts +43 -1
  5. package/src/capability/index.ts +1 -2
  6. package/src/capability/tool.ts +1 -1
  7. package/src/cli/args.ts +1 -2
  8. package/src/cli/config-cli.ts +1 -1
  9. package/src/cli/file-processor.ts +1 -2
  10. package/src/cli/grep-cli.ts +1 -1
  11. package/src/cli/jupyter-cli.ts +1 -1
  12. package/src/cli/plugin-cli.ts +1 -1
  13. package/src/cli/setup-cli.ts +1 -1
  14. package/src/cli/shell-cli.ts +1 -1
  15. package/src/cli/ssh-cli.ts +1 -1
  16. package/src/cli/stats-cli.ts +1 -2
  17. package/src/cli/update-cli.ts +1 -2
  18. package/src/cli/web-search-cli.ts +1 -1
  19. package/src/cli.ts +1 -1
  20. package/src/commands/launch.ts +2 -1
  21. package/src/commit/agentic/agent.ts +2 -1
  22. package/src/commit/agentic/index.ts +1 -2
  23. package/src/commit/agentic/prompts/system.md +3 -3
  24. package/src/commit/agentic/tools/propose-changelog.ts +30 -19
  25. package/src/commit/changelog/generate.ts +16 -6
  26. package/src/commit/changelog/index.ts +2 -1
  27. package/src/commit/pipeline.ts +1 -2
  28. package/src/commit/prompts/reduce-system.md +1 -1
  29. package/src/commit/types.ts +10 -1
  30. package/src/config/keybindings.ts +1 -2
  31. package/src/config/model-registry.ts +1 -1
  32. package/src/config/prompt-templates.ts +14 -2
  33. package/src/config/settings-schema.ts +10 -0
  34. package/src/config/settings.ts +25 -2
  35. package/src/config.ts +1 -2
  36. package/src/debug/index.ts +1 -1
  37. package/src/debug/report-bundle.ts +1 -2
  38. package/src/debug/system-info.ts +1 -2
  39. package/src/discovery/agents.ts +2 -2
  40. package/src/discovery/builtin.ts +24 -14
  41. package/src/discovery/claude-plugins.ts +3 -2
  42. package/src/discovery/claude.ts +9 -9
  43. package/src/discovery/codex.ts +3 -3
  44. package/src/discovery/cursor.ts +5 -4
  45. package/src/discovery/gemini.ts +5 -5
  46. package/src/discovery/helpers.ts +47 -69
  47. package/src/discovery/mcp-json.ts +3 -3
  48. package/src/discovery/opencode.ts +7 -8
  49. package/src/discovery/ssh.ts +3 -3
  50. package/src/discovery/vscode.ts +3 -2
  51. package/src/discovery/windsurf.ts +3 -2
  52. package/src/exa/company.ts +1 -1
  53. package/src/exa/factory.ts +1 -6
  54. package/src/exa/linkedin.ts +1 -1
  55. package/src/exa/mcp-client.ts +19 -8
  56. package/src/exa/search.ts +2 -2
  57. package/src/exa/types.ts +3 -3
  58. package/src/exec/bash-executor.ts +2 -1
  59. package/src/exec/non-interactive-env.ts +43 -0
  60. package/src/export/custom-share.ts +1 -1
  61. package/src/export/html/index.ts +1 -2
  62. package/src/extensibility/custom-commands/loader.ts +1 -2
  63. package/src/extensibility/plugins/installer.ts +1 -2
  64. package/src/extensibility/plugins/loader.ts +1 -2
  65. package/src/extensibility/plugins/manager.ts +3 -2
  66. package/src/extensibility/skills.ts +59 -115
  67. package/src/index.ts +1 -3
  68. package/src/internal-urls/docs-index.generated.ts +1 -1
  69. package/src/ipy/executor.ts +1 -2
  70. package/src/ipy/gateway-coordinator.ts +1 -2
  71. package/src/ipy/modules.ts +1 -1
  72. package/src/ipy/runtime.ts +1 -3
  73. package/src/main.ts +1 -2
  74. package/src/mcp/config.ts +1 -1
  75. package/src/mcp/transports/stdio.ts +1 -2
  76. package/src/memories/index.ts +1 -2
  77. package/src/modes/components/diff.ts +49 -19
  78. package/src/modes/components/extensions/extension-dashboard.ts +1 -1
  79. package/src/modes/components/extensions/inspector-panel.ts +8 -2
  80. package/src/modes/components/footer.ts +1 -2
  81. package/src/modes/components/status-line/segments.ts +1 -2
  82. package/src/modes/components/tool-execution.ts +3 -10
  83. package/src/modes/components/welcome.ts +1 -1
  84. package/src/modes/controllers/command-controller.ts +1 -2
  85. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  86. package/src/modes/controllers/selector-controller.ts +1 -1
  87. package/src/modes/controllers/ssh-command-controller.ts +1 -1
  88. package/src/modes/interactive-mode.ts +2 -3
  89. package/src/modes/shared.ts +1 -2
  90. package/src/modes/theme/theme.ts +1 -2
  91. package/src/patch/index.ts +1 -25
  92. package/src/prompts/agents/designer.md +7 -10
  93. package/src/prompts/agents/explore.md +15 -23
  94. package/src/prompts/agents/init.md +23 -23
  95. package/src/prompts/agents/plan.md +14 -77
  96. package/src/prompts/agents/reviewer.md +6 -5
  97. package/src/prompts/agents/task.md +13 -11
  98. package/src/prompts/compaction/branch-summary.md +3 -3
  99. package/src/prompts/compaction/compaction-short-summary.md +7 -7
  100. package/src/prompts/compaction/compaction-summary-context.md +1 -1
  101. package/src/prompts/compaction/compaction-summary.md +5 -5
  102. package/src/prompts/compaction/compaction-turn-prefix.md +3 -3
  103. package/src/prompts/compaction/compaction-update-summary.md +11 -11
  104. package/src/prompts/memories/consolidation.md +5 -5
  105. package/src/prompts/memories/read-path.md +6 -6
  106. package/src/prompts/memories/stage_one_input.md +1 -1
  107. package/src/prompts/memories/stage_one_system.md +5 -5
  108. package/src/prompts/review-request.md +4 -4
  109. package/src/prompts/system/agent-creation-architect.md +17 -17
  110. package/src/prompts/system/agent-creation-user.md +2 -2
  111. package/src/prompts/system/custom-system-prompt.md +4 -4
  112. package/src/prompts/system/plan-mode-active.md +20 -20
  113. package/src/prompts/system/plan-mode-approved.md +7 -7
  114. package/src/prompts/system/plan-mode-reference.md +2 -2
  115. package/src/prompts/system/plan-mode-subagent.md +8 -8
  116. package/src/prompts/system/subagent-submit-reminder.md +5 -5
  117. package/src/prompts/system/subagent-system-prompt.md +29 -22
  118. package/src/prompts/system/subagent-user-prompt.md +7 -3
  119. package/src/prompts/system/summarization-system.md +1 -1
  120. package/src/prompts/system/system-prompt.md +201 -226
  121. package/src/prompts/system/title-system.md +2 -2
  122. package/src/prompts/system/ttsr-interrupt.md +1 -1
  123. package/src/prompts/system/web-search.md +16 -16
  124. package/src/prompts/tools/ask.md +1 -3
  125. package/src/prompts/tools/await.md +2 -4
  126. package/src/prompts/tools/bash.md +5 -7
  127. package/src/prompts/tools/browser.md +4 -6
  128. package/src/prompts/tools/calculator.md +1 -3
  129. package/src/prompts/tools/cancel-job.md +2 -4
  130. package/src/prompts/tools/exit-plan-mode.md +7 -7
  131. package/src/prompts/tools/fetch.md +0 -2
  132. package/src/prompts/tools/find.md +3 -5
  133. package/src/prompts/tools/gemini-image.md +6 -22
  134. package/src/prompts/tools/grep.md +4 -6
  135. package/src/prompts/tools/hashline.md +12 -15
  136. package/src/prompts/tools/lsp.md +1 -3
  137. package/src/prompts/tools/patch.md +7 -9
  138. package/src/prompts/tools/python.md +10 -14
  139. package/src/prompts/tools/read.md +0 -2
  140. package/src/prompts/tools/replace.md +5 -7
  141. package/src/prompts/tools/ssh.md +3 -5
  142. package/src/prompts/tools/task.md +6 -8
  143. package/src/prompts/tools/todo-write.md +7 -9
  144. package/src/prompts/tools/web-search.md +3 -5
  145. package/src/prompts/tools/write.md +3 -5
  146. package/src/sdk.ts +1 -2
  147. package/src/session/agent-session.ts +10 -26
  148. package/src/session/agent-storage.ts +1 -2
  149. package/src/session/history-storage.ts +1 -2
  150. package/src/session/session-manager.ts +10 -2
  151. package/src/ssh/connection-manager.ts +11 -2
  152. package/src/ssh/sshfs-mount.ts +7 -1
  153. package/src/system-prompt.ts +25 -103
  154. package/src/task/agents.ts +1 -1
  155. package/src/task/worktree.ts +1 -2
  156. package/src/tools/ask.ts +0 -1
  157. package/src/tools/await-tool.ts +6 -3
  158. package/src/tools/bash-interactive.ts +2 -45
  159. package/src/tools/bash.ts +5 -5
  160. package/src/tools/browser.ts +1 -2
  161. package/src/tools/gemini-image.ts +8 -28
  162. package/src/tools/json-tree.ts +2 -1
  163. package/src/tools/python.ts +1 -1
  164. package/src/tools/read.ts +1 -2
  165. package/src/tools/render-utils.ts +5 -2
  166. package/src/tools/todo-write.ts +11 -9
  167. package/src/utils/tools-manager.ts +1 -2
  168. package/src/web/scrapers/artifacthub.ts +2 -1
  169. package/src/web/scrapers/aur.ts +2 -1
  170. package/src/web/scrapers/biorxiv.ts +2 -1
  171. package/src/web/scrapers/bluesky.ts +2 -1
  172. package/src/web/scrapers/chocolatey.ts +2 -1
  173. package/src/web/scrapers/cisa-kev.ts +2 -1
  174. package/src/web/scrapers/clojars.ts +2 -1
  175. package/src/web/scrapers/coingecko.ts +2 -1
  176. package/src/web/scrapers/crates-io.ts +2 -1
  177. package/src/web/scrapers/crossref.ts +2 -1
  178. package/src/web/scrapers/discogs.ts +3 -1
  179. package/src/web/scrapers/discourse.ts +2 -1
  180. package/src/web/scrapers/dockerhub.ts +2 -1
  181. package/src/web/scrapers/fdroid.ts +2 -1
  182. package/src/web/scrapers/firefox-addons.ts +2 -1
  183. package/src/web/scrapers/flathub.ts +2 -1
  184. package/src/web/scrapers/gitlab.ts +1 -1
  185. package/src/web/scrapers/go-pkg.ts +2 -1
  186. package/src/web/scrapers/hackage.ts +2 -1
  187. package/src/web/scrapers/hackernews.ts +2 -1
  188. package/src/web/scrapers/hex.ts +2 -1
  189. package/src/web/scrapers/huggingface.ts +2 -1
  190. package/src/web/scrapers/jetbrains-marketplace.ts +2 -1
  191. package/src/web/scrapers/lemmy.ts +2 -1
  192. package/src/web/scrapers/lobsters.ts +2 -1
  193. package/src/web/scrapers/mastodon.ts +2 -1
  194. package/src/web/scrapers/maven.ts +2 -1
  195. package/src/web/scrapers/mdn.ts +2 -1
  196. package/src/web/scrapers/metacpan.ts +2 -1
  197. package/src/web/scrapers/musicbrainz.ts +3 -1
  198. package/src/web/scrapers/npm.ts +2 -1
  199. package/src/web/scrapers/nuget.ts +2 -1
  200. package/src/web/scrapers/nvd.ts +2 -1
  201. package/src/web/scrapers/ollama.ts +2 -1
  202. package/src/web/scrapers/open-vsx.ts +2 -1
  203. package/src/web/scrapers/opencorporates.ts +2 -1
  204. package/src/web/scrapers/openlibrary.ts +2 -1
  205. package/src/web/scrapers/orcid.ts +3 -1
  206. package/src/web/scrapers/osv.ts +2 -1
  207. package/src/web/scrapers/packagist.ts +2 -1
  208. package/src/web/scrapers/pub-dev.ts +2 -1
  209. package/src/web/scrapers/pubmed.ts +2 -1
  210. package/src/web/scrapers/pypi.ts +2 -1
  211. package/src/web/scrapers/rawg.ts +2 -8
  212. package/src/web/scrapers/reddit.ts +2 -1
  213. package/src/web/scrapers/repology.ts +2 -1
  214. package/src/web/scrapers/rfc.ts +2 -1
  215. package/src/web/scrapers/rubygems.ts +2 -1
  216. package/src/web/scrapers/searchcode.ts +2 -1
  217. package/src/web/scrapers/sec-edgar.ts +2 -1
  218. package/src/web/scrapers/semantic-scholar.ts +2 -1
  219. package/src/web/scrapers/snapcraft.ts +2 -1
  220. package/src/web/scrapers/sourcegraph.ts +2 -1
  221. package/src/web/scrapers/spdx.ts +2 -1
  222. package/src/web/scrapers/stackoverflow.ts +2 -1
  223. package/src/web/scrapers/terraform.ts +2 -1
  224. package/src/web/scrapers/types.ts +0 -11
  225. package/src/web/scrapers/vimeo.ts +2 -1
  226. package/src/web/scrapers/vscode-marketplace.ts +2 -1
  227. package/src/web/scrapers/w3c.ts +2 -1
  228. package/src/web/scrapers/wikidata.ts +2 -1
  229. package/src/web/search/index.ts +10 -14
  230. package/src/web/search/provider.ts +2 -2
  231. package/src/web/search/providers/codex.ts +1 -2
  232. package/src/web/search/providers/exa.ts +1 -6
  233. package/src/web/search/providers/gemini.ts +1 -1
  234. package/src/web/search/providers/perplexity.ts +1 -2
  235. package/src/web/search/providers/utils.ts +1 -1
@@ -10,7 +10,7 @@ Determine:
10
10
  4. CHANGELOG: Metadata for user-visible changes
11
11
  </instructions>
12
12
  <scope-rules>
13
- - Component name if >=60% changes target it
13
+ - Component name if 60% changes target it
14
14
  - null if spread across multiple components
15
15
  - scope_candidates as primary source
16
16
  - Valid: specific component names (api, parser, config, etc.)
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Types for the omp commit pipeline.
3
3
  */
4
-
5
4
  export type CommitType =
6
5
  | "feat"
7
6
  | "fix"
@@ -24,6 +23,16 @@ export type ChangelogCategory =
24
23
  | "Fixed"
25
24
  | "Security";
26
25
 
26
+ export const CHANGELOG_CATEGORIES: ChangelogCategory[] = [
27
+ "Breaking Changes",
28
+ "Added",
29
+ "Changed",
30
+ "Deprecated",
31
+ "Removed",
32
+ "Fixed",
33
+ "Security",
34
+ ];
35
+
27
36
  export interface CommitCommandArgs {
28
37
  /** Push after commit */
29
38
  push: boolean;
@@ -8,8 +8,7 @@ import {
8
8
  matchesKey,
9
9
  setEditorKeybindings,
10
10
  } from "@oh-my-pi/pi-tui";
11
- import { isEnoent, logger } from "@oh-my-pi/pi-utils";
12
- import { getAgentDir } from "@oh-my-pi/pi-utils/dirs";
11
+ import { getAgentDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
13
12
 
14
13
  /**
15
14
  * Application-level actions (coding agent specific).
@@ -47,7 +47,7 @@ export const MODEL_ROLES: Record<ModelRole, ModelRoleInfo> = {
47
47
  smol: { tag: "SMOL", name: "Fast", color: "warning" },
48
48
  slow: { tag: "SLOW", name: "Thinking", color: "accent" },
49
49
  plan: { tag: "PLAN", name: "Architect", color: "muted" },
50
- commit: { name: "Commit" },
50
+ commit: { tag: "COMMIT", name: "Commit", color: "dim" },
51
51
  };
52
52
 
53
53
  export const MODEL_ROLE_IDS: ModelRole[] = ["default", "smol", "slow", "plan", "commit"];
@@ -1,7 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { logger } from "@oh-my-pi/pi-utils";
4
- import { getProjectDir, getProjectPromptsDir, getPromptsDir } from "@oh-my-pi/pi-utils/dirs";
3
+ import { getProjectDir, getProjectPromptsDir, getPromptsDir, logger } from "@oh-my-pi/pi-utils";
5
4
  import Handlebars from "handlebars";
6
5
  import { computeLineHash } from "../patch/hashline";
7
6
  import { jtdToTypeScript } from "../tools/jtd-to-typescript";
@@ -230,6 +229,19 @@ handlebars.registerHelper("jtdToTypeScript", (schema: unknown): string => jtdToT
230
229
 
231
230
  handlebars.registerHelper("jsonStringify", (value: unknown): string => JSON.stringify(value));
232
231
 
232
+ /**
233
+ * Renders a section separator:
234
+ *
235
+ * ═══════════════════════════════
236
+ * Name
237
+ * ═══════════════════════════════
238
+ */
239
+ export function sectionSeparator(name: string): string {
240
+ return `\n\n═══════════${name}═══════════\n`;
241
+ }
242
+
243
+ handlebars.registerHelper("SECTION_SEPERATOR", (name: unknown): string => sectionSeparator(String(name)));
244
+
233
245
  /**
234
246
  * {{hlineref lineNum "content"}} — compute a real hashline ref for prompt examples.
235
247
  * Returns `"lineNum#hash"` using the actual hash algorithm.
@@ -176,6 +176,16 @@ export const SETTINGS_SCHEMA = {
176
176
  description: "Use blue instead of green for diff additions",
177
177
  },
178
178
  },
179
+ "display.tabWidth": {
180
+ type: "number",
181
+ default: 3,
182
+ ui: {
183
+ tab: "display",
184
+ label: "Tab width",
185
+ description: "Default number of spaces used when rendering tab characters",
186
+ submenu: true,
187
+ },
188
+ },
179
189
  defaultThinkingLevel: {
180
190
  type: "enum",
181
191
  values: ["off", "minimal", "low", "medium", "high", "xhigh"] as const,
@@ -13,8 +13,15 @@
13
13
 
14
14
  import * as fs from "node:fs";
15
15
  import * as path from "node:path";
16
- import { isEnoent, logger, procmgr } from "@oh-my-pi/pi-utils";
17
- import { getAgentDbPath, getAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
16
+ import {
17
+ getAgentDbPath,
18
+ getAgentDir,
19
+ getProjectDir,
20
+ isEnoent,
21
+ logger,
22
+ procmgr,
23
+ setDefaultTabWidth,
24
+ } from "@oh-my-pi/pi-utils";
18
25
  import { YAML } from "bun";
19
26
  import { type Settings as SettingsCapabilityItem, settingsCapability } from "../capability/settings";
20
27
  import type { ModelRole } from "../config/model-registry";
@@ -438,6 +445,7 @@ export class Settings {
438
445
 
439
446
  // Build merged view
440
447
  this.#rebuildMerged();
448
+ this.#fireAllHooks();
441
449
  return this;
442
450
  }
443
451
 
@@ -610,6 +618,16 @@ export class Settings {
610
618
  this.#merged = this.#deepMerge(this.#merged, this.#overrides);
611
619
  }
612
620
 
621
+ #fireAllHooks(): void {
622
+ for (const key of Object.keys(SETTING_HOOKS) as SettingPath[]) {
623
+ const hook = SETTING_HOOKS[key];
624
+ if (hook) {
625
+ const value = this.get(key);
626
+ hook(value, value);
627
+ }
628
+ }
629
+ }
630
+
613
631
  #deepMerge(base: RawSettings, overrides: RawSettings): RawSettings {
614
632
  const result = { ...base };
615
633
  for (const key of Object.keys(overrides)) {
@@ -666,6 +684,11 @@ const SETTING_HOOKS: Partial<Record<SettingPath, SettingHook<any>>> = {
666
684
  });
667
685
  }
668
686
  },
687
+ "display.tabWidth": value => {
688
+ if (typeof value === "number") {
689
+ setDefaultTabWidth(value);
690
+ }
691
+ },
669
692
  };
670
693
 
671
694
  // ═══════════════════════════════════════════════════════════════════════════
package/src/config.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import { isEnoent, logger } from "@oh-my-pi/pi-utils";
5
- import { CONFIG_DIR_NAME, getAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
4
+ import { CONFIG_DIR_NAME, getAgentDir, getProjectDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
6
5
  import type { TSchema } from "@sinclair/typebox";
7
6
  import { Value } from "@sinclair/typebox/value";
8
7
  import { Ajv, type ErrorObject, type ValidateFunction } from "ajv";
@@ -7,7 +7,7 @@ import * as fs from "node:fs/promises";
7
7
  import * as url from "node:url";
8
8
  import { getWorkProfile } from "@oh-my-pi/pi-natives";
9
9
  import { Container, Loader, type SelectItem, SelectList, Spacer, Text } from "@oh-my-pi/pi-tui";
10
- import { getSessionsDir } from "@oh-my-pi/pi-utils/dirs";
10
+ import { getSessionsDir } from "@oh-my-pi/pi-utils";
11
11
  import { DynamicBorder } from "../modes/components/dynamic-border";
12
12
  import { getSelectListTheme, getSymbolTheme, theme } from "../modes/theme/theme";
13
13
  import type { InteractiveModeContext } from "../modes/types";
@@ -6,8 +6,7 @@
6
6
  import * as fs from "node:fs/promises";
7
7
  import * as path from "node:path";
8
8
  import type { WorkProfile } from "@oh-my-pi/pi-natives";
9
- import { isEnoent } from "@oh-my-pi/pi-utils";
10
- import { APP_NAME, getLogPath, getLogsDir, getReportsDir } from "@oh-my-pi/pi-utils/dirs";
9
+ import { APP_NAME, getLogPath, getLogsDir, getReportsDir, isEnoent } from "@oh-my-pi/pi-utils";
11
10
  import type { CpuProfile, HeapSnapshot } from "./profiler";
12
11
  import { collectSystemInfo, sanitizeEnv } from "./system-info";
13
12
 
@@ -3,8 +3,7 @@
3
3
  */
4
4
 
5
5
  import * as os from "node:os";
6
- import { formatBytes } from "@oh-my-pi/pi-utils";
7
- import { getProjectDir, VERSION } from "@oh-my-pi/pi-utils/dirs";
6
+ import { formatBytes, getProjectDir, VERSION } from "@oh-my-pi/pi-utils";
8
7
 
9
8
  export interface SystemInfo {
10
9
  os: string;
@@ -13,7 +13,7 @@ import { type Skill, skillCapability } from "../capability/skill";
13
13
  import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
14
14
  import { type SystemPrompt, systemPromptCapability } from "../capability/system-prompt";
15
15
  import type { LoadContext, LoadResult } from "../capability/types";
16
- import { buildRuleFromMarkdown, createSourceMeta, loadFilesFromDir, loadSkillsFromDir } from "./helpers";
16
+ import { buildRuleFromMarkdown, createSourceMeta, loadFilesFromDir, scanSkillsFromDir } from "./helpers";
17
17
 
18
18
  const PROVIDER_ID = "agents";
19
19
  const DISPLAY_NAME = "Agents (standard)";
@@ -28,7 +28,7 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
28
28
  const items: Skill[] = [];
29
29
  const warnings: string[] = [];
30
30
  for (const userSkillsDir of getUserAgentPathCandidates(ctx, "skills")) {
31
- const result = await loadSkillsFromDir(ctx, {
31
+ const result = await scanSkillsFromDir(ctx, {
32
32
  dir: userSkillsDir,
33
33
  providerId: PROVIDER_ID,
34
34
  level: "user",
@@ -4,7 +4,7 @@
4
4
  * Primary provider for OMP native configs. Supports all capabilities.
5
5
  */
6
6
  import * as path from "node:path";
7
- import { logger } from "@oh-my-pi/pi-utils";
7
+ import { logger, tryParseJson } from "@oh-my-pi/pi-utils";
8
8
  import { registerProvider } from "../capability";
9
9
  import { type ContextFile, contextFileCapability } from "../capability/context-file";
10
10
  import { type Extension, type ExtensionManifest, extensionCapability } from "../capability/extension";
@@ -30,9 +30,8 @@ import {
30
30
  expandEnvVarsDeep,
31
31
  getExtensionNameFromPath,
32
32
  loadFilesFromDir,
33
- loadSkillsFromDir,
34
- parseJSON,
35
33
  SOURCE_PATHS,
34
+ scanSkillsFromDir,
36
35
  } from "./helpers";
37
36
 
38
37
  const PROVIDER_ID = "native";
@@ -98,7 +97,7 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
98
97
 
99
98
  const parseMcpServers = (content: string, path: string, level: "user" | "project"): MCPServer[] => {
100
99
  const result: MCPServer[] = [];
101
- const data = parseJSON<{ mcpServers?: Record<string, unknown> }>(content);
100
+ const data = tryParseJson<{ mcpServers?: Record<string, unknown> }>(content);
102
101
  if (!data?.mcpServers) return result;
103
102
 
104
103
  const expanded = expandEnvVarsDeep(data.mcpServers);
@@ -245,7 +244,7 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
245
244
  const configDirs = await getConfigDirs(ctx);
246
245
  const results = await Promise.all(
247
246
  configDirs.map(({ dir, level }) =>
248
- loadSkillsFromDir(ctx, {
247
+ scanSkillsFromDir(ctx, {
249
248
  dir: path.join(dir, "skills"),
250
249
  providerId: PROVIDER_ID,
251
250
  level,
@@ -404,7 +403,7 @@ async function loadExtensionModules(ctx: LoadContext): Promise<LoadResult<Extens
404
403
  if (!settingsContent) continue;
405
404
 
406
405
  const settingsPath = path.join(dir, "settings.json");
407
- const settingsData = parseJSON<{ extensions?: unknown }>(settingsContent);
406
+ const settingsData = tryParseJson<{ extensions?: unknown }>(settingsContent);
408
407
  const extensions = settingsData?.extensions;
409
408
  if (!Array.isArray(extensions)) continue;
410
409
 
@@ -508,7 +507,7 @@ async function loadExtensions(ctx: LoadContext): Promise<LoadResult<Extension>>
508
507
  if (!content) continue;
509
508
 
510
509
  const { extDir, manifestPath, entryName, level } = manifestCandidates[i];
511
- const manifest = parseJSON<ExtensionManifest>(content);
510
+ const manifest = tryParseJson<ExtensionManifest>(content);
512
511
  if (!manifest) {
513
512
  warnings.push(`Failed to parse ${manifestPath}`);
514
513
  continue;
@@ -655,21 +654,31 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
655
654
  extensions: ["json", "md", "ts", "js", "sh", "bash", "py"],
656
655
  transform: (name, content, path, source) => {
657
656
  if (name.endsWith(".json")) {
658
- const data = parseJSON<{ name?: string; description?: string }>(content);
657
+ const data = tryParseJson<{ name?: string; description?: string }>(content);
658
+ const toolName = data?.name || name.replace(/\.json$/, "");
659
+ const description =
660
+ typeof data?.description === "string" && data.description.trim()
661
+ ? data.description
662
+ : `${toolName} custom tool`;
659
663
  return {
660
- name: data?.name || name.replace(/\.json$/, ""),
664
+ name: toolName,
661
665
  path,
662
- description: data?.description,
666
+ description,
663
667
  level,
664
668
  _source: source,
665
669
  };
666
670
  }
667
671
  if (name.endsWith(".md")) {
668
672
  const { frontmatter } = parseFrontmatter(content, { source: path });
673
+ const toolName = (frontmatter.name as string) || name.replace(/\.md$/, "");
674
+ const description =
675
+ typeof frontmatter.description === "string" && frontmatter.description.trim()
676
+ ? String(frontmatter.description)
677
+ : `${toolName} custom tool`;
669
678
  return {
670
- name: (frontmatter.name as string) || name.replace(/\.md$/, ""),
679
+ name: toolName,
671
680
  path,
672
- description: frontmatter.description as string | undefined,
681
+ description,
673
682
  level,
674
683
  _source: source,
675
684
  };
@@ -679,6 +688,7 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
679
688
  return {
680
689
  name: toolName,
681
690
  path,
691
+ description: `${toolName} custom tool`,
682
692
  level,
683
693
  _source: source,
684
694
  };
@@ -715,7 +725,7 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
715
725
  items.push({
716
726
  name: entryName,
717
727
  path: indexPath,
718
- description: undefined,
728
+ description: `${entryName} custom tool`,
719
729
  level,
720
730
  _source: createSourceMeta(PROVIDER_ID, indexPath, level),
721
731
  });
@@ -743,7 +753,7 @@ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
743
753
  const content = await readFile(settingsPath);
744
754
  if (!content) continue;
745
755
 
746
- const data = parseJSON<Record<string, unknown>>(content);
756
+ const data = tryParseJson<Record<string, unknown>>(content);
747
757
  if (!data) {
748
758
  warnings.push(`Failed to parse ${settingsPath}`);
749
759
  continue;
@@ -11,7 +11,7 @@ import { type Skill, skillCapability } from "../capability/skill";
11
11
  import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
12
12
  import { type CustomTool, toolCapability } from "../capability/tool";
13
13
  import type { LoadContext, LoadResult } from "../capability/types";
14
- import { type ClaudePluginRoot, listClaudePluginRoots, loadFilesFromDir, loadSkillsFromDir } from "./helpers";
14
+ import { type ClaudePluginRoot, listClaudePluginRoots, loadFilesFromDir, scanSkillsFromDir } from "./helpers";
15
15
 
16
16
  const PROVIDER_ID = "claude-plugins";
17
17
  const DISPLAY_NAME = "Claude Code Marketplace";
@@ -31,7 +31,7 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
31
31
  const results = await Promise.all(
32
32
  roots.map(async root => {
33
33
  const skillsDir = path.join(root.path, "skills");
34
- return loadSkillsFromDir(ctx, {
34
+ return scanSkillsFromDir(ctx, {
35
35
  dir: skillsDir,
36
36
  providerId: PROVIDER_ID,
37
37
  level: root.scope,
@@ -152,6 +152,7 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
152
152
  return {
153
153
  name: toolName,
154
154
  path: filePath,
155
+ description: `${toolName} custom tool`,
155
156
  level: root.scope,
156
157
  _source: source,
157
158
  };
@@ -5,6 +5,7 @@
5
5
  * Priority: 80 (tool-specific, below builtin but above shared standards)
6
6
  */
7
7
  import * as path from "node:path";
8
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
8
9
  import { registerProvider } from "../capability";
9
10
  import { type ContextFile, contextFileCapability } from "../capability/context-file";
10
11
  import { type ExtensionModule, extensionModuleCapability } from "../capability/extension-module";
@@ -24,8 +25,7 @@ import {
24
25
  expandEnvVarsDeep,
25
26
  getExtensionNameFromPath,
26
27
  loadFilesFromDir,
27
- loadSkillsFromDir,
28
- parseJSON,
28
+ scanSkillsFromDir,
29
29
  } from "./helpers";
30
30
 
31
31
  const PROVIDER_ID = "claude";
@@ -77,7 +77,7 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
77
77
 
78
78
  const parseMcpServers = (content: string | null, path: string, level: "user" | "project"): MCPServer[] => {
79
79
  if (!content) return [];
80
- const json = parseJSON<{ mcpServers?: Record<string, unknown> }>(content);
80
+ const json = tryParseJson<{ mcpServers?: Record<string, unknown> }>(content);
81
81
  if (!json?.mcpServers) return [];
82
82
 
83
83
  const mcpServers = expandEnvVarsDeep(json.mcpServers);
@@ -163,8 +163,8 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
163
163
  const projectSkillsDir = path.join(getProjectClaude(ctx), "skills");
164
164
 
165
165
  const results = await Promise.all([
166
- loadSkillsFromDir(ctx, { dir: userSkillsDir, providerId: PROVIDER_ID, level: "user" }),
167
- loadSkillsFromDir(ctx, { dir: projectSkillsDir, providerId: PROVIDER_ID, level: "project" }),
166
+ scanSkillsFromDir(ctx, { dir: userSkillsDir, providerId: PROVIDER_ID, level: "user" }),
167
+ scanSkillsFromDir(ctx, { dir: projectSkillsDir, providerId: PROVIDER_ID, level: "project" }),
168
168
  ]);
169
169
 
170
170
  return {
@@ -324,10 +324,10 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
324
324
  const userResult = await loadFilesFromDir<CustomTool>(ctx, userToolsDir, PROVIDER_ID, "user", {
325
325
  transform: (name, _content, path, source) => {
326
326
  const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
327
-
328
327
  return {
329
328
  name: toolName,
330
329
  path,
330
+ description: `${toolName} custom tool`,
331
331
  level: "user",
332
332
  _source: source,
333
333
  };
@@ -343,10 +343,10 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
343
343
  const projectResult = await loadFilesFromDir<CustomTool>(ctx, projectToolsDir, PROVIDER_ID, "project", {
344
344
  transform: (name, _content, path, source) => {
345
345
  const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
346
-
347
346
  return {
348
347
  name: toolName,
349
348
  path,
349
+ description: `${toolName} custom tool`,
350
350
  level: "project",
351
351
  _source: source,
352
352
  };
@@ -396,7 +396,7 @@ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
396
396
 
397
397
  const userContent = await readFile(userSettingsJson);
398
398
  if (userContent) {
399
- const data = parseJSON<Record<string, unknown>>(userContent);
399
+ const data = tryParseJson<Record<string, unknown>>(userContent);
400
400
  if (data) {
401
401
  items.push({
402
402
  path: userSettingsJson,
@@ -413,7 +413,7 @@ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
413
413
  const projectSettingsJson = path.join(projectBase, "settings.json");
414
414
  const projectContent = await readFile(projectSettingsJson);
415
415
  if (projectContent) {
416
- const data = parseJSON<Record<string, unknown>>(projectContent);
416
+ const data = tryParseJson<Record<string, unknown>>(projectContent);
417
417
  if (data) {
418
418
  items.push({
419
419
  path: projectSettingsJson,
@@ -35,8 +35,8 @@ import {
35
35
  discoverExtensionModulePaths,
36
36
  getExtensionNameFromPath,
37
37
  loadFilesFromDir,
38
- loadSkillsFromDir,
39
38
  SOURCE_PATHS,
39
+ scanSkillsFromDir,
40
40
  } from "./helpers";
41
41
 
42
42
  const PROVIDER_ID = "codex";
@@ -214,12 +214,12 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
214
214
  const projectSkillsDir = path.join(codexDir, "skills");
215
215
 
216
216
  const results = await Promise.all([
217
- loadSkillsFromDir(ctx, {
217
+ scanSkillsFromDir(ctx, {
218
218
  dir: userSkillsDir,
219
219
  providerId: PROVIDER_ID,
220
220
  level: "user",
221
221
  }),
222
- loadSkillsFromDir(ctx, {
222
+ scanSkillsFromDir(ctx, {
223
223
  dir: projectSkillsDir,
224
224
  providerId: PROVIDER_ID,
225
225
  level: "project",
@@ -13,6 +13,8 @@
13
13
  * - rules: From rules/*.mdc files with MDC frontmatter (description, globs, alwaysApply)
14
14
  * - settings: From settings.json if present
15
15
  */
16
+
17
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
16
18
  import { registerProvider } from "../capability";
17
19
  import { readFile } from "../capability/fs";
18
20
  import { type MCPServer, mcpCapability } from "../capability/mcp";
@@ -28,7 +30,6 @@ import {
28
30
  getProjectPath,
29
31
  getUserPath,
30
32
  loadFilesFromDir,
31
- parseJSON,
32
33
  } from "./helpers";
33
34
 
34
35
  const PROVIDER_ID = "cursor";
@@ -46,7 +47,7 @@ function parseMCPServers(
46
47
  ): { items: MCPServer[]; warning?: string } {
47
48
  const items: MCPServer[] = [];
48
49
 
49
- const parsed = parseJSON<{ mcpServers?: Record<string, unknown> }>(content);
50
+ const parsed = tryParseJson<{ mcpServers?: Record<string, unknown> }>(content);
50
51
  if (!parsed?.mcpServers) {
51
52
  return { items, warning: `${path}: missing or invalid 'mcpServers' key` };
52
53
  }
@@ -158,7 +159,7 @@ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
158
159
  const projectContentPromise = projectPath ? readFile(projectPath) : Promise.resolve(null);
159
160
 
160
161
  if (userContent && userPath) {
161
- const parsed = parseJSON<Record<string, unknown>>(userContent);
162
+ const parsed = tryParseJson<Record<string, unknown>>(userContent);
162
163
  if (parsed) {
163
164
  items.push({
164
165
  path: userPath,
@@ -173,7 +174,7 @@ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
173
174
 
174
175
  const projectContent = await projectContentPromise;
175
176
  if (projectContent && projectPath) {
176
- const parsed = parseJSON<Record<string, unknown>>(projectContent);
177
+ const parsed = tryParseJson<Record<string, unknown>>(projectContent);
177
178
  if (parsed) {
178
179
  items.push({
179
180
  path: projectPath,
@@ -16,6 +16,7 @@
16
16
  * - settings: From settings.json
17
17
  */
18
18
  import * as path from "node:path";
19
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
19
20
  import { registerProvider } from "../capability";
20
21
  import { type ContextFile, contextFileCapability } from "../capability/context-file";
21
22
  import { type Extension, type ExtensionManifest, extensionCapability } from "../capability/extension";
@@ -33,7 +34,6 @@ import {
33
34
  getExtensionNameFromPath,
34
35
  getProjectPath,
35
36
  getUserPath,
36
- parseJSON,
37
37
  } from "./helpers";
38
38
 
39
39
  const PROVIDER_ID = "gemini";
@@ -80,7 +80,7 @@ async function loadMCPFromSettings(
80
80
  return { items, warnings };
81
81
  }
82
82
 
83
- const parsed = parseJSON<{ mcpServers?: Record<string, unknown> }>(content);
83
+ const parsed = tryParseJson<{ mcpServers?: Record<string, unknown> }>(content);
84
84
  if (!parsed) {
85
85
  warnings.push(`Invalid JSON in ${path}`);
86
86
  return { items, warnings };
@@ -206,7 +206,7 @@ async function loadExtensionsFromDir(extensionsDir: string, level: "user" | "pro
206
206
  for (const { entry, extPath, manifestPath, content } of results) {
207
207
  if (!content) continue;
208
208
 
209
- const manifest = parseJSON<ExtensionManifest>(content);
209
+ const manifest = tryParseJson<ExtensionManifest>(content);
210
210
  if (!manifest) {
211
211
  warnings.push(`Invalid JSON in ${manifestPath}`);
212
212
  continue;
@@ -268,7 +268,7 @@ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
268
268
  if (userPath) {
269
269
  const content = await readFile(userPath);
270
270
  if (content) {
271
- const parsed = parseJSON<Record<string, unknown>>(content);
271
+ const parsed = tryParseJson<Record<string, unknown>>(content);
272
272
  if (parsed) {
273
273
  items.push({
274
274
  path: userPath,
@@ -287,7 +287,7 @@ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
287
287
  if (projectPath) {
288
288
  const content = await readFile(projectPath);
289
289
  if (content) {
290
- const parsed = parseJSON<Record<string, unknown>>(content);
290
+ const parsed = tryParseJson<Record<string, unknown>>(content);
291
291
  if (parsed) {
292
292
  items.push({
293
293
  path: projectPath,