@oh-my-pi/pi-coding-agent 3.15.0 → 3.20.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 (193) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/docs/extensions.md +1055 -0
  3. package/docs/rpc.md +69 -13
  4. package/docs/session-tree-plan.md +1 -1
  5. package/examples/extensions/README.md +141 -0
  6. package/examples/extensions/api-demo.ts +87 -0
  7. package/examples/extensions/chalk-logger.ts +26 -0
  8. package/examples/extensions/hello.ts +33 -0
  9. package/examples/extensions/pirate.ts +44 -0
  10. package/examples/extensions/plan-mode.ts +551 -0
  11. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  12. package/examples/extensions/todo.ts +299 -0
  13. package/examples/extensions/tools.ts +145 -0
  14. package/examples/extensions/with-deps/index.ts +36 -0
  15. package/examples/extensions/with-deps/package-lock.json +31 -0
  16. package/examples/extensions/with-deps/package.json +16 -0
  17. package/examples/sdk/02-custom-model.ts +3 -3
  18. package/examples/sdk/05-tools.ts +7 -3
  19. package/examples/sdk/06-extensions.ts +81 -0
  20. package/examples/sdk/06-hooks.ts +14 -13
  21. package/examples/sdk/08-prompt-templates.ts +42 -0
  22. package/examples/sdk/08-slash-commands.ts +17 -12
  23. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  24. package/examples/sdk/12-full-control.ts +6 -6
  25. package/package.json +11 -7
  26. package/src/capability/extension-module.ts +34 -0
  27. package/src/cli/args.ts +22 -7
  28. package/src/cli/file-processor.ts +38 -67
  29. package/src/cli/list-models.ts +1 -1
  30. package/src/config.ts +25 -14
  31. package/src/core/agent-session.ts +505 -242
  32. package/src/core/auth-storage.ts +33 -21
  33. package/src/core/compaction/branch-summarization.ts +4 -4
  34. package/src/core/compaction/compaction.ts +3 -3
  35. package/src/core/custom-commands/bundled/wt/index.ts +430 -0
  36. package/src/core/custom-commands/loader.ts +9 -0
  37. package/src/core/custom-tools/wrapper.ts +5 -0
  38. package/src/core/event-bus.ts +59 -0
  39. package/src/core/export-html/vendor/highlight.min.js +1213 -0
  40. package/src/core/export-html/vendor/marked.min.js +6 -0
  41. package/src/core/extensions/index.ts +100 -0
  42. package/src/core/extensions/loader.ts +501 -0
  43. package/src/core/extensions/runner.ts +477 -0
  44. package/src/core/extensions/types.ts +712 -0
  45. package/src/core/extensions/wrapper.ts +147 -0
  46. package/src/core/hooks/types.ts +2 -2
  47. package/src/core/index.ts +10 -21
  48. package/src/core/keybindings.ts +199 -0
  49. package/src/core/messages.ts +26 -7
  50. package/src/core/model-registry.ts +123 -46
  51. package/src/core/model-resolver.ts +7 -5
  52. package/src/core/prompt-templates.ts +242 -0
  53. package/src/core/sdk.ts +378 -295
  54. package/src/core/session-manager.ts +72 -58
  55. package/src/core/settings-manager.ts +118 -22
  56. package/src/core/system-prompt.ts +24 -1
  57. package/src/core/terminal-notify.ts +37 -0
  58. package/src/core/tools/context.ts +4 -4
  59. package/src/core/tools/exa/mcp-client.ts +5 -4
  60. package/src/core/tools/exa/render.ts +176 -131
  61. package/src/core/tools/gemini-image.ts +361 -0
  62. package/src/core/tools/git.ts +216 -0
  63. package/src/core/tools/index.ts +28 -15
  64. package/src/core/tools/lsp/config.ts +5 -4
  65. package/src/core/tools/lsp/index.ts +17 -12
  66. package/src/core/tools/lsp/render.ts +39 -47
  67. package/src/core/tools/read.ts +66 -29
  68. package/src/core/tools/render-utils.ts +268 -0
  69. package/src/core/tools/renderers.ts +243 -225
  70. package/src/core/tools/task/discovery.ts +2 -2
  71. package/src/core/tools/task/executor.ts +66 -58
  72. package/src/core/tools/task/index.ts +29 -10
  73. package/src/core/tools/task/model-resolver.ts +8 -13
  74. package/src/core/tools/task/omp-command.ts +24 -0
  75. package/src/core/tools/task/render.ts +35 -60
  76. package/src/core/tools/task/types.ts +3 -0
  77. package/src/core/tools/web-fetch.ts +29 -28
  78. package/src/core/tools/web-search/index.ts +6 -5
  79. package/src/core/tools/web-search/providers/exa.ts +6 -5
  80. package/src/core/tools/web-search/render.ts +66 -111
  81. package/src/core/voice-controller.ts +135 -0
  82. package/src/core/voice-supervisor.ts +1003 -0
  83. package/src/core/voice.ts +308 -0
  84. package/src/discovery/builtin.ts +75 -1
  85. package/src/discovery/claude.ts +47 -1
  86. package/src/discovery/codex.ts +54 -2
  87. package/src/discovery/gemini.ts +55 -2
  88. package/src/discovery/helpers.ts +100 -1
  89. package/src/discovery/index.ts +2 -0
  90. package/src/index.ts +14 -9
  91. package/src/lib/worktree/collapse.ts +179 -0
  92. package/src/lib/worktree/constants.ts +14 -0
  93. package/src/lib/worktree/errors.ts +23 -0
  94. package/src/lib/worktree/git.ts +110 -0
  95. package/src/lib/worktree/index.ts +23 -0
  96. package/src/lib/worktree/operations.ts +216 -0
  97. package/src/lib/worktree/session.ts +114 -0
  98. package/src/lib/worktree/stats.ts +67 -0
  99. package/src/main.ts +61 -37
  100. package/src/migrations.ts +37 -7
  101. package/src/modes/interactive/components/bash-execution.ts +6 -4
  102. package/src/modes/interactive/components/custom-editor.ts +55 -0
  103. package/src/modes/interactive/components/custom-message.ts +95 -0
  104. package/src/modes/interactive/components/extensions/extension-list.ts +5 -0
  105. package/src/modes/interactive/components/extensions/inspector-panel.ts +18 -12
  106. package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
  107. package/src/modes/interactive/components/extensions/types.ts +1 -0
  108. package/src/modes/interactive/components/footer.ts +324 -0
  109. package/src/modes/interactive/components/hook-editor.ts +1 -0
  110. package/src/modes/interactive/components/hook-selector.ts +3 -3
  111. package/src/modes/interactive/components/model-selector.ts +7 -6
  112. package/src/modes/interactive/components/oauth-selector.ts +3 -3
  113. package/src/modes/interactive/components/settings-defs.ts +55 -6
  114. package/src/modes/interactive/components/status-line/separators.ts +4 -4
  115. package/src/modes/interactive/components/status-line.ts +45 -35
  116. package/src/modes/interactive/components/tool-execution.ts +95 -23
  117. package/src/modes/interactive/interactive-mode.ts +644 -113
  118. package/src/modes/interactive/theme/defaults/alabaster.json +99 -0
  119. package/src/modes/interactive/theme/defaults/amethyst.json +103 -0
  120. package/src/modes/interactive/theme/defaults/anthracite.json +100 -0
  121. package/src/modes/interactive/theme/defaults/basalt.json +90 -0
  122. package/src/modes/interactive/theme/defaults/birch.json +101 -0
  123. package/src/modes/interactive/theme/defaults/dark-abyss.json +97 -0
  124. package/src/modes/interactive/theme/defaults/dark-aurora.json +94 -0
  125. package/src/modes/interactive/theme/defaults/dark-cavern.json +97 -0
  126. package/src/modes/interactive/theme/defaults/dark-copper.json +94 -0
  127. package/src/modes/interactive/theme/defaults/dark-cosmos.json +96 -0
  128. package/src/modes/interactive/theme/defaults/dark-eclipse.json +97 -0
  129. package/src/modes/interactive/theme/defaults/dark-ember.json +94 -0
  130. package/src/modes/interactive/theme/defaults/dark-equinox.json +96 -0
  131. package/src/modes/interactive/theme/defaults/dark-lavender.json +94 -0
  132. package/src/modes/interactive/theme/defaults/dark-lunar.json +95 -0
  133. package/src/modes/interactive/theme/defaults/dark-midnight.json +94 -0
  134. package/src/modes/interactive/theme/defaults/dark-nebula.json +96 -0
  135. package/src/modes/interactive/theme/defaults/dark-rainforest.json +97 -0
  136. package/src/modes/interactive/theme/defaults/dark-reef.json +97 -0
  137. package/src/modes/interactive/theme/defaults/dark-sakura.json +94 -0
  138. package/src/modes/interactive/theme/defaults/dark-slate.json +94 -0
  139. package/src/modes/interactive/theme/defaults/dark-solstice.json +96 -0
  140. package/src/modes/interactive/theme/defaults/dark-starfall.json +97 -0
  141. package/src/modes/interactive/theme/defaults/dark-swamp.json +96 -0
  142. package/src/modes/interactive/theme/defaults/dark-taiga.json +97 -0
  143. package/src/modes/interactive/theme/defaults/dark-terminal.json +94 -0
  144. package/src/modes/interactive/theme/defaults/dark-tundra.json +97 -0
  145. package/src/modes/interactive/theme/defaults/dark-twilight.json +97 -0
  146. package/src/modes/interactive/theme/defaults/dark-volcanic.json +97 -0
  147. package/src/modes/interactive/theme/defaults/graphite.json +99 -0
  148. package/src/modes/interactive/theme/defaults/index.ts +128 -0
  149. package/src/modes/interactive/theme/defaults/light-aurora-day.json +97 -0
  150. package/src/modes/interactive/theme/defaults/light-canyon.json +97 -0
  151. package/src/modes/interactive/theme/defaults/light-cirrus.json +96 -0
  152. package/src/modes/interactive/theme/defaults/light-coral.json +94 -0
  153. package/src/modes/interactive/theme/defaults/light-dawn.json +96 -0
  154. package/src/modes/interactive/theme/defaults/light-dunes.json +97 -0
  155. package/src/modes/interactive/theme/defaults/light-eucalyptus.json +94 -0
  156. package/src/modes/interactive/theme/defaults/light-frost.json +94 -0
  157. package/src/modes/interactive/theme/defaults/light-glacier.json +97 -0
  158. package/src/modes/interactive/theme/defaults/light-haze.json +96 -0
  159. package/src/modes/interactive/theme/defaults/light-honeycomb.json +94 -0
  160. package/src/modes/interactive/theme/defaults/light-lagoon.json +97 -0
  161. package/src/modes/interactive/theme/defaults/light-lavender.json +94 -0
  162. package/src/modes/interactive/theme/defaults/light-meadow.json +97 -0
  163. package/src/modes/interactive/theme/defaults/light-mint.json +94 -0
  164. package/src/modes/interactive/theme/defaults/light-opal.json +97 -0
  165. package/src/modes/interactive/theme/defaults/light-orchard.json +97 -0
  166. package/src/modes/interactive/theme/defaults/light-paper.json +94 -0
  167. package/src/modes/interactive/theme/defaults/light-prism.json +96 -0
  168. package/src/modes/interactive/theme/defaults/light-sand.json +94 -0
  169. package/src/modes/interactive/theme/defaults/light-savanna.json +97 -0
  170. package/src/modes/interactive/theme/defaults/light-soleil.json +96 -0
  171. package/src/modes/interactive/theme/defaults/light-wetland.json +97 -0
  172. package/src/modes/interactive/theme/defaults/light-zenith.json +95 -0
  173. package/src/modes/interactive/theme/defaults/limestone.json +100 -0
  174. package/src/modes/interactive/theme/defaults/mahogany.json +104 -0
  175. package/src/modes/interactive/theme/defaults/marble.json +99 -0
  176. package/src/modes/interactive/theme/defaults/obsidian.json +90 -0
  177. package/src/modes/interactive/theme/defaults/onyx.json +90 -0
  178. package/src/modes/interactive/theme/defaults/pearl.json +99 -0
  179. package/src/modes/interactive/theme/defaults/porcelain.json +90 -0
  180. package/src/modes/interactive/theme/defaults/quartz.json +102 -0
  181. package/src/modes/interactive/theme/defaults/sandstone.json +101 -0
  182. package/src/modes/interactive/theme/defaults/titanium.json +89 -0
  183. package/src/modes/print-mode.ts +14 -72
  184. package/src/modes/rpc/rpc-client.ts +23 -9
  185. package/src/modes/rpc/rpc-mode.ts +137 -125
  186. package/src/modes/rpc/rpc-types.ts +46 -24
  187. package/src/prompts/task.md +1 -0
  188. package/src/prompts/tools/gemini-image.md +4 -0
  189. package/src/prompts/tools/git.md +9 -0
  190. package/src/prompts/voice-summary.md +12 -0
  191. package/src/utils/image-convert.ts +26 -0
  192. package/src/utils/image-resize.ts +215 -0
  193. package/src/utils/shell-snapshot.ts +22 -20
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Shared utilities and constants for tool renderers.
3
+ *
4
+ * Provides consistent formatting, truncation, and display patterns across all
5
+ * tool renderers to ensure a unified TUI experience.
6
+ */
7
+
8
+ import type { Theme } from "../../modes/interactive/theme/theme";
9
+
10
+ // =============================================================================
11
+ // Standardized Display Constants
12
+ // =============================================================================
13
+
14
+ /** Preview limits for collapsed/expanded views */
15
+ export const PREVIEW_LIMITS = {
16
+ /** Lines shown in collapsed view */
17
+ COLLAPSED_LINES: 3,
18
+ /** Lines shown in expanded view */
19
+ EXPANDED_LINES: 12,
20
+ /** Items (files, results) shown in collapsed view */
21
+ COLLAPSED_ITEMS: 8,
22
+ /** Output preview lines in collapsed view */
23
+ OUTPUT_COLLAPSED: 3,
24
+ /** Output preview lines in expanded view */
25
+ OUTPUT_EXPANDED: 10,
26
+ } as const;
27
+
28
+ /** Truncation lengths for different content types */
29
+ export const TRUNCATE_LENGTHS = {
30
+ /** Short titles, labels */
31
+ TITLE: 60,
32
+ /** Medium-length content (messages, previews) */
33
+ CONTENT: 80,
34
+ /** Longer content (code, explanations) */
35
+ LONG: 100,
36
+ /** Full line content */
37
+ LINE: 110,
38
+ /** Very short (task previews, badges) */
39
+ SHORT: 40,
40
+ } as const;
41
+
42
+ /** Standard expand hint text */
43
+ export const EXPAND_HINT = "(Ctrl+O to expand)";
44
+
45
+ // =============================================================================
46
+ // Text Truncation Utilities
47
+ // =============================================================================
48
+
49
+ /**
50
+ * Truncate text to max length with ellipsis.
51
+ * The most commonly duplicated utility across renderers.
52
+ */
53
+ export function truncate(text: string, maxLen: number, ellipsis: string): string {
54
+ if (text.length <= maxLen) return text;
55
+ const sliceLen = Math.max(0, maxLen - ellipsis.length);
56
+ return `${text.slice(0, sliceLen)}${ellipsis}`;
57
+ }
58
+
59
+ /**
60
+ * Get first N lines of text as preview, with each line truncated.
61
+ */
62
+ export function getPreviewLines(text: string, maxLines: number, maxLineLen: number, ellipsis: string): string[] {
63
+ const lines = text.split("\n").filter((l) => l.trim());
64
+ return lines.slice(0, maxLines).map((l) => truncate(l.trim(), maxLineLen, ellipsis));
65
+ }
66
+
67
+ // =============================================================================
68
+ // URL Utilities
69
+ // =============================================================================
70
+
71
+ /**
72
+ * Extract domain from URL, stripping www. prefix.
73
+ */
74
+ export function getDomain(url: string): string {
75
+ try {
76
+ const u = new URL(url);
77
+ return u.hostname.replace(/^www\./, "");
78
+ } catch {
79
+ return url;
80
+ }
81
+ }
82
+
83
+ // =============================================================================
84
+ // Formatting Utilities
85
+ // =============================================================================
86
+
87
+ /**
88
+ * Format byte count for display (e.g., "1.5KB", "2.3MB").
89
+ */
90
+ export function formatBytes(bytes: number): string {
91
+ if (bytes < 1024) return `${bytes}B`;
92
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
93
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
94
+ }
95
+
96
+ /**
97
+ * Format token count for display (e.g., "1.5k", "25k").
98
+ */
99
+ export function formatTokens(tokens: number): string {
100
+ if (tokens >= 1000) {
101
+ return `${(tokens / 1000).toFixed(1)}k`;
102
+ }
103
+ return String(tokens);
104
+ }
105
+
106
+ /**
107
+ * Format duration for display (e.g., "500ms", "2.5s", "1.2m").
108
+ */
109
+ export function formatDuration(ms: number): string {
110
+ if (ms < 1000) return `${ms}ms`;
111
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
112
+ return `${(ms / 60000).toFixed(1)}m`;
113
+ }
114
+
115
+ /**
116
+ * Format count with pluralized label (e.g., "3 files", "1 error").
117
+ */
118
+ export function formatCount(label: string, count: number): string {
119
+ const safeCount = Number.isFinite(count) ? count : 0;
120
+ return `${safeCount} ${pluralize(label, safeCount)}`;
121
+ }
122
+
123
+ /**
124
+ * Format age from seconds to human-readable string.
125
+ */
126
+ export function formatAge(ageSeconds: number | null | undefined): string {
127
+ if (!ageSeconds) return "";
128
+ const mins = Math.floor(ageSeconds / 60);
129
+ const hours = Math.floor(mins / 60);
130
+ const days = Math.floor(hours / 24);
131
+ const weeks = Math.floor(days / 7);
132
+ const months = Math.floor(days / 30);
133
+
134
+ if (months > 0) return `${months}mo ago`;
135
+ if (weeks > 0) return `${weeks}w ago`;
136
+ if (days > 0) return `${days}d ago`;
137
+ if (hours > 0) return `${hours}h ago`;
138
+ if (mins > 0) return `${mins}m ago`;
139
+ return "just now";
140
+ }
141
+
142
+ // =============================================================================
143
+ // Theme Helper Utilities
144
+ // =============================================================================
145
+
146
+ /**
147
+ * Get the appropriate status icon with color for a given state.
148
+ * Standardizes status icon usage across all renderers.
149
+ */
150
+ export function getStyledStatusIcon(
151
+ status: "success" | "error" | "warning" | "info" | "pending" | "running" | "aborted",
152
+ theme: Theme,
153
+ spinnerFrame?: number,
154
+ ): string {
155
+ switch (status) {
156
+ case "success":
157
+ return theme.styledSymbol("status.success", "success");
158
+ case "error":
159
+ return theme.styledSymbol("status.error", "error");
160
+ case "warning":
161
+ return theme.styledSymbol("status.warning", "warning");
162
+ case "info":
163
+ return theme.styledSymbol("status.info", "accent");
164
+ case "pending":
165
+ return theme.styledSymbol("status.pending", "muted");
166
+ case "running":
167
+ if (spinnerFrame !== undefined) {
168
+ const frames = theme.spinnerFrames;
169
+ return frames[spinnerFrame % frames.length];
170
+ }
171
+ return theme.styledSymbol("status.running", "accent");
172
+ case "aborted":
173
+ return theme.styledSymbol("status.aborted", "error");
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Format the expand hint with proper theming.
179
+ * Returns empty string if already expanded or there is nothing more to show.
180
+ */
181
+ export function formatExpandHint(expanded: boolean, hasMore: boolean, theme: Theme): string {
182
+ return !expanded && hasMore ? theme.fg("dim", ` ${EXPAND_HINT}`) : "";
183
+ }
184
+
185
+ /**
186
+ * Format a badge like [done] or [failed] with brackets and color.
187
+ */
188
+ export function formatBadge(
189
+ label: string,
190
+ color: "success" | "error" | "warning" | "accent" | "muted",
191
+ theme: Theme,
192
+ ): string {
193
+ const left = theme.format.bracketLeft;
194
+ const right = theme.format.bracketRight;
195
+ return theme.fg(color, `${left}${label}${right}`);
196
+ }
197
+
198
+ /**
199
+ * Build a "more items" suffix line for truncated lists.
200
+ * Uses consistent wording pattern.
201
+ */
202
+ export function formatMoreItems(remaining: number, itemType: string, theme: Theme): string {
203
+ const safeRemaining = Number.isFinite(remaining) ? remaining : 0;
204
+ return `${theme.format.ellipsis} ${safeRemaining} more ${pluralize(itemType, safeRemaining)}`;
205
+ }
206
+
207
+ function pluralize(label: string, count: number): string {
208
+ if (count === 1) return label;
209
+ if (/(?:ch|sh|s|x|z)$/i.test(label)) return `${label}es`;
210
+ if (/[^aeiou]y$/i.test(label)) return `${label.slice(0, -1)}ies`;
211
+ return `${label}s`;
212
+ }
213
+
214
+ // =============================================================================
215
+ // Tree Rendering Utilities
216
+ // =============================================================================
217
+
218
+ /**
219
+ * Get the branch character for a tree item.
220
+ */
221
+ export function getTreeBranch(isLast: boolean, theme: Theme): string {
222
+ return isLast ? theme.tree.last : theme.tree.branch;
223
+ }
224
+
225
+ /**
226
+ * Get the continuation prefix for nested content under a tree item.
227
+ */
228
+ export function getTreeContinuePrefix(isLast: boolean, theme: Theme): string {
229
+ return isLast ? " " : `${theme.tree.vertical} `;
230
+ }
231
+
232
+ /**
233
+ * Render a list of items with tree branches, handling truncation.
234
+ *
235
+ * @param items - Full list of items to render
236
+ * @param expanded - Whether view is expanded
237
+ * @param maxCollapsed - Max items to show when collapsed
238
+ * @param renderItem - Function to render a single item
239
+ * @param itemType - Type name for "more X" message (e.g., "file", "entry")
240
+ * @param theme - Theme instance
241
+ * @returns Array of formatted lines
242
+ */
243
+ export function renderTreeList<T>(
244
+ items: T[],
245
+ expanded: boolean,
246
+ maxCollapsed: number,
247
+ renderItem: (item: T, branch: string, isLast: boolean, theme: Theme) => string,
248
+ itemType: string,
249
+ theme: Theme,
250
+ ): string[] {
251
+ const lines: string[] = [];
252
+ const maxItems = expanded ? items.length : Math.min(items.length, maxCollapsed);
253
+
254
+ for (let i = 0; i < maxItems; i++) {
255
+ const isLast = i === maxItems - 1 && (expanded || items.length <= maxCollapsed);
256
+ const branch = getTreeBranch(isLast, theme);
257
+ lines.push(renderItem(items[i], branch, isLast, theme));
258
+ }
259
+
260
+ if (!expanded && items.length > maxCollapsed) {
261
+ const remaining = items.length - maxCollapsed;
262
+ lines.push(
263
+ ` ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, itemType, theme))}`,
264
+ );
265
+ }
266
+
267
+ return lines;
268
+ }