@oh-my-pi/pi-coding-agent 8.1.0 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (402) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/docs/session.md +111 -46
  3. package/examples/custom-tools/hello/index.ts +1 -1
  4. package/examples/custom-tools/todo/index.ts +3 -4
  5. package/examples/extensions/api-demo.ts +0 -1
  6. package/examples/extensions/chalk-logger.ts +2 -3
  7. package/examples/extensions/hello.ts +0 -1
  8. package/examples/extensions/pirate.ts +0 -1
  9. package/examples/extensions/plan-mode.ts +15 -16
  10. package/examples/extensions/todo.ts +3 -4
  11. package/examples/extensions/tools.ts +1 -2
  12. package/examples/extensions/with-deps/index.ts +0 -1
  13. package/examples/hooks/auto-commit-on-exit.ts +1 -2
  14. package/examples/hooks/confirm-destructive.ts +0 -1
  15. package/examples/hooks/custom-compaction.ts +1 -2
  16. package/examples/hooks/dirty-repo-guard.ts +0 -1
  17. package/examples/hooks/file-trigger.ts +3 -4
  18. package/examples/hooks/git-checkpoint.ts +0 -1
  19. package/examples/hooks/handoff.ts +3 -4
  20. package/examples/hooks/permission-gate.ts +1 -2
  21. package/examples/hooks/protected-paths.ts +1 -2
  22. package/examples/hooks/qna.ts +2 -3
  23. package/examples/hooks/snake.ts +4 -5
  24. package/examples/hooks/status-line.ts +0 -1
  25. package/examples/sdk/01-minimal.ts +2 -3
  26. package/examples/sdk/02-custom-model.ts +2 -3
  27. package/examples/sdk/03-custom-prompt.ts +3 -4
  28. package/examples/sdk/04-skills.ts +2 -3
  29. package/examples/sdk/06-extensions.ts +1 -2
  30. package/examples/sdk/06-hooks.ts +6 -7
  31. package/examples/sdk/07-context-files.ts +0 -1
  32. package/examples/sdk/08-prompt-templates.ts +0 -1
  33. package/examples/sdk/08-slash-commands.ts +0 -1
  34. package/examples/sdk/09-api-keys-and-oauth.ts +0 -1
  35. package/examples/sdk/10-settings.ts +0 -1
  36. package/examples/sdk/11-sessions.ts +0 -1
  37. package/package.json +51 -23
  38. package/scripts/format-prompts.ts +0 -1
  39. package/src/capability/context-file.ts +2 -3
  40. package/src/capability/extension-module.ts +2 -3
  41. package/src/capability/extension.ts +2 -3
  42. package/src/capability/fs.ts +20 -21
  43. package/src/capability/hook.ts +2 -3
  44. package/src/capability/index.ts +15 -16
  45. package/src/capability/instruction.ts +2 -3
  46. package/src/capability/mcp.ts +2 -3
  47. package/src/capability/prompt.ts +2 -3
  48. package/src/capability/rule.ts +2 -3
  49. package/src/capability/settings.ts +1 -2
  50. package/src/capability/skill.ts +2 -3
  51. package/src/capability/slash-command.ts +2 -3
  52. package/src/capability/ssh.ts +2 -3
  53. package/src/capability/system-prompt.ts +2 -3
  54. package/src/capability/tool.ts +2 -3
  55. package/src/cli/args.ts +5 -6
  56. package/src/cli/config-cli.ts +6 -7
  57. package/src/cli/file-processor.ts +19 -17
  58. package/src/cli/jupyter-cli.ts +105 -0
  59. package/src/cli/list-models.ts +10 -11
  60. package/src/cli/plugin-cli.ts +20 -21
  61. package/src/cli/session-picker.ts +2 -3
  62. package/src/cli/setup-cli.ts +2 -3
  63. package/src/cli/stats-cli.ts +2 -3
  64. package/src/cli/update-cli.ts +25 -22
  65. package/src/commit/agentic/agent.ts +21 -23
  66. package/src/commit/agentic/fallback.ts +9 -9
  67. package/src/commit/agentic/index.ts +30 -38
  68. package/src/commit/agentic/state.ts +1 -6
  69. package/src/commit/agentic/tools/analyze-file.ts +15 -15
  70. package/src/commit/agentic/tools/git-file-diff.ts +3 -3
  71. package/src/commit/agentic/tools/git-hunk.ts +7 -7
  72. package/src/commit/agentic/tools/git-overview.ts +5 -5
  73. package/src/commit/agentic/tools/index.ts +14 -14
  74. package/src/commit/agentic/tools/propose-changelog.ts +6 -6
  75. package/src/commit/agentic/tools/propose-commit.ts +8 -8
  76. package/src/commit/agentic/tools/recent-commits.ts +2 -2
  77. package/src/commit/agentic/tools/split-commit.ts +19 -23
  78. package/src/commit/agentic/topo-sort.ts +1 -1
  79. package/src/commit/agentic/trivial.ts +3 -3
  80. package/src/commit/agentic/validation.ts +12 -12
  81. package/src/commit/analysis/conventional.ts +7 -11
  82. package/src/commit/analysis/index.ts +4 -4
  83. package/src/commit/analysis/scope.ts +4 -4
  84. package/src/commit/analysis/summary.ts +7 -9
  85. package/src/commit/analysis/validation.ts +1 -1
  86. package/src/commit/changelog/detect.ts +6 -6
  87. package/src/commit/changelog/generate.ts +7 -9
  88. package/src/commit/changelog/index.ts +13 -13
  89. package/src/commit/changelog/parse.ts +2 -2
  90. package/src/commit/cli.ts +1 -1
  91. package/src/commit/git/diff.ts +3 -3
  92. package/src/commit/git/index.ts +19 -24
  93. package/src/commit/index.ts +1 -1
  94. package/src/commit/map-reduce/index.ts +9 -9
  95. package/src/commit/map-reduce/map-phase.ts +19 -34
  96. package/src/commit/map-reduce/reduce-phase.ts +9 -11
  97. package/src/commit/message.ts +2 -2
  98. package/src/commit/model-selection.ts +3 -7
  99. package/src/commit/pipeline.ts +20 -22
  100. package/src/commit/utils/exclusions.ts +3 -3
  101. package/src/config/file-lock.ts +17 -7
  102. package/src/config/keybindings.ts +6 -8
  103. package/src/config/model-registry.ts +55 -37
  104. package/src/config/model-resolver.ts +18 -19
  105. package/src/config/prompt-templates.ts +11 -11
  106. package/src/config/settings-manager.ts +50 -34
  107. package/src/config.ts +60 -62
  108. package/src/cursor.ts +11 -9
  109. package/src/discovery/agents-md.ts +11 -12
  110. package/src/discovery/builtin.ts +68 -73
  111. package/src/discovery/claude.ts +41 -42
  112. package/src/discovery/cline.ts +11 -12
  113. package/src/discovery/codex.ts +52 -53
  114. package/src/discovery/cursor.ts +9 -10
  115. package/src/discovery/gemini.ts +17 -22
  116. package/src/discovery/github.ts +13 -14
  117. package/src/discovery/helpers.ts +35 -34
  118. package/src/discovery/index.ts +16 -18
  119. package/src/discovery/mcp-json.ts +8 -9
  120. package/src/discovery/ssh.ts +8 -9
  121. package/src/discovery/vscode.ts +4 -5
  122. package/src/discovery/windsurf.ts +6 -7
  123. package/src/exa/company.ts +1 -2
  124. package/src/exa/index.ts +2 -3
  125. package/src/exa/linkedin.ts +1 -2
  126. package/src/exa/mcp-client.ts +14 -16
  127. package/src/exa/render.ts +10 -11
  128. package/src/exa/researcher.ts +1 -2
  129. package/src/exa/search.ts +1 -2
  130. package/src/exa/types.ts +0 -1
  131. package/src/exa/websets.ts +1 -2
  132. package/src/exec/bash-executor.ts +3 -4
  133. package/src/exec/exec.ts +0 -1
  134. package/src/export/custom-share.ts +5 -6
  135. package/src/export/html/index.ts +24 -21
  136. package/src/export/ttsr.ts +2 -3
  137. package/src/extensibility/custom-commands/bundled/review/index.ts +7 -8
  138. package/src/extensibility/custom-commands/loader.ts +17 -14
  139. package/src/extensibility/custom-commands/types.ts +1 -2
  140. package/src/extensibility/custom-tools/loader.ts +10 -11
  141. package/src/extensibility/custom-tools/types.ts +6 -7
  142. package/src/extensibility/custom-tools/wrapper.ts +2 -3
  143. package/src/extensibility/extensions/loader.ts +75 -53
  144. package/src/extensibility/extensions/runner.ts +11 -12
  145. package/src/extensibility/extensions/types.ts +19 -26
  146. package/src/extensibility/extensions/wrapper.ts +3 -4
  147. package/src/extensibility/hooks/index.ts +1 -1
  148. package/src/extensibility/hooks/loader.ts +8 -9
  149. package/src/extensibility/hooks/runner.ts +7 -8
  150. package/src/extensibility/hooks/tool-wrapper.ts +0 -1
  151. package/src/extensibility/hooks/types.ts +10 -17
  152. package/src/extensibility/plugins/doctor.ts +3 -3
  153. package/src/extensibility/plugins/installer.ts +27 -27
  154. package/src/extensibility/plugins/loader.ts +59 -56
  155. package/src/extensibility/plugins/manager.ts +211 -171
  156. package/src/extensibility/plugins/parser.ts +1 -1
  157. package/src/extensibility/plugins/paths.ts +8 -8
  158. package/src/extensibility/skills.ts +63 -60
  159. package/src/extensibility/slash-commands.ts +10 -10
  160. package/src/index.ts +46 -46
  161. package/src/internal-urls/agent-protocol.ts +21 -11
  162. package/src/internal-urls/artifact-protocol.ts +17 -13
  163. package/src/internal-urls/router.ts +1 -2
  164. package/src/internal-urls/rule-protocol.ts +3 -4
  165. package/src/internal-urls/skill-protocol.ts +3 -4
  166. package/src/ipy/executor.ts +14 -10
  167. package/src/ipy/gateway-coordinator.ts +79 -90
  168. package/src/ipy/kernel.ts +32 -30
  169. package/src/ipy/modules.ts +13 -13
  170. package/src/lsp/client.ts +21 -10
  171. package/src/lsp/clients/biome-client.ts +1 -2
  172. package/src/lsp/clients/index.ts +3 -3
  173. package/src/lsp/clients/lsp-linter-client.ts +4 -5
  174. package/src/lsp/config.ts +15 -15
  175. package/src/lsp/edits.ts +4 -5
  176. package/src/lsp/index.ts +43 -44
  177. package/src/lsp/lspmux.ts +8 -8
  178. package/src/lsp/render.ts +10 -16
  179. package/src/lsp/utils.ts +3 -3
  180. package/src/main.ts +55 -34
  181. package/src/mcp/client.ts +2 -3
  182. package/src/mcp/config.ts +5 -6
  183. package/src/mcp/json-rpc.ts +0 -1
  184. package/src/mcp/loader.ts +3 -4
  185. package/src/mcp/manager.ts +17 -18
  186. package/src/mcp/tool-bridge.ts +4 -9
  187. package/src/mcp/tool-cache.ts +2 -3
  188. package/src/mcp/transports/http.ts +2 -4
  189. package/src/mcp/transports/stdio.ts +1 -2
  190. package/src/migrations.ts +60 -49
  191. package/src/modes/components/armin.ts +4 -5
  192. package/src/modes/components/assistant-message.ts +6 -6
  193. package/src/modes/components/bash-execution.ts +7 -8
  194. package/src/modes/components/bordered-loader.ts +3 -3
  195. package/src/modes/components/branch-summary-message.ts +3 -3
  196. package/src/modes/components/compaction-summary-message.ts +3 -3
  197. package/src/modes/components/countdown-timer.ts +0 -1
  198. package/src/modes/components/custom-message.ts +5 -5
  199. package/src/modes/components/diff.ts +1 -1
  200. package/src/modes/components/dynamic-border.ts +2 -2
  201. package/src/modes/components/extensions/extension-dashboard.ts +6 -7
  202. package/src/modes/components/extensions/extension-list.ts +2 -3
  203. package/src/modes/components/extensions/inspector-panel.ts +3 -4
  204. package/src/modes/components/extensions/state-manager.ts +25 -26
  205. package/src/modes/components/extensions/types.ts +1 -2
  206. package/src/modes/components/footer.ts +47 -43
  207. package/src/modes/components/history-search.ts +2 -2
  208. package/src/modes/components/hook-editor.ts +3 -4
  209. package/src/modes/components/hook-input.ts +2 -3
  210. package/src/modes/components/hook-message.ts +5 -5
  211. package/src/modes/components/hook-selector.ts +2 -3
  212. package/src/modes/components/keybinding-hints.ts +2 -3
  213. package/src/modes/components/login-dialog.ts +2 -2
  214. package/src/modes/components/model-selector.ts +12 -12
  215. package/src/modes/components/oauth-selector.ts +2 -2
  216. package/src/modes/components/plugin-settings.ts +20 -20
  217. package/src/modes/components/python-execution.ts +7 -8
  218. package/src/modes/components/queue-mode-selector.ts +3 -3
  219. package/src/modes/components/read-tool-group.ts +2 -2
  220. package/src/modes/components/session-selector.ts +4 -4
  221. package/src/modes/components/settings-defs.ts +77 -69
  222. package/src/modes/components/settings-selector.ts +16 -16
  223. package/src/modes/components/show-images-selector.ts +2 -2
  224. package/src/modes/components/status-line/segments.ts +4 -4
  225. package/src/modes/components/status-line/separators.ts +1 -1
  226. package/src/modes/components/status-line/types.ts +2 -2
  227. package/src/modes/components/status-line-segment-editor.ts +7 -8
  228. package/src/modes/components/status-line.ts +12 -12
  229. package/src/modes/components/theme-selector.ts +8 -7
  230. package/src/modes/components/thinking-selector.ts +4 -4
  231. package/src/modes/components/todo-display.ts +2 -2
  232. package/src/modes/components/todo-reminder.ts +4 -4
  233. package/src/modes/components/tool-execution.ts +11 -16
  234. package/src/modes/components/tree-selector.ts +11 -11
  235. package/src/modes/components/ttsr-notification.ts +5 -5
  236. package/src/modes/components/user-message-selector.ts +1 -1
  237. package/src/modes/components/user-message.ts +1 -1
  238. package/src/modes/components/visual-truncate.ts +0 -1
  239. package/src/modes/components/welcome.ts +4 -4
  240. package/src/modes/controllers/command-controller.ts +46 -47
  241. package/src/modes/controllers/event-controller.ts +16 -20
  242. package/src/modes/controllers/extension-ui-controller.ts +40 -46
  243. package/src/modes/controllers/input-controller.ts +17 -18
  244. package/src/modes/controllers/selector-controller.ts +103 -91
  245. package/src/modes/index.ts +3 -3
  246. package/src/modes/interactive-mode.ts +27 -29
  247. package/src/modes/print-mode.ts +12 -13
  248. package/src/modes/rpc/rpc-client.ts +7 -8
  249. package/src/modes/rpc/rpc-mode.ts +24 -25
  250. package/src/modes/rpc/rpc-types.ts +3 -4
  251. package/src/modes/theme/mermaid-cache.ts +2 -2
  252. package/src/modes/theme/theme.ts +128 -53
  253. package/src/modes/types.ts +10 -10
  254. package/src/modes/utils/ui-helpers.ts +17 -17
  255. package/src/patch/applicator.ts +18 -19
  256. package/src/patch/diff.ts +1 -2
  257. package/src/patch/fuzzy.ts +1 -2
  258. package/src/patch/index.ts +10 -11
  259. package/src/patch/normalize.ts +4 -4
  260. package/src/patch/normative.ts +1 -2
  261. package/src/patch/parser.ts +8 -9
  262. package/src/patch/shared.ts +12 -13
  263. package/src/sdk.ts +60 -63
  264. package/src/session/agent-session.ts +83 -84
  265. package/src/session/agent-storage.ts +11 -11
  266. package/src/session/artifacts.ts +8 -9
  267. package/src/session/auth-storage.ts +25 -29
  268. package/src/session/compaction/branch-summarization.ts +7 -10
  269. package/src/session/compaction/compaction.ts +8 -19
  270. package/src/session/compaction/utils.ts +6 -9
  271. package/src/session/history-storage.ts +10 -10
  272. package/src/session/messages.ts +4 -5
  273. package/src/session/session-manager.ts +76 -65
  274. package/src/session/session-storage.ts +57 -69
  275. package/src/session/storage-migration.ts +2 -3
  276. package/src/session/streaming-output.ts +2 -2
  277. package/src/ssh/connection-manager.ts +43 -50
  278. package/src/ssh/ssh-executor.ts +2 -2
  279. package/src/ssh/sshfs-mount.ts +11 -18
  280. package/src/system-prompt.ts +27 -34
  281. package/src/task/agents.ts +45 -30
  282. package/src/task/commands.ts +6 -7
  283. package/src/task/discovery.ts +39 -76
  284. package/src/task/executor.ts +14 -15
  285. package/src/task/index.ts +33 -36
  286. package/src/task/output-manager.ts +3 -4
  287. package/src/task/parallel.ts +0 -1
  288. package/src/task/render.ts +19 -20
  289. package/src/task/subprocess-tool-registry.ts +1 -2
  290. package/src/task/worker-protocol.ts +3 -3
  291. package/src/task/worker.ts +32 -38
  292. package/src/task/worktree.ts +19 -19
  293. package/src/tools/ask.ts +8 -9
  294. package/src/tools/bash-interceptor.ts +1 -5
  295. package/src/tools/bash.ts +19 -18
  296. package/src/tools/calculator.ts +12 -12
  297. package/src/tools/complete.ts +3 -4
  298. package/src/tools/context.ts +2 -2
  299. package/src/tools/fetch.ts +23 -26
  300. package/src/tools/find.ts +15 -16
  301. package/src/tools/gemini-image.ts +14 -14
  302. package/src/tools/grep.ts +27 -27
  303. package/src/tools/index.ts +78 -56
  304. package/src/tools/list-limit.ts +1 -1
  305. package/src/tools/ls.ts +7 -7
  306. package/src/tools/notebook.ts +5 -5
  307. package/src/tools/output-meta.ts +3 -4
  308. package/src/tools/output-utils.ts +1 -1
  309. package/src/tools/path-utils.ts +5 -5
  310. package/src/tools/python.ts +36 -37
  311. package/src/tools/read.ts +23 -23
  312. package/src/tools/render-utils.ts +8 -9
  313. package/src/tools/renderers.ts +6 -7
  314. package/src/tools/review.ts +8 -11
  315. package/src/tools/ssh.ts +31 -30
  316. package/src/tools/todo-write.ts +13 -13
  317. package/src/tools/tool-errors.ts +3 -3
  318. package/src/tools/tool-result.ts +3 -8
  319. package/src/tools/write.ts +11 -16
  320. package/src/tui/code-cell.ts +3 -9
  321. package/src/tui/file-list.ts +3 -4
  322. package/src/tui/output-block.ts +1 -2
  323. package/src/tui/status-line.ts +2 -3
  324. package/src/tui/tree-list.ts +2 -3
  325. package/src/tui/types.ts +1 -2
  326. package/src/tui/utils.ts +2 -3
  327. package/src/utils/changelog.ts +9 -10
  328. package/src/utils/clipboard.ts +11 -11
  329. package/src/utils/file-mentions.ts +4 -10
  330. package/src/utils/frontmatter.ts +6 -3
  331. package/src/utils/fuzzy.ts +2 -2
  332. package/src/utils/image-convert.ts +1 -1
  333. package/src/utils/image-resize.ts +1 -1
  334. package/src/utils/mime.ts +2 -2
  335. package/src/utils/shell-snapshot.ts +11 -13
  336. package/src/utils/shell.ts +4 -5
  337. package/src/utils/title-generator.ts +8 -9
  338. package/src/utils/tools-manager.ts +23 -23
  339. package/src/vendor/photon/index.js +1099 -1059
  340. package/src/vendor/photon/photon_rs_bg.wasm +0 -0
  341. package/src/web/scrapers/artifacthub.ts +1 -1
  342. package/src/web/scrapers/arxiv.ts +2 -2
  343. package/src/web/scrapers/bluesky.ts +2 -2
  344. package/src/web/scrapers/cheatsh.ts +1 -1
  345. package/src/web/scrapers/chocolatey.ts +2 -2
  346. package/src/web/scrapers/choosealicense.ts +5 -5
  347. package/src/web/scrapers/cisa-kev.ts +1 -1
  348. package/src/web/scrapers/crossref.ts +2 -2
  349. package/src/web/scrapers/devto.ts +3 -3
  350. package/src/web/scrapers/discogs.ts +3 -4
  351. package/src/web/scrapers/discourse.ts +1 -1
  352. package/src/web/scrapers/dockerhub.ts +1 -1
  353. package/src/web/scrapers/fdroid.ts +2 -2
  354. package/src/web/scrapers/firefox-addons.ts +3 -3
  355. package/src/web/scrapers/flathub.ts +1 -1
  356. package/src/web/scrapers/github.ts +3 -3
  357. package/src/web/scrapers/gitlab.ts +4 -4
  358. package/src/web/scrapers/hackernews.ts +2 -2
  359. package/src/web/scrapers/huggingface.ts +1 -1
  360. package/src/web/scrapers/iacr.ts +2 -2
  361. package/src/web/scrapers/index.ts +0 -1
  362. package/src/web/scrapers/jetbrains-marketplace.ts +1 -1
  363. package/src/web/scrapers/lemmy.ts +2 -2
  364. package/src/web/scrapers/maven.ts +2 -2
  365. package/src/web/scrapers/mdn.ts +2 -4
  366. package/src/web/scrapers/metacpan.ts +2 -2
  367. package/src/web/scrapers/musicbrainz.ts +1 -2
  368. package/src/web/scrapers/npm.ts +1 -1
  369. package/src/web/scrapers/nuget.ts +2 -2
  370. package/src/web/scrapers/nvd.ts +3 -3
  371. package/src/web/scrapers/ollama.ts +7 -9
  372. package/src/web/scrapers/opencorporates.ts +2 -2
  373. package/src/web/scrapers/openlibrary.ts +6 -6
  374. package/src/web/scrapers/orcid.ts +0 -1
  375. package/src/web/scrapers/osv.ts +2 -2
  376. package/src/web/scrapers/packagist.ts +1 -1
  377. package/src/web/scrapers/pubmed.ts +1 -2
  378. package/src/web/scrapers/rawg.ts +2 -2
  379. package/src/web/scrapers/readthedocs.ts +1 -2
  380. package/src/web/scrapers/repology.ts +2 -2
  381. package/src/web/scrapers/rfc.ts +1 -1
  382. package/src/web/scrapers/searchcode.ts +2 -2
  383. package/src/web/scrapers/semantic-scholar.ts +1 -1
  384. package/src/web/scrapers/snapcraft.ts +2 -2
  385. package/src/web/scrapers/sourcegraph.ts +1 -1
  386. package/src/web/scrapers/spdx.ts +3 -3
  387. package/src/web/scrapers/spotify.ts +0 -1
  388. package/src/web/scrapers/twitter.ts +1 -1
  389. package/src/web/scrapers/types.ts +1 -2
  390. package/src/web/scrapers/utils.ts +5 -5
  391. package/src/web/scrapers/wikidata.ts +3 -3
  392. package/src/web/scrapers/youtube.ts +9 -14
  393. package/src/web/search/auth.ts +4 -9
  394. package/src/web/search/index.ts +11 -21
  395. package/src/web/search/providers/anthropic.ts +3 -9
  396. package/src/web/search/providers/exa.ts +6 -10
  397. package/src/web/search/providers/perplexity.ts +5 -5
  398. package/src/web/search/render.ts +16 -18
  399. package/scripts/generate-wasm-b64.ts +0 -24
  400. package/src/commit/map-reduce/.map-phase.ts.kate-swp +0 -0
  401. package/src/task/.executor.ts.kate-swp +0 -0
  402. package/src/vendor/photon/photon_rs_bg.wasm.b64.js +0 -1
