@oh-my-pi/pi-coding-agent 0.1.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 (337) hide show
  1. package/CHANGELOG.md +1629 -0
  2. package/README.md +1041 -0
  3. package/docs/compaction.md +403 -0
  4. package/docs/config-usage.md +113 -0
  5. package/docs/custom-tools.md +541 -0
  6. package/docs/extension-loading.md +1004 -0
  7. package/docs/hooks.md +867 -0
  8. package/docs/rpc.md +1040 -0
  9. package/docs/sdk.md +994 -0
  10. package/docs/session-tree-plan.md +441 -0
  11. package/docs/session.md +240 -0
  12. package/docs/skills.md +290 -0
  13. package/docs/theme.md +670 -0
  14. package/docs/tree.md +197 -0
  15. package/docs/tui.md +341 -0
  16. package/examples/README.md +21 -0
  17. package/examples/custom-tools/README.md +124 -0
  18. package/examples/custom-tools/hello/index.ts +20 -0
  19. package/examples/custom-tools/question/index.ts +84 -0
  20. package/examples/custom-tools/subagent/README.md +172 -0
  21. package/examples/custom-tools/subagent/agents/planner.md +37 -0
  22. package/examples/custom-tools/subagent/agents/scout.md +50 -0
  23. package/examples/custom-tools/subagent/agents/worker.md +24 -0
  24. package/examples/custom-tools/subagent/agents.ts +156 -0
  25. package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
  26. package/examples/custom-tools/subagent/commands/implement.md +10 -0
  27. package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
  28. package/examples/custom-tools/subagent/index.ts +1002 -0
  29. package/examples/custom-tools/todo/index.ts +212 -0
  30. package/examples/hooks/README.md +56 -0
  31. package/examples/hooks/auto-commit-on-exit.ts +49 -0
  32. package/examples/hooks/confirm-destructive.ts +59 -0
  33. package/examples/hooks/custom-compaction.ts +116 -0
  34. package/examples/hooks/dirty-repo-guard.ts +52 -0
  35. package/examples/hooks/file-trigger.ts +41 -0
  36. package/examples/hooks/git-checkpoint.ts +53 -0
  37. package/examples/hooks/handoff.ts +150 -0
  38. package/examples/hooks/permission-gate.ts +34 -0
  39. package/examples/hooks/protected-paths.ts +30 -0
  40. package/examples/hooks/qna.ts +119 -0
  41. package/examples/hooks/snake.ts +343 -0
  42. package/examples/hooks/status-line.ts +40 -0
  43. package/examples/sdk/01-minimal.ts +22 -0
  44. package/examples/sdk/02-custom-model.ts +49 -0
  45. package/examples/sdk/03-custom-prompt.ts +44 -0
  46. package/examples/sdk/04-skills.ts +44 -0
  47. package/examples/sdk/05-tools.ts +90 -0
  48. package/examples/sdk/06-hooks.ts +61 -0
  49. package/examples/sdk/07-context-files.ts +36 -0
  50. package/examples/sdk/08-slash-commands.ts +42 -0
  51. package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
  52. package/examples/sdk/10-settings.ts +38 -0
  53. package/examples/sdk/11-sessions.ts +48 -0
  54. package/examples/sdk/12-full-control.ts +95 -0
  55. package/examples/sdk/README.md +154 -0
  56. package/package.json +89 -0
  57. package/src/bun-imports.d.ts +16 -0
  58. package/src/capability/context-file.ts +40 -0
  59. package/src/capability/extension.ts +48 -0
  60. package/src/capability/hook.ts +40 -0
  61. package/src/capability/index.ts +616 -0
  62. package/src/capability/instruction.ts +37 -0
  63. package/src/capability/mcp.ts +52 -0
  64. package/src/capability/prompt.ts +35 -0
  65. package/src/capability/rule.ts +56 -0
  66. package/src/capability/settings.ts +35 -0
  67. package/src/capability/skill.ts +49 -0
  68. package/src/capability/slash-command.ts +40 -0
  69. package/src/capability/system-prompt.ts +35 -0
  70. package/src/capability/tool.ts +38 -0
  71. package/src/capability/types.ts +166 -0
  72. package/src/cli/args.ts +259 -0
  73. package/src/cli/file-processor.ts +121 -0
  74. package/src/cli/list-models.ts +104 -0
  75. package/src/cli/plugin-cli.ts +661 -0
  76. package/src/cli/session-picker.ts +41 -0
  77. package/src/cli/update-cli.ts +274 -0
  78. package/src/cli.ts +10 -0
  79. package/src/config.ts +391 -0
  80. package/src/core/agent-session.ts +2178 -0
  81. package/src/core/auth-storage.ts +258 -0
  82. package/src/core/bash-executor.ts +197 -0
  83. package/src/core/compaction/branch-summarization.ts +315 -0
  84. package/src/core/compaction/compaction.ts +664 -0
  85. package/src/core/compaction/index.ts +7 -0
  86. package/src/core/compaction/utils.ts +153 -0
  87. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  88. package/src/core/custom-commands/index.ts +15 -0
  89. package/src/core/custom-commands/loader.ts +226 -0
  90. package/src/core/custom-commands/types.ts +112 -0
  91. package/src/core/custom-tools/index.ts +22 -0
  92. package/src/core/custom-tools/loader.ts +248 -0
  93. package/src/core/custom-tools/types.ts +185 -0
  94. package/src/core/custom-tools/wrapper.ts +29 -0
  95. package/src/core/exec.ts +139 -0
  96. package/src/core/export-html/index.ts +159 -0
  97. package/src/core/export-html/template.css +774 -0
  98. package/src/core/export-html/template.generated.ts +2 -0
  99. package/src/core/export-html/template.html +45 -0
  100. package/src/core/export-html/template.js +1185 -0
  101. package/src/core/export-html/template.macro.ts +24 -0
  102. package/src/core/file-mentions.ts +54 -0
  103. package/src/core/hooks/index.ts +16 -0
  104. package/src/core/hooks/loader.ts +288 -0
  105. package/src/core/hooks/runner.ts +434 -0
  106. package/src/core/hooks/tool-wrapper.ts +98 -0
  107. package/src/core/hooks/types.ts +770 -0
  108. package/src/core/index.ts +53 -0
  109. package/src/core/logger.ts +112 -0
  110. package/src/core/mcp/client.ts +185 -0
  111. package/src/core/mcp/config.ts +248 -0
  112. package/src/core/mcp/index.ts +45 -0
  113. package/src/core/mcp/loader.ts +99 -0
  114. package/src/core/mcp/manager.ts +235 -0
  115. package/src/core/mcp/tool-bridge.ts +156 -0
  116. package/src/core/mcp/transports/http.ts +316 -0
  117. package/src/core/mcp/transports/index.ts +6 -0
  118. package/src/core/mcp/transports/stdio.ts +252 -0
  119. package/src/core/mcp/types.ts +228 -0
  120. package/src/core/messages.ts +211 -0
  121. package/src/core/model-registry.ts +334 -0
  122. package/src/core/model-resolver.ts +494 -0
  123. package/src/core/plugins/doctor.ts +67 -0
  124. package/src/core/plugins/index.ts +38 -0
  125. package/src/core/plugins/installer.ts +189 -0
  126. package/src/core/plugins/loader.ts +339 -0
  127. package/src/core/plugins/manager.ts +672 -0
  128. package/src/core/plugins/parser.ts +105 -0
  129. package/src/core/plugins/paths.ts +37 -0
  130. package/src/core/plugins/types.ts +190 -0
  131. package/src/core/sdk.ts +900 -0
  132. package/src/core/session-manager.ts +1837 -0
  133. package/src/core/settings-manager.ts +860 -0
  134. package/src/core/skills.ts +352 -0
  135. package/src/core/slash-commands.ts +132 -0
  136. package/src/core/system-prompt.ts +442 -0
  137. package/src/core/timings.ts +25 -0
  138. package/src/core/title-generator.ts +110 -0
  139. package/src/core/tools/ask.ts +193 -0
  140. package/src/core/tools/bash-interceptor.ts +120 -0
  141. package/src/core/tools/bash.ts +91 -0
  142. package/src/core/tools/context.ts +32 -0
  143. package/src/core/tools/edit-diff.ts +487 -0
  144. package/src/core/tools/edit.ts +140 -0
  145. package/src/core/tools/exa/company.ts +59 -0
  146. package/src/core/tools/exa/index.ts +63 -0
  147. package/src/core/tools/exa/linkedin.ts +59 -0
  148. package/src/core/tools/exa/mcp-client.ts +368 -0
  149. package/src/core/tools/exa/render.ts +200 -0
  150. package/src/core/tools/exa/researcher.ts +90 -0
  151. package/src/core/tools/exa/search.ts +338 -0
  152. package/src/core/tools/exa/types.ts +167 -0
  153. package/src/core/tools/exa/websets.ts +248 -0
  154. package/src/core/tools/find.ts +244 -0
  155. package/src/core/tools/grep.ts +584 -0
  156. package/src/core/tools/index.ts +283 -0
  157. package/src/core/tools/ls.ts +142 -0
  158. package/src/core/tools/lsp/client.ts +767 -0
  159. package/src/core/tools/lsp/clients/biome-client.ts +207 -0
  160. package/src/core/tools/lsp/clients/index.ts +49 -0
  161. package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
  162. package/src/core/tools/lsp/config.ts +845 -0
  163. package/src/core/tools/lsp/edits.ts +110 -0
  164. package/src/core/tools/lsp/index.ts +1364 -0
  165. package/src/core/tools/lsp/render.ts +560 -0
  166. package/src/core/tools/lsp/rust-analyzer.ts +145 -0
  167. package/src/core/tools/lsp/types.ts +495 -0
  168. package/src/core/tools/lsp/utils.ts +526 -0
  169. package/src/core/tools/notebook.ts +182 -0
  170. package/src/core/tools/output.ts +198 -0
  171. package/src/core/tools/path-utils.ts +61 -0
  172. package/src/core/tools/read.ts +507 -0
  173. package/src/core/tools/renderers.ts +820 -0
  174. package/src/core/tools/review.ts +275 -0
  175. package/src/core/tools/rulebook.ts +124 -0
  176. package/src/core/tools/task/agents.ts +158 -0
  177. package/src/core/tools/task/artifacts.ts +114 -0
  178. package/src/core/tools/task/commands.ts +157 -0
  179. package/src/core/tools/task/discovery.ts +217 -0
  180. package/src/core/tools/task/executor.ts +531 -0
  181. package/src/core/tools/task/index.ts +548 -0
  182. package/src/core/tools/task/model-resolver.ts +176 -0
  183. package/src/core/tools/task/parallel.ts +38 -0
  184. package/src/core/tools/task/render.ts +502 -0
  185. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  186. package/src/core/tools/task/types.ts +142 -0
  187. package/src/core/tools/truncate.ts +265 -0
  188. package/src/core/tools/web-fetch.ts +2511 -0
  189. package/src/core/tools/web-search/auth.ts +199 -0
  190. package/src/core/tools/web-search/index.ts +583 -0
  191. package/src/core/tools/web-search/providers/anthropic.ts +198 -0
  192. package/src/core/tools/web-search/providers/exa.ts +196 -0
  193. package/src/core/tools/web-search/providers/perplexity.ts +195 -0
  194. package/src/core/tools/web-search/render.ts +372 -0
  195. package/src/core/tools/web-search/types.ts +180 -0
  196. package/src/core/tools/write.ts +63 -0
  197. package/src/core/ttsr.ts +211 -0
  198. package/src/core/utils.ts +187 -0
  199. package/src/discovery/agents-md.ts +75 -0
  200. package/src/discovery/builtin.ts +647 -0
  201. package/src/discovery/claude.ts +623 -0
  202. package/src/discovery/cline.ts +104 -0
  203. package/src/discovery/codex.ts +571 -0
  204. package/src/discovery/cursor.ts +266 -0
  205. package/src/discovery/gemini.ts +368 -0
  206. package/src/discovery/github.ts +120 -0
  207. package/src/discovery/helpers.test.ts +127 -0
  208. package/src/discovery/helpers.ts +249 -0
  209. package/src/discovery/index.ts +84 -0
  210. package/src/discovery/mcp-json.ts +127 -0
  211. package/src/discovery/vscode.ts +99 -0
  212. package/src/discovery/windsurf.ts +219 -0
  213. package/src/index.ts +192 -0
  214. package/src/main.ts +507 -0
  215. package/src/migrations.ts +156 -0
  216. package/src/modes/cleanup.ts +23 -0
  217. package/src/modes/index.ts +48 -0
  218. package/src/modes/interactive/components/armin.ts +382 -0
  219. package/src/modes/interactive/components/assistant-message.ts +86 -0
  220. package/src/modes/interactive/components/bash-execution.ts +199 -0
  221. package/src/modes/interactive/components/bordered-loader.ts +41 -0
  222. package/src/modes/interactive/components/branch-summary-message.ts +42 -0
  223. package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
  224. package/src/modes/interactive/components/custom-editor.ts +122 -0
  225. package/src/modes/interactive/components/diff.ts +147 -0
  226. package/src/modes/interactive/components/dynamic-border.ts +25 -0
  227. package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
  228. package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
  229. package/src/modes/interactive/components/extensions/index.ts +9 -0
  230. package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
  231. package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
  232. package/src/modes/interactive/components/extensions/types.ts +191 -0
  233. package/src/modes/interactive/components/hook-editor.ts +117 -0
  234. package/src/modes/interactive/components/hook-input.ts +64 -0
  235. package/src/modes/interactive/components/hook-message.ts +96 -0
  236. package/src/modes/interactive/components/hook-selector.ts +91 -0
  237. package/src/modes/interactive/components/model-selector.ts +560 -0
  238. package/src/modes/interactive/components/oauth-selector.ts +136 -0
  239. package/src/modes/interactive/components/plugin-settings.ts +481 -0
  240. package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
  241. package/src/modes/interactive/components/session-selector.ts +220 -0
  242. package/src/modes/interactive/components/settings-defs.ts +597 -0
  243. package/src/modes/interactive/components/settings-selector.ts +545 -0
  244. package/src/modes/interactive/components/show-images-selector.ts +45 -0
  245. package/src/modes/interactive/components/status-line/index.ts +4 -0
  246. package/src/modes/interactive/components/status-line/presets.ts +94 -0
  247. package/src/modes/interactive/components/status-line/segments.ts +350 -0
  248. package/src/modes/interactive/components/status-line/separators.ts +55 -0
  249. package/src/modes/interactive/components/status-line/types.ts +81 -0
  250. package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
  251. package/src/modes/interactive/components/status-line.ts +384 -0
  252. package/src/modes/interactive/components/theme-selector.ts +62 -0
  253. package/src/modes/interactive/components/thinking-selector.ts +64 -0
  254. package/src/modes/interactive/components/tool-execution.ts +946 -0
  255. package/src/modes/interactive/components/tree-selector.ts +877 -0
  256. package/src/modes/interactive/components/ttsr-notification.ts +82 -0
  257. package/src/modes/interactive/components/user-message-selector.ts +159 -0
  258. package/src/modes/interactive/components/user-message.ts +18 -0
  259. package/src/modes/interactive/components/visual-truncate.ts +50 -0
  260. package/src/modes/interactive/components/welcome.ts +228 -0
  261. package/src/modes/interactive/interactive-mode.ts +2669 -0
  262. package/src/modes/interactive/theme/dark.json +102 -0
  263. package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
  264. package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
  265. package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
  266. package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
  267. package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
  268. package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
  269. package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
  270. package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
  271. package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
  272. package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
  273. package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
  274. package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
  275. package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
  276. package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
  277. package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
  278. package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
  279. package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
  280. package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
  281. package/src/modes/interactive/theme/defaults/index.ts +67 -0
  282. package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
  283. package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
  284. package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
  285. package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
  286. package/src/modes/interactive/theme/defaults/light-github.json +114 -0
  287. package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
  288. package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
  289. package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
  290. package/src/modes/interactive/theme/defaults/light-one.json +105 -0
  291. package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
  292. package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
  293. package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
  294. package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
  295. package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
  296. package/src/modes/interactive/theme/light.json +99 -0
  297. package/src/modes/interactive/theme/theme-schema.json +424 -0
  298. package/src/modes/interactive/theme/theme.ts +2211 -0
  299. package/src/modes/print-mode.ts +163 -0
  300. package/src/modes/rpc/rpc-client.ts +527 -0
  301. package/src/modes/rpc/rpc-mode.ts +494 -0
  302. package/src/modes/rpc/rpc-types.ts +203 -0
  303. package/src/prompts/architect-plan.md +10 -0
  304. package/src/prompts/branch-summary-preamble.md +3 -0
  305. package/src/prompts/branch-summary.md +28 -0
  306. package/src/prompts/browser.md +71 -0
  307. package/src/prompts/compaction-summary.md +34 -0
  308. package/src/prompts/compaction-turn-prefix.md +16 -0
  309. package/src/prompts/compaction-update-summary.md +41 -0
  310. package/src/prompts/explore.md +82 -0
  311. package/src/prompts/implement-with-critic.md +11 -0
  312. package/src/prompts/implement.md +11 -0
  313. package/src/prompts/init.md +30 -0
  314. package/src/prompts/plan.md +54 -0
  315. package/src/prompts/reviewer.md +81 -0
  316. package/src/prompts/summarization-system.md +3 -0
  317. package/src/prompts/system-prompt.md +27 -0
  318. package/src/prompts/task.md +56 -0
  319. package/src/prompts/title-system.md +8 -0
  320. package/src/prompts/tools/ask.md +24 -0
  321. package/src/prompts/tools/bash.md +23 -0
  322. package/src/prompts/tools/edit.md +9 -0
  323. package/src/prompts/tools/find.md +6 -0
  324. package/src/prompts/tools/grep.md +12 -0
  325. package/src/prompts/tools/lsp.md +14 -0
  326. package/src/prompts/tools/output.md +23 -0
  327. package/src/prompts/tools/read.md +25 -0
  328. package/src/prompts/tools/web-fetch.md +8 -0
  329. package/src/prompts/tools/web-search.md +10 -0
  330. package/src/prompts/tools/write.md +10 -0
  331. package/src/utils/changelog.ts +99 -0
  332. package/src/utils/clipboard.ts +265 -0
  333. package/src/utils/fuzzy.ts +108 -0
  334. package/src/utils/mime.ts +30 -0
  335. package/src/utils/shell-snapshot.ts +218 -0
  336. package/src/utils/shell.ts +364 -0
  337. package/src/utils/tools-manager.ts +265 -0
