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