@@ -1,5 +1,6 @@
1
- import { existsSync, lstatSync, mkdirSync, readFileSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { isEnoent, logger } from "@oh-my-pi/pi-utils";
3
4
  import { extractPackageName, parsePluginSpec } from "./parser";
4
5
  import {
5
6
  getPluginsDir,
@@ -46,43 +47,47 @@ function validatePackageName(name: string): void {
46
47
  // =============================================================================
47
48
 
48
49
  export class PluginManager {
49
- private runtimeConfig: PluginRuntimeConfig;
50
+ private runtimeConfig: PluginRuntimeConfig | null = null;
50
51
  private cwd: string;
51
52
 
52
53
  constructor(cwd: string = process.cwd()) {
53
54
  this.cwd = cwd;
54
- this.runtimeConfig = this.loadRuntimeConfig();
55
55
  }
56
56
 
57
57
  // ==========================================================================
58
58
  // Runtime Config Management
59
59
  // ==========================================================================
60
60
 
61
- private loadRuntimeConfig(): PluginRuntimeConfig {
61
+ private async loadRuntimeConfig(): Promise<PluginRuntimeConfig> {
62
62
  const lockPath = getPluginsLockfile();
63
- if (!existsSync(lockPath)) {
64
- return { plugins: {}, settings: {} };
65
- }
66
63
  try {
67
- return JSON.parse(readFileSync(lockPath, "utf-8"));
68
- } catch {
64
+ return await Bun.file(lockPath).json();
65
+ } catch (err) {
66
+ if (isEnoent(err)) return { plugins: {}, settings: {} };
67
+ logger.warn("Failed to load plugin runtime config", { path: lockPath, error: String(err) });
69
68
  return { plugins: {}, settings: {} };
70
69
  }
71
70
  }
72
71
 
73
- private saveRuntimeConfig(): void {
74
- this.ensurePluginsDir();
75
- writeFileSync(getPluginsLockfile(), JSON.stringify(this.runtimeConfig, null, 2));
72
+ private async ensureConfigLoaded(): Promise<PluginRuntimeConfig> {
73
+ if (!this.runtimeConfig) {
74
+ this.runtimeConfig = await this.loadRuntimeConfig();
75
+ }
76
+ return this.runtimeConfig;
77
+ }
78
+
79
+ private async saveRuntimeConfig(): Promise<void> {
80
+ await this.ensureConfigLoaded();
81
+ await Bun.write(getPluginsLockfile(), JSON.stringify(this.runtimeConfig, null, 2));
76
82
  }
77
83
 
78
- private loadProjectOverrides(): ProjectPluginOverrides {
84
+ private async loadProjectOverrides(): Promise<ProjectPluginOverrides> {
79
85
  const overridesPath = getProjectPluginOverrides(this.cwd);
80
- if (!existsSync(overridesPath)) {
81
- return {};
82
- }
83
86
  try {
84
- return JSON.parse(readFileSync(overridesPath, "utf-8"));
85
- } catch {
87
+ return await Bun.file(overridesPath).json();
88
+ } catch (err) {
89
+ if (isEnoent(err)) return {};
90
+ logger.warn("Failed to load project plugin overrides", { path: overridesPath, error: String(err) });
86
91
  return {};
87
92
  }
88
93
  }
@@ -91,33 +96,32 @@ export class PluginManager {
91
96
  // Directory Management
92
97
  // ==========================================================================
93
98
 
94
- private ensurePluginsDir(): void {
95
- const dir = getPluginsDir();
96
- if (!existsSync(dir)) {
97
- mkdirSync(dir, { recursive: true });
98
- }
99
- const nodeModules = getPluginsNodeModules();
100
- if (!existsSync(nodeModules)) {
101
- mkdirSync(nodeModules, { recursive: true });
102
- }
99
+ private async ensurePluginsDir(): Promise<void> {
100
+ await fs.promises.mkdir(getPluginsDir(), { recursive: true });
101
+ await fs.promises.mkdir(getPluginsNodeModules(), { recursive: true });
103
102
  }
104
103
 
105
- private ensurePackageJson(): void {
106
- this.ensurePluginsDir();
104
+ private async ensurePackageJson(): Promise<void> {
107
105
  const pkgJsonPath = getPluginsPackageJson();
108
- if (!existsSync(pkgJsonPath)) {
109
- writeFileSync(
110
- pkgJsonPath,
111
- JSON.stringify(
112
- {
113
- name: "omp-plugins",
114
- private: true,
115
- dependencies: {},
116
- },
117
- null,
118
- 2,
119
- ),
120
- );
106
+ try {
107
+ await Bun.file(pkgJsonPath).json();
108
+ } catch (err) {
109
+ if (isEnoent(err)) {
110
+ await Bun.write(
111
+ pkgJsonPath,
112
+ JSON.stringify(
113
+ {
114
+ name: "omp-plugins",
115
+ private: true,
116
+ dependencies: {},
117
+ },
118
+ null,
119
+ 2,
120
+ ),
121
+ );
122
+ return;
123
+ }
124
+ throw err;
121
125
  }
122
126
  }
123
127
 
@@ -136,7 +140,7 @@ export class PluginManager {
136
140
  const spec = parsePluginSpec(specString);
137
141
  validatePackageName(spec.packageName);
138
142
 
139
- this.ensurePackageJson();
143
+ await this.ensurePackageJson();
140
144
 
141
145
  if (options.dryRun) {
142
146
  return {
@@ -165,13 +169,17 @@ export class PluginManager {
165
169
 
166
170
  // Resolve actual package name (strip version specifier)
167
171
  const actualName = extractPackageName(spec.packageName);
168
- const pkgPath = join(getPluginsNodeModules(), actualName, "package.json");
172
+ const pkgPath = path.join(getPluginsNodeModules(), actualName, "package.json");
169
173
 
170
- if (!existsSync(pkgPath)) {
171
- throw new Error(`Package installed but package.json not found at ${pkgPath}`);
174
+ let pkg: { name: string; version: string; omp?: PluginManifest; pi?: PluginManifest };
175
+ try {
176
+ pkg = await Bun.file(pkgPath).json();
177
+ } catch (err) {
178
+ if (isEnoent(err)) {
179
+ throw new Error(`Package installed but package.json not found at ${pkgPath}`);
180
+ }
181
+ throw err;
172
182
  }
173
-
174
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
175
183
  const manifest: PluginManifest = pkg.omp || pkg.pi || { version: pkg.version };
176
184
  manifest.version = pkg.version;
177
185
 
@@ -201,17 +209,18 @@ export class PluginManager {
201
209
  // null = use defaults
202
210
 
203
211
  // Update runtime config
204
- this.runtimeConfig.plugins[pkg.name] = {
212
+ const config = await this.ensureConfigLoaded();
213
+ config.plugins[pkg.name] = {
205
214
  version: pkg.version,
206
215
  enabledFeatures,
207
216
  enabled: true,
208
217
  };
209
- this.saveRuntimeConfig();
218
+ await this.saveRuntimeConfig();
210
219
 
211
220
  return {
212
221
  name: pkg.name,
213
222
  version: pkg.version,
214
- path: join(getPluginsNodeModules(), actualName),
223
+ path: path.join(getPluginsNodeModules(), actualName),
215
224
  manifest,
216
225
  enabledFeatures,
217
226
  enabled: true,
@@ -223,7 +232,7 @@ export class PluginManager {
223
232
  */
224
233
  async uninstall(name: string): Promise<void> {
225
234
  validatePackageName(name);
226
- this.ensurePackageJson();
235
+ await this.ensurePackageJson();
227
236
 
228
237
  const proc = Bun.spawn(["npm", "uninstall", name], {
229
238
  cwd: getPluginsDir(),
@@ -238,9 +247,10 @@ export class PluginManager {
238
247
  }
239
248
 
240
249
  // Remove from runtime config
241
- delete this.runtimeConfig.plugins[name];
242
- delete this.runtimeConfig.settings[name];
243
- this.saveRuntimeConfig();
250
+ const config = await this.ensureConfigLoaded();
251
+ delete config.plugins[name];
252
+ delete config.settings[name];
253
+ await this.saveRuntimeConfig();
244
254
  }
245
255
 
246
256
  /**
@@ -248,41 +258,49 @@ export class PluginManager {
248
258
  */
249
259
  async list(): Promise<InstalledPlugin[]> {
250
260
  const pkgJsonPath = getPluginsPackageJson();
251
- if (!existsSync(pkgJsonPath)) {
252
- return [];
261
+ let pkg: { dependencies?: Record<string, string> };
262
+ try {
263
+ pkg = await Bun.file(pkgJsonPath).json();
264
+ } catch (err) {
265
+ if (isEnoent(err)) return [];
266
+ throw err;
253
267
  }
254
268
 
255
- const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
256
269
  const deps = pkg.dependencies || {};
257
- const projectOverrides = this.loadProjectOverrides();
270
+ const projectOverrides = await this.loadProjectOverrides();
271
+ const config = await this.ensureConfigLoaded();
258
272
  const plugins: InstalledPlugin[] = [];
259
273
 
260
274
  for (const [name] of Object.entries(deps)) {
261
- const pluginPkgPath = join(getPluginsNodeModules(), name, "package.json");
262
- if (existsSync(pluginPkgPath)) {
263
- const pluginPkg = JSON.parse(readFileSync(pluginPkgPath, "utf-8"));
264
- const manifest: PluginManifest = pluginPkg.omp || pluginPkg.pi || { version: pluginPkg.version };
265
- manifest.version = pluginPkg.version;
266
-
267
- const runtimeState = this.runtimeConfig.plugins[name] || {
268
- version: pluginPkg.version,
269
- enabledFeatures: null,
270
- enabled: true,
271
- };
272
-
273
- // Apply project overrides
274
- const isDisabledInProject = projectOverrides.disabled?.includes(name) ?? false;
275
- const projectFeatures = projectOverrides.features?.[name];
276
-
277
- plugins.push({
278
- name,
279
- version: pluginPkg.version,
280
- path: join(getPluginsNodeModules(), name),
281
- manifest,
282
- enabledFeatures: projectFeatures ?? runtimeState.enabledFeatures,
283
- enabled: runtimeState.enabled && !isDisabledInProject,
284
- });
275
+ const pluginPkgPath = path.join(getPluginsNodeModules(), name, "package.json");
276
+ let pluginPkg: { version: string; omp?: PluginManifest; pi?: PluginManifest };
277
+ try {
278
+ pluginPkg = await Bun.file(pluginPkgPath).json();
279
+ } catch (err) {
280
+ if (isEnoent(err)) continue;
281
+ throw err;
285
282
  }
283
+ const manifest: PluginManifest = pluginPkg.omp || pluginPkg.pi || { version: pluginPkg.version };
284
+ manifest.version = pluginPkg.version;
285
+
286
+ const runtimeState = config.plugins[name] || {
287
+ version: pluginPkg.version,
288
+ enabledFeatures: null,
289
+ enabled: true,
290
+ };
291
+
292
+ // Apply project overrides
293
+ const isDisabledInProject = projectOverrides.disabled?.includes(name) ?? false;
294
+ const projectFeatures = projectOverrides.features?.[name];
295
+
296
+ plugins.push({
297
+ name,
298
+ version: pluginPkg.version,
299
+ path: path.join(getPluginsNodeModules(), name),
300
+ manifest,
301
+ enabledFeatures: projectFeatures ?? runtimeState.enabledFeatures,
302
+ enabled: runtimeState.enabled && !isDisabledInProject,
303
+ });
286
304
  }
287
305
 
288
306
  return plugins;
@@ -292,52 +310,53 @@ export class PluginManager {
292
310
  * Link a local plugin for development.
293
311
  */
294
312
  async link(localPath: string): Promise<InstalledPlugin> {
295
- const absolutePath = resolve(this.cwd, localPath);
313
+ const absolutePath = path.resolve(this.cwd, localPath);
296
314
 
297
- const pkgFile = join(absolutePath, "package.json");
298
- if (!existsSync(pkgFile)) {
299
- throw new Error(`package.json not found at ${absolutePath}`);
315
+ const pkgFilePath = path.join(absolutePath, "package.json");
316
+ let pkg: { name?: string; version: string; omp?: PluginManifest; pi?: PluginManifest };
317
+ try {
318
+ pkg = await Bun.file(pkgFilePath).json();
319
+ } catch (err) {
320
+ if (isEnoent(err)) throw new Error(`package.json not found at ${absolutePath}`);
321
+ throw err;
300
322
  }
301
-
302
- const pkg = JSON.parse(readFileSync(pkgFile, "utf-8"));
303
323
  if (!pkg.name) {
304
324
  throw new Error("package.json must have a name field");
305
325
  }
306
326
 
307
- this.ensurePluginsDir();
327
+ await this.ensurePluginsDir();
308
328
 
309
- const linkPath = join(getPluginsNodeModules(), pkg.name);
329
+ const linkPath = path.join(getPluginsNodeModules(), pkg.name);
310
330
 
311
331
  // Handle scoped packages
312
332
  if (pkg.name.startsWith("@")) {
313
- const scopeDir = join(getPluginsNodeModules(), pkg.name.split("/")[0]);
314
- if (!existsSync(scopeDir)) {
315
- mkdirSync(scopeDir, { recursive: true });
316
- }
333
+ const scopeDir = path.join(getPluginsNodeModules(), pkg.name.split("/")[0]);
334
+ await fs.promises.mkdir(scopeDir, { recursive: true });
317
335
  }
318
336
 
319
337
  // Remove existing
320
338
  try {
321
- const stat = lstatSync(linkPath);
322
- if (stat.isSymbolicLink() || stat.isDirectory()) {
323
- unlinkSync(linkPath);
339
+ const stats = await fs.promises.lstat(linkPath);
340
+ if (stats.isSymbolicLink() || stats.isDirectory()) {
341
+ await fs.promises.unlink(linkPath);
324
342
  }
325
- } catch {
326
- // Doesn't exist
343
+ } catch (err) {
344
+ if (!isEnoent(err)) throw err;
327
345
  }
328
346
 
329
- symlinkSync(absolutePath, linkPath);
347
+ await fs.promises.symlink(absolutePath, linkPath);
330
348
 
331
349
  const manifest: PluginManifest = pkg.omp || pkg.pi || { version: pkg.version };
332
350
  manifest.version = pkg.version;
333
351
 
334
352
  // Add to runtime config
335
- this.runtimeConfig.plugins[pkg.name] = {
353
+ const config = await this.ensureConfigLoaded();
354
+ config.plugins[pkg.name] = {
336
355
  version: pkg.version,
337
356
  enabledFeatures: null,
338
357
  enabled: true,
339
358
  };
340
- this.saveRuntimeConfig();
359
+ await this.saveRuntimeConfig();
341
360
 
342
361
  return {
343
362
  name: pkg.name,
@@ -357,11 +376,12 @@ export class PluginManager {
357
376
  * Enable or disable a plugin globally.
358
377
  */
359
378
  async setEnabled(name: string, enabled: boolean): Promise<void> {
360
- if (!this.runtimeConfig.plugins[name]) {
379
+ const config = await this.ensureConfigLoaded();
380
+ if (!config.plugins[name]) {
361
381
  throw new Error(`Plugin ${name} not found in runtime config`);
362
382
  }
363
- this.runtimeConfig.plugins[name].enabled = enabled;
364
- this.saveRuntimeConfig();
383
+ config.plugins[name].enabled = enabled;
384
+ await this.saveRuntimeConfig();
365
385
  }
366
386
 
367
387
  // ==========================================================================
@@ -371,22 +391,24 @@ export class PluginManager {
371
391
  /**
372
392
  * Get enabled features for a plugin.
373
393
  */
374
- getEnabledFeatures(name: string): string[] | null {
375
- return this.runtimeConfig.plugins[name]?.enabledFeatures ?? null;
394
+ async getEnabledFeatures(name: string): Promise<string[] | null> {
395
+ const config = await this.ensureConfigLoaded();
396
+ return config.plugins[name]?.enabledFeatures ?? null;
376
397
  }
377
398
 
378
399
  /**
379
400
  * Set enabled features for a plugin.
380
401
  */
381
402
  async setEnabledFeatures(name: string, features: string[] | null): Promise<void> {
382
- if (!this.runtimeConfig.plugins[name]) {
403
+ const config = await this.ensureConfigLoaded();
404
+ if (!config.plugins[name]) {
383
405
  throw new Error(`Plugin ${name} not found in runtime config`);
384
406
  }
385
407
 
386
408
  // Validate features if setting specific ones
387
409
  if (features && features.length > 0) {
388
410
  const plugins = await this.list();
389
- const plugin = plugins.find((p) => p.name === name);
411
+ const plugin = plugins.find(p => p.name === name);
390
412
  if (plugin?.manifest.features) {
391
413
  for (const feat of features) {
392
414
  if (!(feat in plugin.manifest.features)) {
@@ -398,8 +420,8 @@ export class PluginManager {
398
420
  }
399
421
  }
400
422
 
401
- this.runtimeConfig.plugins[name].enabledFeatures = features;
402
- this.saveRuntimeConfig();
423
+ config.plugins[name].enabledFeatures = features;
424
+ await this.saveRuntimeConfig();
403
425
  }
404
426
 
405
427
  // ==========================================================================
@@ -409,9 +431,10 @@ export class PluginManager {
409
431
  /**
410
432
  * Get all settings for a plugin.
411
433
  */
412
- getPluginSettings(name: string): Record<string, unknown> {
413
- const global = this.runtimeConfig.settings[name] || {};
414
- const projectOverrides = this.loadProjectOverrides();
434
+ async getPluginSettings(name: string): Promise<Record<string, unknown>> {
435
+ const config = await this.ensureConfigLoaded();
436
+ const global = config.settings[name] || {};
437
+ const projectOverrides = await this.loadProjectOverrides();
415
438
  const project = projectOverrides.settings?.[name] || {};
416
439
 
417
440
  // Project settings override global
@@ -421,21 +444,23 @@ export class PluginManager {
421
444
  /**
422
445
  * Set a plugin setting value.
423
446
  */
424
- setPluginSetting(name: string, key: string, value: unknown): void {
425
- if (!this.runtimeConfig.settings[name]) {
426
- this.runtimeConfig.settings[name] = {};
447
+ async setPluginSetting(name: string, key: string, value: unknown): Promise<void> {
448
+ const config = await this.ensureConfigLoaded();
449
+ if (!config.settings[name]) {
450
+ config.settings[name] = {};
427
451
  }
428
- this.runtimeConfig.settings[name][key] = value;
429
- this.saveRuntimeConfig();
452
+ config.settings[name][key] = value;
453
+ await this.saveRuntimeConfig();
430
454
  }
431
455
 
432
456
  /**
433
457
  * Delete a plugin setting.
434
458
  */
435
- deletePluginSetting(name: string, key: string): void {
436
- if (this.runtimeConfig.settings[name]) {
437
- delete this.runtimeConfig.settings[name][key];
438
- this.saveRuntimeConfig();
459
+ async deletePluginSetting(name: string, key: string): Promise<void> {
460
+ const config = await this.ensureConfigLoaded();
461
+ if (config.settings[name]) {
462
+ delete config.settings[name][key];
463
+ await this.saveRuntimeConfig();
439
464
  }
440
465
  }
441
466
 
@@ -451,15 +476,27 @@ export class PluginManager {
451
476
 
452
477
  // Check 1: Plugins directory exists
453
478
  const pluginsDir = getPluginsDir();
479
+ const pluginsDirExists = fs.existsSync(pluginsDir);
454
480
  checks.push({
455
481
  name: "plugins_directory",
456
- status: existsSync(pluginsDir) ? "ok" : "warning",
457
- message: existsSync(pluginsDir) ? `Found at ${pluginsDir}` : "Not created yet",
482
+ status: pluginsDirExists ? "ok" : "warning",
483
+ message: pluginsDirExists ? `Found at ${pluginsDir}` : "Not created yet",
458
484
  });
459
485
 
460
486
  // Check 2: package.json exists
461
487
  const pkgJsonPath = getPluginsPackageJson();
462
- const hasPkgJson = existsSync(pkgJsonPath);
488
+ let pkg: { dependencies?: Record<string, string> };
489
+ let hasPkgJson = true;
490
+ try {
491
+ pkg = await Bun.file(pkgJsonPath).json();
492
+ } catch (err) {
493
+ if (isEnoent(err)) {
494
+ hasPkgJson = false;
495
+ pkg = {};
496
+ } else {
497
+ throw err;
498
+ }
499
+ }
463
500
  checks.push({
464
501
  name: "package_manifest",
465
502
  status: hasPkgJson ? "ok" : "warning",
@@ -468,7 +505,7 @@ export class PluginManager {
468
505
 
469
506
  // Check 3: node_modules exists
470
507
  const nodeModulesPath = getPluginsNodeModules();
471
- const hasNodeModules = existsSync(nodeModulesPath);
508
+ const hasNodeModules = fs.existsSync(nodeModulesPath);
472
509
  checks.push({
473
510
  name: "node_modules",
474
511
  status: hasNodeModules ? "ok" : hasPkgJson ? "error" : "warning",
@@ -478,36 +515,37 @@ export class PluginManager {
478
515
  if (!hasPkgJson) {
479
516
  return checks;
480
517
  }
481
-
482
- // Check each installed plugin
483
- const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
484
518
  const deps = pkg.dependencies || {};
519
+ const config = await this.ensureConfigLoaded();
485
520
 
486
521
  for (const [name] of Object.entries(deps)) {
487
- const pluginPath = join(nodeModulesPath, name);
488
- const pluginPkgPath = join(pluginPath, "package.json");
489
-
490
- if (!existsSync(pluginPath)) {
491
- const fixed = options.fix ? await this.fixMissingPlugin() : false;
492
- checks.push({
493
- name: `plugin:${name}`,
494
- status: "error",
495
- message: "Missing from node_modules",
496
- fixed,
497
- });
498
- continue;
499
- }
500
-
501
- if (!existsSync(pluginPkgPath)) {
502
- checks.push({
503
- name: `plugin:${name}`,
504
- status: "error",
505
- message: "Missing package.json",
506
- });
507
- continue;
522
+ const pluginPath = path.join(nodeModulesPath, name);
523
+ const pluginPkgPath = path.join(pluginPath, "package.json");
524
+
525
+ let pluginPkg: { version: string; description?: string; omp?: PluginManifest; pi?: PluginManifest };
526
+ try {
527
+ pluginPkg = await Bun.file(pluginPkgPath).json();
528
+ } catch (err) {
529
+ if (isEnoent(err)) {
530
+ if (!fs.existsSync(pluginPath)) {
531
+ const fixed = options.fix ? await this.fixMissingPlugin() : false;
532
+ checks.push({
533
+ name: `plugin:${name}`,
534
+ status: "error",
535
+ message: "Missing from node_modules",
536
+ fixed,
537
+ });
538
+ } else {
539
+ checks.push({
540
+ name: `plugin:${name}`,
541
+ status: "error",
542
+ message: "Missing package.json",
543
+ });
544
+ }
545
+ continue;
546
+ }
547
+ throw err;
508
548
  }
509
-
510
- const pluginPkg = JSON.parse(readFileSync(pluginPkgPath, "utf-8"));
511
549
  const hasManifest = !!(pluginPkg.omp || pluginPkg.pi);
512
550
  const manifest: PluginManifest | undefined = pluginPkg.omp || pluginPkg.pi;
513
551
 
@@ -521,8 +559,8 @@ export class PluginManager {
521
559
 
522
560
  // Check tools path exists if specified
523
561
  if (manifest?.tools) {
524
- const toolsPath = join(pluginPath, manifest.tools);
525
- if (!existsSync(toolsPath)) {
562
+ const toolsPath = path.join(pluginPath, manifest.tools);
563
+ if (!fs.existsSync(toolsPath)) {
526
564
  checks.push({
527
565
  name: `plugin:${name}:tools`,
528
566
  status: "error",
@@ -533,8 +571,8 @@ export class PluginManager {
533
571
 
534
572
  // Check hooks path exists if specified
535
573
  if (manifest?.hooks) {
536
- const hooksPath = join(pluginPath, manifest.hooks);
537
- if (!existsSync(hooksPath)) {
574
+ const hooksPath = path.join(pluginPath, manifest.hooks);
575
+ if (!fs.existsSync(hooksPath)) {
538
576
  checks.push({
539
577
  name: `plugin:${name}:hooks`,
540
578
  status: "error",
@@ -544,11 +582,11 @@ export class PluginManager {
544
582
  }
545
583
 
546
584
  // Check enabled features exist in manifest
547
- const runtimeState = this.runtimeConfig.plugins[name];
585
+ const runtimeState = config.plugins[name];
548
586
  if (runtimeState?.enabledFeatures && manifest?.features) {
549
587
  for (const feat of runtimeState.enabledFeatures) {
550
588
  if (!(feat in manifest.features)) {
551
- const fixed = options.fix ? this.removeInvalidFeature(name, feat) : false;
589
+ const fixed = options.fix ? await this.removeInvalidFeature(name, feat) : false;
552
590
  checks.push({
553
591
  name: `plugin:${name}:feature:${feat}`,
554
592
  status: "warning",
@@ -561,9 +599,9 @@ export class PluginManager {
561
599
  }
562
600
 
563
601
  // Check for orphaned runtime config entries
564
- for (const name of Object.keys(this.runtimeConfig.plugins)) {
602
+ for (const name of Object.keys(config.plugins)) {
565
603
  if (!(name in deps)) {
566
- const fixed = options.fix ? this.removeOrphanedConfig(name) : false;
604
+ const fixed = options.fix ? await this.removeOrphanedConfig(name) : false;
567
605
  checks.push({
568
606
  name: `orphan:${name}`,
569
607
  status: "warning",
@@ -590,20 +628,22 @@ export class PluginManager {
590
628
  }
591
629
  }
592
630
 
593
- private removeInvalidFeature(name: string, feat: string): boolean {
594
- const state = this.runtimeConfig.plugins[name];
631
+ private async removeInvalidFeature(name: string, feat: string): Promise<boolean> {
632
+ const config = await this.ensureConfigLoaded();
633
+ const state = config.plugins[name];
595
634
  if (state?.enabledFeatures) {
596
- state.enabledFeatures = state.enabledFeatures.filter((f) => f !== feat);
597
- this.saveRuntimeConfig();
635
+ state.enabledFeatures = state.enabledFeatures.filter(f => f !== feat);
636
+ await this.saveRuntimeConfig();
598
637
  return true;
599
638
  }
600
639
  return false;
601
640
  }
602
641
 
603
- private removeOrphanedConfig(name: string): boolean {
604
- delete this.runtimeConfig.plugins[name];
605
- delete this.runtimeConfig.settings[name];
606
- this.saveRuntimeConfig();
642
+ private async removeOrphanedConfig(name: string): Promise<boolean> {
643
+ const config = await this.ensureConfigLoaded();
644
+ delete config.plugins[name];
645
+ delete config.settings[name];
646
+ await this.saveRuntimeConfig();
607
647
  return true;
608
648
  }
609
649
  }
@@ -57,7 +57,7 @@ export function parsePluginSpec(spec: string): ParsedPluginSpec {
57
57
  // Specific features (comma-separated)
58
58
  const features = featureStr
59
59
  .split(",")
60
- .map((f) => f.trim())
60
+ .map(f => f.trim())
61
61
  .filter(Boolean);
62
62
 
63
63
  return { packageName, features };