@@ -0,0 +1,661 @@
1
+ /**
2
+ * Plugin CLI command handlers.
3
+ *
4
+ * Handles `omp plugin <command>` subcommands for plugin lifecycle management.
5
+ */
6
+
7
+ import chalk from "chalk";
8
+ import { APP_NAME } from "../config";
9
+ import { PluginManager, parseSettingValue, validateSetting } from "../core/plugins/index";
10
+ import { theme } from "../modes/interactive/theme/theme";
11
+
12
+ // =============================================================================
13
+ // Types
14
+ // =============================================================================
15
+
16
+ export type PluginAction =
17
+ | "install"
18
+ | "uninstall"
19
+ | "list"
20
+ | "link"
21
+ | "doctor"
22
+ | "features"
23
+ | "config"
24
+ | "enable"
25
+ | "disable";
26
+
27
+ export interface PluginCommandArgs {
28
+ action: PluginAction;
29
+ args: string[];
30
+ flags: {
31
+ json?: boolean;
32
+ fix?: boolean;
33
+ force?: boolean;
34
+ dryRun?: boolean;
35
+ local?: boolean;
36
+ enable?: string;
37
+ disable?: string;
38
+ set?: string;
39
+ };
40
+ }
41
+
42
+ // =============================================================================
43
+ // Argument Parser
44
+ // =============================================================================
45
+
46
+ const VALID_ACTIONS: PluginAction[] = [
47
+ "install",
48
+ "uninstall",
49
+ "list",
50
+ "link",
51
+ "doctor",
52
+ "features",
53
+ "config",
54
+ "enable",
55
+ "disable",
56
+ ];
57
+
58
+ /**
59
+ * Parse plugin subcommand arguments.
60
+ * Returns undefined if not a plugin command.
61
+ */
62
+ export function parsePluginArgs(args: string[]): PluginCommandArgs | undefined {
63
+ if (args.length === 0 || args[0] !== "plugin") {
64
+ return undefined;
65
+ }
66
+
67
+ if (args.length < 2) {
68
+ return { action: "list", args: [], flags: {} };
69
+ }
70
+
71
+ const action = args[1];
72
+ if (!VALID_ACTIONS.includes(action as PluginAction)) {
73
+ console.error(chalk.red(`Unknown plugin command: ${action}`));
74
+ console.error(`Valid commands: ${VALID_ACTIONS.join(", ")}`);
75
+ process.exit(1);
76
+ }
77
+
78
+ const result: PluginCommandArgs = {
79
+ action: action as PluginAction,
80
+ args: [],
81
+ flags: {},
82
+ };
83
+
84
+ // Parse remaining arguments
85
+ for (let i = 2; i < args.length; i++) {
86
+ const arg = args[i];
87
+ if (arg === "--json") {
88
+ result.flags.json = true;
89
+ } else if (arg === "--fix") {
90
+ result.flags.fix = true;
91
+ } else if (arg === "--force") {
92
+ result.flags.force = true;
93
+ } else if (arg === "--dry-run") {
94
+ result.flags.dryRun = true;
95
+ } else if (arg === "-l" || arg === "--local") {
96
+ result.flags.local = true;
97
+ } else if (arg === "--enable" && i + 1 < args.length) {
98
+ result.flags.enable = args[++i];
99
+ } else if (arg === "--disable" && i + 1 < args.length) {
100
+ result.flags.disable = args[++i];
101
+ } else if (arg === "--set" && i + 1 < args.length) {
102
+ result.flags.set = args[++i];
103
+ } else if (!arg.startsWith("-")) {
104
+ result.args.push(arg);
105
+ }
106
+ }
107
+
108
+ return result;
109
+ }
110
+
111
+ // =============================================================================
112
+ // Command Handlers
113
+ // =============================================================================
114
+
115
+ /**
116
+ * Run a plugin command.
117
+ */
118
+ export async function runPluginCommand(cmd: PluginCommandArgs): Promise<void> {
119
+ const manager = new PluginManager();
120
+
121
+ switch (cmd.action) {
122
+ case "install":
123
+ await handleInstall(manager, cmd.args, cmd.flags);
124
+ break;
125
+ case "uninstall":
126
+ await handleUninstall(manager, cmd.args, cmd.flags);
127
+ break;
128
+ case "list":
129
+ await handleList(manager, cmd.flags);
130
+ break;
131
+ case "link":
132
+ await handleLink(manager, cmd.args, cmd.flags);
133
+ break;
134
+ case "doctor":
135
+ await handleDoctor(manager, cmd.flags);
136
+ break;
137
+ case "features":
138
+ await handleFeatures(manager, cmd.args, cmd.flags);
139
+ break;
140
+ case "config":
141
+ await handleConfig(manager, cmd.args, cmd.flags);
142
+ break;
143
+ case "enable":
144
+ await handleEnable(manager, cmd.args, cmd.flags);
145
+ break;
146
+ case "disable":
147
+ await handleDisable(manager, cmd.args, cmd.flags);
148
+ break;
149
+ }
150
+ }
151
+
152
+ async function handleInstall(
153
+ manager: PluginManager,
154
+ packages: string[],
155
+ flags: { json?: boolean; force?: boolean; dryRun?: boolean },
156
+ ): Promise<void> {
157
+ if (packages.length === 0) {
158
+ console.error(chalk.red(`Usage: ${APP_NAME} plugin install <package[@version]>[features] ...`));
159
+ console.error(chalk.dim("Examples:"));
160
+ console.error(chalk.dim(` ${APP_NAME} plugin install @oh-my-pi/exa`));
161
+ console.error(chalk.dim(` ${APP_NAME} plugin install @oh-my-pi/exa[search,websets]`));
162
+ console.error(chalk.dim(` ${APP_NAME} plugin install @oh-my-pi/exa[*] # all features`));
163
+ console.error(chalk.dim(` ${APP_NAME} plugin install @oh-my-pi/exa[] # no optional features`));
164
+ process.exit(1);
165
+ }
166
+
167
+ for (const spec of packages) {
168
+ try {
169
+ const result = await manager.install(spec, { force: flags.force, dryRun: flags.dryRun });
170
+
171
+ if (flags.json) {
172
+ console.log(JSON.stringify(result, null, 2));
173
+ } else {
174
+ if (flags.dryRun) {
175
+ console.log(chalk.dim(`[dry-run] Would install ${spec}`));
176
+ } else {
177
+ console.log(chalk.green(`${theme.status.success} Installed ${result.name}@${result.version}`));
178
+ if (result.enabledFeatures && result.enabledFeatures.length > 0) {
179
+ console.log(chalk.dim(` Features: ${result.enabledFeatures.join(", ")}`));
180
+ }
181
+ if (result.manifest.description) {
182
+ console.log(chalk.dim(` ${result.manifest.description}`));
183
+ }
184
+ }
185
+ }
186
+ } catch (err) {
187
+ console.error(chalk.red(`${theme.status.error} Failed to install ${spec}: ${err}`));
188
+ process.exit(1);
189
+ }
190
+ }
191
+ }
192
+
193
+ async function handleUninstall(manager: PluginManager, packages: string[], flags: { json?: boolean }): Promise<void> {
194
+ if (packages.length === 0) {
195
+ console.error(chalk.red(`Usage: ${APP_NAME} plugin uninstall <package> ...`));
196
+ process.exit(1);
197
+ }
198
+
199
+ for (const name of packages) {
200
+ try {
201
+ await manager.uninstall(name);
202
+
203
+ if (flags.json) {
204
+ console.log(JSON.stringify({ uninstalled: name }));
205
+ } else {
206
+ console.log(chalk.green(`${theme.status.success} Uninstalled ${name}`));
207
+ }
208
+ } catch (err) {
209
+ console.error(chalk.red(`${theme.status.error} Failed to uninstall ${name}: ${err}`));
210
+ process.exit(1);
211
+ }
212
+ }
213
+ }
214
+
215
+ async function handleList(manager: PluginManager, flags: { json?: boolean }): Promise<void> {
216
+ const plugins = await manager.list();
217
+
218
+ if (flags.json) {
219
+ console.log(JSON.stringify(plugins, null, 2));
220
+ return;
221
+ }
222
+
223
+ if (plugins.length === 0) {
224
+ console.log(chalk.dim("No plugins installed"));
225
+ console.log(chalk.dim(`\nInstall plugins with: ${APP_NAME} plugin install <package>`));
226
+ return;
227
+ }
228
+
229
+ console.log(chalk.bold("Installed Plugins:\n"));
230
+
231
+ for (const plugin of plugins) {
232
+ const status = plugin.enabled ? chalk.green(theme.status.enabled) : chalk.dim(theme.status.disabled);
233
+ const nameVersion = `${plugin.name}@${plugin.version}`;
234
+ console.log(`${status} ${nameVersion}`);
235
+
236
+ if (plugin.manifest.description) {
237
+ console.log(chalk.dim(` ${plugin.manifest.description}`));
238
+ }
239
+
240
+ if (plugin.enabledFeatures && plugin.enabledFeatures.length > 0) {
241
+ console.log(chalk.dim(` Features: ${plugin.enabledFeatures.join(", ")}`));
242
+ }
243
+
244
+ // Show available features if manifest has them
245
+ if (plugin.manifest.features) {
246
+ const availableFeatures = Object.keys(plugin.manifest.features);
247
+ if (availableFeatures.length > 0) {
248
+ const enabledSet = new Set(plugin.enabledFeatures ?? []);
249
+ const featureDisplay = availableFeatures
250
+ .map((f) => (enabledSet.has(f) ? chalk.green(f) : chalk.dim(f)))
251
+ .join(", ");
252
+ console.log(chalk.dim(` Available: [${featureDisplay}]`));
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ async function handleLink(manager: PluginManager, paths: string[], flags: { json?: boolean }): Promise<void> {
259
+ if (paths.length === 0) {
260
+ console.error(chalk.red(`Usage: ${APP_NAME} plugin link <path>`));
261
+ process.exit(1);
262
+ }
263
+
264
+ try {
265
+ const result = await manager.link(paths[0]);
266
+
267
+ if (flags.json) {
268
+ console.log(JSON.stringify(result, null, 2));
269
+ } else {
270
+ console.log(chalk.green(`${theme.status.success} Linked ${result.name} from ${paths[0]}`));
271
+ }
272
+ } catch (err) {
273
+ console.error(chalk.red(`${theme.status.error} Failed to link: ${err}`));
274
+ process.exit(1);
275
+ }
276
+ }
277
+
278
+ async function handleDoctor(manager: PluginManager, flags: { json?: boolean; fix?: boolean }): Promise<void> {
279
+ const checks = await manager.doctor({ fix: flags.fix });
280
+
281
+ if (flags.json) {
282
+ console.log(JSON.stringify(checks, null, 2));
283
+ return;
284
+ }
285
+
286
+ console.log(chalk.bold("Plugin Health Check\n"));
287
+
288
+ for (const check of checks) {
289
+ const icon =
290
+ check.status === "ok"
291
+ ? chalk.green(theme.status.success)
292
+ : check.status === "warning"
293
+ ? chalk.yellow(theme.status.warning)
294
+ : chalk.red(theme.status.error);
295
+ console.log(`${icon} ${check.name}: ${check.message}`);
296
+ if (check.fixed) {
297
+ console.log(chalk.dim(` ${theme.nav.cursor} Fixed`));
298
+ }
299
+ }
300
+
301
+ const errors = checks.filter((c) => c.status === "error" && !c.fixed).length;
302
+ const warnings = checks.filter((c) => c.status === "warning" && !c.fixed).length;
303
+ const ok = checks.filter((c) => c.status === "ok").length;
304
+ const fixed = checks.filter((c) => c.fixed).length;
305
+
306
+ console.log("");
307
+ console.log(`Summary: ${ok} ok, ${warnings} warnings, ${errors} errors${fixed > 0 ? `, ${fixed} fixed` : ""}`);
308
+
309
+ if (errors > 0) {
310
+ if (!flags.fix) {
311
+ console.log(chalk.dim("\nRun with --fix to attempt automatic repair"));
312
+ }
313
+ process.exit(1);
314
+ }
315
+ }
316
+
317
+ async function handleFeatures(
318
+ manager: PluginManager,
319
+ args: string[],
320
+ flags: { json?: boolean; enable?: string; disable?: string; set?: string },
321
+ ): Promise<void> {
322
+ if (args.length === 0) {
323
+ console.error(
324
+ chalk.red(`Usage: ${APP_NAME} plugin features <plugin> [--enable f1,f2] [--disable f1] [--set f1,f2]`),
325
+ );
326
+ process.exit(1);
327
+ }
328
+
329
+ const pluginName = args[0];
330
+ const plugins = await manager.list();
331
+ const plugin = plugins.find((p) => p.name === pluginName);
332
+
333
+ if (!plugin) {
334
+ console.error(chalk.red(`Plugin "${pluginName}" not found`));
335
+ process.exit(1);
336
+ }
337
+
338
+ // Handle modifications
339
+ if (flags.enable || flags.disable || flags.set) {
340
+ let currentFeatures = new Set(manager.getEnabledFeatures(pluginName) ?? []);
341
+
342
+ if (flags.set) {
343
+ // --set replaces all features
344
+ currentFeatures = new Set(
345
+ flags.set
346
+ .split(",")
347
+ .map((f) => f.trim())
348
+ .filter(Boolean),
349
+ );
350
+ } else {
351
+ if (flags.enable) {
352
+ for (const f of flags.enable
353
+ .split(",")
354
+ .map((f) => f.trim())
355
+ .filter(Boolean)) {
356
+ currentFeatures.add(f);
357
+ }
358
+ }
359
+ if (flags.disable) {
360
+ for (const f of flags.disable
361
+ .split(",")
362
+ .map((f) => f.trim())
363
+ .filter(Boolean)) {
364
+ currentFeatures.delete(f);
365
+ }
366
+ }
367
+ }
368
+
369
+ await manager.setEnabledFeatures(pluginName, [...currentFeatures]);
370
+ console.log(chalk.green(`${theme.status.success} Updated features for ${pluginName}`));
371
+ }
372
+
373
+ // Display current state
374
+ const updatedFeatures = manager.getEnabledFeatures(pluginName);
375
+
376
+ if (flags.json) {
377
+ console.log(
378
+ JSON.stringify(
379
+ {
380
+ plugin: pluginName,
381
+ enabledFeatures: updatedFeatures,
382
+ availableFeatures: plugin.manifest.features ? Object.keys(plugin.manifest.features) : [],
383
+ },
384
+ null,
385
+ 2,
386
+ ),
387
+ );
388
+ return;
389
+ }
390
+
391
+ console.log(chalk.bold(`Features for ${pluginName}:\n`));
392
+
393
+ if (!plugin.manifest.features || Object.keys(plugin.manifest.features).length === 0) {
394
+ console.log(chalk.dim(" No optional features available"));
395
+ return;
396
+ }
397
+
398
+ const enabledSet = new Set(updatedFeatures ?? []);
399
+ for (const [name, feat] of Object.entries(plugin.manifest.features)) {
400
+ const enabled = enabledSet.has(name);
401
+ const icon = enabled ? chalk.green(theme.status.enabled) : chalk.dim(theme.status.disabled);
402
+ const defaultLabel = feat.default ? chalk.dim(" (default)") : "";
403
+ console.log(`${icon} ${name}${defaultLabel}`);
404
+ if (feat.description) {
405
+ console.log(chalk.dim(` ${feat.description}`));
406
+ }
407
+ }
408
+ }
409
+
410
+ async function handleConfig(
411
+ manager: PluginManager,
412
+ args: string[],
413
+ flags: { json?: boolean; local?: boolean },
414
+ ): Promise<void> {
415
+ if (args.length === 0) {
416
+ console.error(
417
+ chalk.red(`Usage: ${APP_NAME} plugin config <list|get|set|delete|validate> <plugin> [key] [value]`),
418
+ );
419
+ process.exit(1);
420
+ }
421
+
422
+ const [subcommand, pluginName, key, ...valueArgs] = args;
423
+
424
+ // Special case: validate doesn't need a plugin name
425
+ if (subcommand === "validate") {
426
+ await handleConfigValidate(manager, flags);
427
+ return;
428
+ }
429
+
430
+ if (!pluginName) {
431
+ console.error(chalk.red("Plugin name required"));
432
+ process.exit(1);
433
+ }
434
+
435
+ const plugins = await manager.list();
436
+ const plugin = plugins.find((p) => p.name === pluginName);
437
+
438
+ if (!plugin) {
439
+ console.error(chalk.red(`Plugin "${pluginName}" not found`));
440
+ process.exit(1);
441
+ }
442
+
443
+ switch (subcommand) {
444
+ case "list": {
445
+ const settings = manager.getPluginSettings(pluginName);
446
+ const schema = plugin.manifest.settings || {};
447
+
448
+ if (flags.json) {
449
+ console.log(JSON.stringify({ settings, schema }, null, 2));
450
+ return;
451
+ }
452
+
453
+ console.log(chalk.bold(`Settings for ${pluginName}:\n`));
454
+
455
+ if (Object.keys(schema).length === 0) {
456
+ console.log(chalk.dim(" No settings defined"));
457
+ return;
458
+ }
459
+
460
+ for (const [k, s] of Object.entries(schema)) {
461
+ const value = settings[k] ?? s.default;
462
+ const displayValue = s.secret && value ? "********" : String(value ?? chalk.dim("(not set)"));
463
+ console.log(` ${k}: ${displayValue}`);
464
+ if (s.description) {
465
+ console.log(chalk.dim(` ${s.description}`));
466
+ }
467
+ if (s.env) {
468
+ console.log(chalk.dim(` env: ${s.env}`));
469
+ }
470
+ }
471
+ break;
472
+ }
473
+
474
+ case "get": {
475
+ if (!key) {
476
+ console.error(chalk.red("Key required"));
477
+ process.exit(1);
478
+ }
479
+
480
+ const settings = manager.getPluginSettings(pluginName);
481
+ const schema = plugin.manifest.settings?.[key];
482
+ const value = settings[key] ?? schema?.default;
483
+
484
+ if (flags.json) {
485
+ console.log(JSON.stringify({ [key]: value }));
486
+ } else {
487
+ const displayValue = schema?.secret && value ? "********" : String(value ?? "(not set)");
488
+ console.log(displayValue);
489
+ }
490
+ break;
491
+ }
492
+
493
+ case "set": {
494
+ if (!key) {
495
+ console.error(chalk.red("Key required"));
496
+ process.exit(1);
497
+ }
498
+
499
+ const valueStr = valueArgs.join(" ");
500
+ const schema = plugin.manifest.settings?.[key];
501
+
502
+ // Parse value according to type
503
+ let value: unknown = valueStr;
504
+ if (schema) {
505
+ value = parseSettingValue(valueStr, schema);
506
+
507
+ // Validate
508
+ const validation = validateSetting(value, schema);
509
+ if (!validation.valid) {
510
+ console.error(chalk.red(validation.error!));
511
+ process.exit(1);
512
+ }
513
+ }
514
+
515
+ manager.setPluginSetting(pluginName, key, value);
516
+ console.log(chalk.green(`${theme.status.success} Set ${key}`));
517
+ break;
518
+ }
519
+
520
+ case "delete": {
521
+ if (!key) {
522
+ console.error(chalk.red("Key required"));
523
+ process.exit(1);
524
+ }
525
+
526
+ manager.deletePluginSetting(pluginName, key);
527
+ console.log(chalk.green(`${theme.status.success} Deleted ${key}`));
528
+ break;
529
+ }
530
+
531
+ default:
532
+ console.error(chalk.red(`Unknown config subcommand: ${subcommand}`));
533
+ console.error(chalk.dim("Valid subcommands: list, get, set, delete, validate"));
534
+ process.exit(1);
535
+ }
536
+ }
537
+
538
+ async function handleConfigValidate(manager: PluginManager, flags: { json?: boolean }): Promise<void> {
539
+ const plugins = await manager.list();
540
+ const results: Array<{ plugin: string; key: string; error: string }> = [];
541
+
542
+ for (const plugin of plugins) {
543
+ const settings = manager.getPluginSettings(plugin.name);
544
+ const schema = plugin.manifest.settings || {};
545
+
546
+ for (const [key, s] of Object.entries(schema)) {
547
+ const value = settings[key];
548
+ if (value !== undefined) {
549
+ const validation = validateSetting(value, s);
550
+ if (!validation.valid) {
551
+ results.push({ plugin: plugin.name, key, error: validation.error! });
552
+ }
553
+ }
554
+ }
555
+ }
556
+
557
+ if (flags.json) {
558
+ console.log(JSON.stringify({ valid: results.length === 0, errors: results }, null, 2));
559
+ return;
560
+ }
561
+
562
+ if (results.length === 0) {
563
+ console.log(chalk.green(`${theme.status.success} All settings valid`));
564
+ } else {
565
+ for (const { plugin, key, error } of results) {
566
+ console.log(chalk.red(`${theme.status.error} ${plugin}.${key}: ${error}`));
567
+ }
568
+ process.exit(1);
569
+ }
570
+ }
571
+
572
+ async function handleEnable(manager: PluginManager, plugins: string[], flags: { json?: boolean }): Promise<void> {
573
+ if (plugins.length === 0) {
574
+ console.error(chalk.red(`Usage: ${APP_NAME} plugin enable <plugin> ...`));
575
+ process.exit(1);
576
+ }
577
+
578
+ for (const name of plugins) {
579
+ try {
580
+ await manager.setEnabled(name, true);
581
+
582
+ if (flags.json) {
583
+ console.log(JSON.stringify({ enabled: name }));
584
+ } else {
585
+ console.log(chalk.green(`${theme.status.success} Enabled ${name}`));
586
+ }
587
+ } catch (err) {
588
+ console.error(chalk.red(`${theme.status.error} Failed to enable ${name}: ${err}`));
589
+ process.exit(1);
590
+ }
591
+ }
592
+ }
593
+
594
+ async function handleDisable(manager: PluginManager, plugins: string[], flags: { json?: boolean }): Promise<void> {
595
+ if (plugins.length === 0) {
596
+ console.error(chalk.red(`Usage: ${APP_NAME} plugin disable <plugin> ...`));
597
+ process.exit(1);
598
+ }
599
+
600
+ for (const name of plugins) {
601
+ try {
602
+ await manager.setEnabled(name, false);
603
+
604
+ if (flags.json) {
605
+ console.log(JSON.stringify({ disabled: name }));
606
+ } else {
607
+ console.log(chalk.green(`${theme.status.success} Disabled ${name}`));
608
+ }
609
+ } catch (err) {
610
+ console.error(chalk.red(`${theme.status.error} Failed to disable ${name}: ${err}`));
611
+ process.exit(1);
612
+ }
613
+ }
614
+ }
615
+
616
+ // =============================================================================
617
+ // Help
618
+ // =============================================================================
619
+
620
+ export function printPluginHelp(): void {
621
+ console.log(`${chalk.bold(`${APP_NAME} plugin`)} - Plugin lifecycle management
622
+
623
+ ${chalk.bold("Commands:")}
624
+ install <pkg[@ver]>[features] Install plugins from npm
625
+ uninstall <pkg> Remove plugins
626
+ list Show installed plugins
627
+ link <path> Link local plugin for development
628
+ doctor Check plugin health
629
+ features <pkg> View/modify enabled features
630
+ config <cmd> <pkg> [key] [val] Manage plugin settings
631
+ enable <pkg> Enable a disabled plugin
632
+ disable <pkg> Disable plugin without uninstalling
633
+
634
+ ${chalk.bold("Feature Syntax:")}
635
+ pkg Install with default features
636
+ pkg[feat1,feat2] Install with specific features
637
+ pkg[*] Install with all features
638
+ pkg[] Install with no optional features
639
+
640
+ ${chalk.bold("Config Subcommands:")}
641
+ config list <pkg> List all settings
642
+ config get <pkg> <key> Get a setting value
643
+ config set <pkg> <key> <val> Set a setting value
644
+ config delete <pkg> <key> Delete a setting
645
+ config validate Validate all plugin settings
646
+
647
+ ${chalk.bold("Options:")}
648
+ --json Output as JSON
649
+ --fix Attempt automatic fixes (doctor)
650
+ --force Overwrite without prompting (install)
651
+ --dry-run Preview changes without applying (install)
652
+ -l, --local Use project-local overrides
653
+
654
+ ${chalk.bold("Examples:")}
655
+ ${APP_NAME} plugin install @oh-my-pi/exa[search]
656
+ ${APP_NAME} plugin list --json
657
+ ${APP_NAME} plugin features my-plugin --enable search,web
658
+ ${APP_NAME} plugin config set my-plugin apiKey sk-xxx
659
+ ${APP_NAME} plugin doctor --fix
660
+ `);
661
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * TUI session selector for --resume flag
3
+ */
4
+
5
+ import { ProcessTerminal, TUI } from "@oh-my-pi/pi-tui";
6
+ import type { SessionInfo } from "../core/session-manager";
7
+ import { SessionSelectorComponent } from "../modes/interactive/components/session-selector";
8
+
9
+ /** Show TUI session selector and return selected session path or null if cancelled */
10
+ export async function selectSession(sessions: SessionInfo[]): Promise<string | null> {
11
+ return new Promise((resolve) => {
12
+ const ui = new TUI(new ProcessTerminal());
13
+ let resolved = false;
14
+
15
+ const selector = new SessionSelectorComponent(
16
+ sessions,
17
+ (path: string) => {
18
+ if (!resolved) {
19
+ resolved = true;
20
+ ui.stop();
21
+ resolve(path);
22
+ }
23
+ },
24
+ () => {
25
+ if (!resolved) {
26
+ resolved = true;
27
+ ui.stop();
28
+ resolve(null);
29
+ }
30
+ },
31
+ () => {
32
+ ui.stop();
33
+ process.exit(0);
34
+ },
35
+ );
36
+
37
+ ui.addChild(selector);
38
+ ui.setFocus(selector.getSessionList());
39
+ ui.start();
40
+ });
41
+ }